diff --git a/.github/workflows/ruby.yml b/.github/workflows/Windows.yml similarity index 69% rename from .github/workflows/ruby.yml rename to .github/workflows/Windows.yml index a375f704..87d19d4f 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/Windows.yml @@ -4,13 +4,9 @@ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby -name: Building iodine +name: Windows CI -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] +on: [push, pull_request] permissions: contents: read @@ -20,23 +16,27 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.3', '2.7', '3.0', '3.1', '3.2'] - os: [ubuntu-latest, macos-latest] # , windows-latest + ruby-version: ['3.2', '3.3'] + os: [windows-2022] # , windows-latest runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby # see https://github.com/ruby/setup-ruby#versioning) uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - name: Build and Test Iodine + - name: Setup ENV + run: | + echo "CI=false" >> "$GITHUB_ENV" + - name: Install Gems + run: bundle install + - name: Compile and Install Iodine run: | echo CFLAGS = $CFLAGS echo cflags = $cflags echo HOME = $HOME ruby -e 'puts Gem.default_dir' - bundle exec rake install + env VERBOSE=1 bundle exec rake install --trace # env VERBOSE=1 bundle exec rspec --format documentation # - name: Run tests # run: bundle exec rake diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml new file mode 100644 index 00000000..ef739a6b --- /dev/null +++ b/.github/workflows/posix.yml @@ -0,0 +1,42 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: POSIX CI + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + test: + strategy: + fail-fast: false + matrix: + ruby-version: ['3.2', '3.3'] + os: [ubuntu-24.04, macos-14] # , windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby # see https://github.com/ruby/setup-ruby#versioning) + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + - name: Setup ENV + run: | + echo "CI=false" >> "$GITHUB_ENV" + - name: Install Gems + run: bundle install + - name: Compile Iodine + run: | + echo CFLAGS = $CFLAGS + echo cflags = $cflags + echo HOME = $HOME + ruby -e 'puts Gem.default_dir' + bundle exec rake compile -v +# env VERBOSE=1 bundle exec rspec --format documentation +# - name: Run tests +# run: bundle exec rake diff --git a/.yardopts b/.yardopts index 65590056..f6e2978b 100644 --- a/.yardopts +++ b/.yardopts @@ -4,5 +4,4 @@ LIMITS.md CHANGELOG.md LICENSE.txt -SPEC-WebSocket-Draft.md -SPEC-PubSub-Draft.md +mustache.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1df2b1..7e0465e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,37 @@ # Iodine + [![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine) [![Inline docs](http://inch-ci.org/github/boazsegev/iodine.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/iodine/master/frames) -Please notice that this change log contains changes for upcoming releases as well. Please refer to the current gem version to review the current release. +Please notice that this change log contains updates for upcoming releases as well as previous releases. + +Please refer to the current gem version to review the relevant changes for your release. + +## Changes + +#### Change log v.0.8.0.rc (2024----) + +**Update**: updated to facil.io 0.8.0, using the latest version of the [facil.io C STL](https://github.com/facil-io/cstl). + +**Update**: totally re-written both the C extension and the Ruby code. + +**TODO**: this version is still missing Pub/Sub engines and Redis support, which will be added (hopefully) soon. + +------------------------ + +## Total Rewrite -## Changes: +The new version is a complete rewrite, with a new architecture and a new API. Backwards compatibility was kept where possible. + +All previous changes influenced the architecture of the new version. + +------------------------ + +#### Change log v.0.7.58 (2024-04-28) + +**Fix**: possible fix for compilation issues on Fedora. Credit to @garytaylor for opening issue #155. + +**Fix**: possible fix for an OpenSSL certificate chain import issue that would cause certificate chains to be imported incorrectly. Credit to @dwolrdcojp for opening the facil.io repo PR #151. #### Change log v.0.7.57 (2023-09-04) @@ -142,7 +169,7 @@ Please notice that this change log contains changes for upcoming releases as wel #### Change log v.0.7.31 -**Security**: a heap-overflow vulnerability was fixed in the WebSocket parser. This attack could have been triggered remotely by a maliciously crafted message-header. Credit to Dane (4cad@silvertoque) for exposing this issue and providing a Python script demonstrating the attack. +**Security**: a heap-overflow vulnerability was fixed in the WebSocket parser. This attack could have been triggered remotely by a maliciously crafted message-header. Credit to Dane (4cad@silvertoque) for exposing this issue and providing a Python script demonstrating the attack. It's recommended that all iodine users update to the latest version. @@ -208,7 +235,7 @@ It's recommended that all iodine users update to the latest version. #### Change log v.0.7.20 -**Security**: (`fio`) lower and smarter Slowloris detection limits (backlog limit is now 1,024 responses / messages per client). +**Security**: (`fio`) lower and smarter Slowloris detection limits (backlog limit is now 1,024 responses / messages per client). **Security**: (`http`) HTTP/1.1 slow client throttling - new requests will not be consumed until pending responses were sent. Since HTTP/1.1 is a response-request protocol, this protocol specific approach should protect the HTTP application against slow clients. @@ -392,7 +419,7 @@ It's recommended that all iodine users update to the latest version. #### Change log v.0.7.0 -This version bump is performed because the internal engine changed significantly and might be considered less mature. The public API remains unbroken. +This version bump is performed because the internal engine changed significantly and might be considered less mature. The public API remains unbroken. **Fix**: Fixed a documentation error. Credit to @Fonsan (Erik Fonselius) for PR #41. @@ -400,7 +427,6 @@ This version bump is performed because the internal engine changed significantly **Update**: (facil.io) Updated to facil.io version 0.7.0 (edge). This could effect memory consumption behavior but otherwise shouldn't effect iodine all that much. - #### Change log v.0.6.5 **Fix**: (facil.io - logging) Fix typo in log output. Credit to @bjeanes (Bo Jeanes) for PR #39. @@ -685,7 +711,7 @@ Iodine.start **Compatibility**: (from `facil.io`) Now checks for HTTP/1.0 clients to determine connection persistence. -**Compatibility**: (from `facil.io`) Added spaces after header names (`:` => `: `), since some parsers don't seem to read the RFC. +**Compatibility**: (from `facil.io`) Added spaces after header names (`:` => `:`), since some parsers don't seem to read the RFC. --- @@ -699,7 +725,7 @@ Iodine.start #### Change log v.0.3.1 -**Update**: Follow `facil.io`'s update for healthier thread throttling and energy consumption. +**Update**: Follow `facil.io`'s update for healthier thread throttling and energy consumption --- #### Change log v.0.3.1 @@ -792,7 +818,6 @@ Iodine.start * `bscrypt` random generator (where `dev/random` is unavailable) should now provide more entropy. - --- #### Change log v.0.2.9 @@ -1091,8 +1116,6 @@ Another example is that real-life deployment preferences were favored over adjus I tested this new gem during the 0.0.x version releases, and I feel that version 0.1.0 is stable enough to work with. For instance, I left the Iodine server running all night under stress (repeatedly benchmarking it)... millions of requests later, under heavy load, a restart wasn't required and memory consumption didn't show any increase after the warmup period. - - ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/Gemfile b/Gemfile index 73e52f4f..fc179577 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,8 @@ -source 'https://rubygems.org' +# frozen_string_literal: true -group :test do - gem 'pry' - gem 'rspec' - gem 'rack' - gem 'http' -end +source "https://rubygems.org" # Specify your gem's dependencies in iodine.gemspec gemspec + +gem "rake", "~> 13.0" diff --git a/LICENSE.txt b/LICENSE.txt index 846a7ccd..8cac1628 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2019 Boaz Segev +Copyright (c) 2015-2023 Boaz Segev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LIMITS.md b/LIMITS.md deleted file mode 100644 index 9c4dd535..00000000 --- a/LIMITS.md +++ /dev/null @@ -1,41 +0,0 @@ -# Iodine limits and settings - -I will, at some point, document these... here's the key points: - -## SSL/TLS - -* TLS support requires OpenSSL 1.1.0 and above. On Heroku, this requires `heroku-18`. - -* Iodine supports TLS 1.2 and above (depending on the OpenSSL version used). - -## HTTP limits - -* Uploads are adjustable and limited to ~50Mib by default. - -* HTTP connection timeout (keep-alive) is adjustable and set to ~60 seconds by default. - -* HTTP total header size is adjustable and limited to ~32Kib by default. - -* HTTP header line length size is limited to a hard-coded limit of 8Kb. - -* HTTP headers count is limited to a hard-coded limit of 128 headers. - -## Websocket - -* Incoming Websocket message size limits are adjustable and limited to ~250Kib by default. - -## EventSource / SSE - -* Iodine will automatically attempt to send a `ping` event instead of disconnecting the connection. The ping interval is the same as the HTTP connection timeout interval. - -## Pub/Sub - -* Channel names are binary safe and unlimited in length. However, name lengths effect performance. - - **Do NOT allow clients to dictate the channel name**, as they might use extremely long names and cause resource starvation. - -* Pub/sub is limited to the process cluster. To use pub/sub with an external service (such as Redis) an "Engine" is required (see YARD documentation). - -* Pub/sub pattern matching supports only the Redis pattern matching approach. This makes patterns significantly more expensive and exact matches simpler and faster. - - It's recommended to prefer exact channel/stream name matching when possible. diff --git a/README.md b/README.md index 8a088f15..346502f5 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,54 @@ -# iodine - Why Settle for a fast HTTP / WebSocket Server with native Pub/Sub? +# iodine - The One with the Event Stream and Native WebSockets -[![Gem](https://img.shields.io/gem/dt/iodine.svg)](https://rubygems.org/gems/iodine) -[![Build Status](https://github.com/boazsegev/iodine/actions/workflows/ruby.yml/badge.svg)](https://github.com/boazsegev/iodine/actions/workflows/ruby.yml) -[![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine) +[![POSIX CI](https://github.com/boazsegev/iodine/actions/workflows/posix.yml/badge.svg)](https://github.com/boazsegev/iodine/actions/workflows/posix.yml) +[![Windows CI](https://github.com/boazsegev/iodine/actions/workflows/Windows.yml/badge.svg)](https://github.com/boazsegev/iodine/actions/workflows/Windows.yml) [![Inline docs](http://inch-ci.org/github/boazsegev/iodine.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/iodine/master/frames) +[![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine) +[![Gem](https://img.shields.io/gem/dt/iodine.svg)](https://rubygems.org/gems/iodine) [![GitHub](https://img.shields.io/badge/GitHub-Open%20Source-blue.svg)](https://github.com/boazsegev/iodine) [![Logo](https://github.com/boazsegev/iodine/raw/master/logo.png)](https://github.com/boazsegev/iodine) -Iodine is a fast concurrent web application server for real-time Ruby applications, with native support for WebSockets and Pub/Sub services - but it's also so much more. +## The Ruby Server You Wanted -Iodine is a Ruby wrapper for many of the [facil.io](https://facil.io) C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, free to concentrate on your application logic. +Iodine is a fast concurrent Web Application Server for your Real-Time and Event-Stream needs, with native support for WebSockets and Pub/Sub services - but it's also so much more. Iodine includes native support for: -* HTTP, WebSockets and EventSource (SSE) Services (server); -* WebSocket connections (server / client); -* Pub/Sub (with optional Redis Pub/Sub scaling); -* Fast(!) builtin Mustache template engine. -* Static file service (with automatic `gzip` support for pre-compressed assets); -* Optimized Logging to `stderr`. -* Asynchronous event scheduling and timers; -* HTTP/1.1 keep-alive and pipelining; -* Heap Fragmentation Protection. -* TLS 1.2 and above (Requires OpenSSL >= 1.1.0); -* TCP/IP server and client connectivity; -* Unix Socket server and client connectivity; -* Hot Restart (using the USR1 signal and without hot deployment); -* Custom protocol authoring; -* [Sequel](https://github.com/jeremyevans/sequel) and ActiveRecord forking protection. +* HTTP, WebSockets and EventSource (SSE) Services (server/client); +* Event-Stream Pub/Sub (with optional Redis Pub/Sub scaling); +* Fast(!) builtin Mustache template render engine; +* Static File Service (with automatic `.gz`, `.br` and `.zip` support for pre-compressed assets); +* Performant Request Logging to `stderr`; +* Asynchronous Tasks and Timers (memory cached); +* HTTP/1.1 keep-alive and pipeline throttling; +* Separate Memory Allocators for Heap Fragmentation Protection; +* TLS 1.2 and above (Requiring OpenSSL >= 3); * and more! -Since iodine wraps much of the [C facil.io framework](https://github.com/boazsegev/facil.io) to Ruby: +Iodine is a Ruby wrapper for much of the [facil.io](https://facil.io) C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, leaving you free to concentrate on your application logic. + +Since iodine wraps much of the [C facil.io framework](https://github.com/boazsegev/facil.io) for Ruby: -* Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections on Linux)! +* Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections on Linux)! Limits depend on machine resources rather than software. -* Iodine is ideal for **Linux/Unix** based systems (i.e. macOS, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO *completion* events, which are very different). +* Iodine is ideal for **Linux/Unix** based systems (i.e. macOS, Ubuntu, FreeBSD etc') and evented IO (while Windows and Solaris are better at IO *completion* events, which are very different). -Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.3 and up... it should support the whole Ruby 2.x and 3.x MRI family, but CI tests start at Ruby 2.3. +Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.3 and up... it should support the whole Ruby 2.x and 3.x MRI family, but CI tests are often limited to stable releases that are still maintained. -**Note**: iodine does **not** support streaming when using Rack. It's recommended to avoid blocking the server when using `body.each` since the `each` loop will block the iodine's thread until it's finished and iodine won't send any data before the loop is done. +**Note**: -## Iodine - a fast & powerful HTTP + WebSockets server with native Pub/Sub +Streaming a Ruby response is often a bad performance choice no matter the server you use. [NeoRack](https://github.com/boazsegev/neorack) attempts to offer a better approach for streaming, but at the end of the day, it would be better to avoid streaming when possible. If you plan to Stream anyway, consider using [NeoRack](https://github.com/boazsegev/neorack) with an evented approach rather than blocking the thread (which is what happens when `each` is called in Ruby). -Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [Websocket draft extension](./SPEC-Websocket-Draft.md). +## Security -With `Iodine.listen service: :http` it's possible to run multiple HTTP applications (please remember not to set more than a single application on a single TCP/IP port). +Iodine was built with security in mind, making sure that clients will not have the possibility to abuse the server's resources. This includes limiting header line length, body payload sizes, WebSocket message length etc', as well as diverting larger HTTP payloads to temporary files. + +please review the `iodine -h` command line options for more details on these and see if you need to change the defaults to fit better with your specific restrictions and use-cases. + +## Iodine - a fast & powerful HTTP + WebSockets server/client with native Pub/Sub + +Iodine includes a light and fast HTTP and Websocket server written in C that was written to support both the [NeoRack](https://github.com/boazsegev/neorack) and [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [WebSocket](https://github.com/boazsegev/neorack/blob/master/extensions/websockets.md) and [SSE]](https://github.com/boazsegev/neorack/blob/master/extensions/sse.md) extension draft. Iodine also supports native process cluster Pub/Sub and a native RedisEngine to easily scale iodine's Pub/Sub horizontally. @@ -64,7 +67,7 @@ gem install iodine Using the iodine server is easy, simply add iodine as a gem to your Rails / Sinatra / Rack application's `Gemfile`: ```ruby -gem 'iodine', '~>0.7' +gem 'iodine', '~>0.8' ``` Then start your application from the command-line / terminal using iodine: @@ -73,104 +76,85 @@ Then start your application from the command-line / terminal using iodine: bundler exec iodine ``` -#### Installing with SSL/TLS +#### Installing with TLS/SSL + +Iodine should automatically detect when Ruby was installed with OpenSSL and link against that same library. + +Iodine will always respect encryption requirements, even if no library is available. -**Note**: iodine has known issues with the TLS/SSL support. TLS/SSL should **NOT** be used in production (see issues #95 and #94). +Requiring Iodine to use TLS when unavailable will result in Iodine crashing with an error message. -Make sure to update OpenSSL to the latest version **before installing Ruby** (`rbenv` should do this automatically). -To avoid name resolution conflicts, iodine will bind to the same OpenSSL version Ruby is bound to. To use SSL/TLS this should be OpenSSL >= 1.1.0 or LibreSSL >= 2.7.4. +### Optimizing Iodine's Concurrency + +To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires. + +Iodine will calculate, when possible, a good enough default concurrency model for fast applications. See if this works for your application or customize according to the application's needs. -Verbose installation should provide a confirmation message, such as: +Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes: ```bash -$ gem install iodine -f -V -... -checking for -lcrypto... yes -checking for -lssl... yes -Detected OpenSSL library, testing for version. -Confirmed OpenSSL to be version 1.1.0 or above (OpenSSL 1.1.0j 20 Nov 2018)... -* Compiling with HAVE_OPENSSL. -... +bundler exec iodine -t 16 -w 4 ``` -The installation script tests for OpenSSL 1.1.0 and above. However, this testing approach sometimes provides false positives. **If TLS isn't required, install with `NO_SSL=1`**. i.e.: +The environment variables `THREADS` and `WORKERS` are automatically recognized when iodine is first required, allowing environment specific customization. i.e.: ```bash -NO_SSL=1 bundler exec iodine +export THREADS=4 +export WORKERS=-2 # negative values are fractions of CPU cores. +bundler exec iodine +``` + +Negative values are evaluated as "CPU Cores / abs(Value)". i.e., on an 8 core CPU machine, this will produce 4 worker processes with 2 threads per worker: + +```bash +bundler exec iodine -t 2 -w -2 ``` ### Running with Rails On Rails: -1. Replace the `puma` gem with the `iodine` gem. +1. Add `gem "iodine", "~> 0.8"` to your `Gemfile` (and comment out the `puma` gem). -1. Remove the `config/puma.rb` file (or comment out the code). +1. Remove the `config/puma.rb` file (or make sure the code is conditional). 1. Optionally, it's possible to add a `config/initializers/iodine.rb` file. For example: ```ruby - # Iodine setup - use conditional setup to allow command-line arguments to override these: + # Iodine setup - use conditional setup to make it easy to test other servers such as Puma: if(defined?(Iodine)) - Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if Iodine.threads.zero? - Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if Iodine.workers.zero? - Iodine::DEFAULT_SETTINGS[:port] ||= ENV.fetch("PORT") if ENV.fetch("PORT") + Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if ENV["RAILS_MAX_THREADS"] + Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if ENV["WEB_CONCURRENCY"] end ``` -When using native WebSockets with Rails, middle-ware is probably the best approach. A guide for this approach will, hopefully, get published in the future. - -**Note**: command-line instructions (CLI) should be the preferred way for configuring iodine, allowing for code-less configuration updates. - -### Optimizing Iodine's Concurrency - -To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires. - -Iodine will calculate, when possible, a good enough default concurrency model for fast applications. See if this works for your application or customize according to the application's needs. +**Note**: command-line instructions (CLI) and environment variables are the recommended way for configuring iodine, allowing for code-less configuration updates. -Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes: - -```bash -bundler exec iodine -p $PORT -t 16 -w 4 -``` - -The environment variables `THREADS` and `WORKERS` are automatically recognized when iodine is first required, allowing environment specific customization. i.e.: - -```bash -export THREADS=16 -export WORKERS=-1 # negative values are fractions of CPU cores. -bundler exec iodine -p $PORT -``` +### Logging -Negative values are evaluated as "CPU Cores / abs(Value)". i.e., on an 8 core CPU machine, this will produce 4 worker processes with 2 threads per worker: +To enable performant logging from the command line, use the `-v` (verbose) option: ```bash -bundler exec iodine -p $PORT -t 2 -w -2 +bundler exec iodine -p $PORT -t 16 -w -2 -www /my/public/folder -v ``` -### Heap Fragmentation Protection - -Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC). - -This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space. - -It is still recommended to consider [jemalloc](http://jemalloc.net) or other allocators that also help mitigate heap fragmentation issues. +Iodine will cache the date and time String data when answering multiple requests during the same time frame, improving performance. -### Static file serving support +### Static Files and Assets -Iodine supports an internal static file service that bypasses the Ruby layer and serves static files directly from "C-land". +Iodine can send static file and assets directly, bypassing the Ruby layer completely. -This means that iodine won't lock Ruby's GVL when sending static files. The files will be sent directly, allowing for true native concurrency. +This means that Iodine won't lock Ruby's GVL when serving static files. -Since the Ruby layer is unaware of these requests, logging can be performed by turning iodine's logger on. +Since the Ruby layer is unaware of these requests, logging can be performed by turning iodine's logger on (see above). To use native static file service, setup the public folder's address **before** starting the server. This can be done when starting the server from the command line: ```bash -bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder +bundler exec iodine -t 16 -w 4 -www /my/public/folder ``` Or using a simple Ruby script. i.e. (a `my_server.rb` example): @@ -179,55 +163,21 @@ Or using a simple Ruby script. i.e. (a `my_server.rb` example): require 'iodine' # static file service Iodine.listen, service: :http, public: '/my/public/folder' -# for static file service, we only need a single thread and a single worker. -Iodine.threads = 1 +# for static file service, we need no worker processes nor worker threads. +# However, it is good practice to use at least one worker for hot restarts +Iodine.threads = 0 +Iodine.workers = 1 Iodine.start ``` -To enable logging from the command line, use the `-v` (verbose) option: - -```bash -bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder -v -``` - -#### X-Sendfile - -When a public folder is assigned (the static file server is active), iodine automatically adds support for the `X-Sendfile` header in any Ruby application response. - -This allows Ruby to send very large files using a very small memory footprint and usually leverages the `sendfile` system call. - -i.e. (example `config.ru` for iodine): - -```ruby -app = proc do |env| - request = Rack::Request.new(env) - if request.path_info == '/source'.freeze - [200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []] - elsif request.path_info == '/file'.freeze - [200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)] - else - [200, { 'Content-Type' => 'text/html', - 'Content-Length' => request.path_info.length.to_s }, - [request.path_info]] - end -end -# # optional: -# use Rack::Sendfile -run app -``` - -Benchmark [localhost:3000/source](http://localhost:3000/source) to experience the `X-Sendfile` extension at work. - #### Pre-Compressed assets / files -Rails does this automatically when compiling assets, which is: `gzip` your static files. - -Iodine will automatically recognize and send the `gz` version if the client (browser) supports the `gzip` transfer-encoding. +Iodine will automatically recognize and send the compressed version of a static file (`.gz`, `.br`, `.zip`) if the client (browser) supports the compressed transfer-encoding. For example, to offer a compressed version of `style.css`, run (in the terminal): ```bash -$ gzip -k -9 style.css +gzip -k -9 style.css ``` This results in both files, `style.css` (the original) and `style.css.gz` (the compressed). @@ -236,72 +186,104 @@ When a browser that supports compressed encoding (which is most browsers) reques It's as easy as that. No extra code required. -### Special HTTP `Upgrade` and SSE support +### Native HTTP `Upgrade` and SSE support -Iodine's HTTP server implements the [WebSocket/SSE Rack Specification Draft](SPEC-Websocket-Draft.md), supporting native WebSocket/SSE connections using Rack's `env` Hash. +Iodine's HTTP server has native support for [WebSocket](https://github.com/boazsegev/neorack/blob/master/extensions/websockets.md)/[SSE](https://github.com/boazsegev/neorack/blob/master/extensions/sse.md), using both [NeoRack](https://github.com/boazsegev/neorack) extensions and the Rack `env` response style. This promotes separation of concerns, where iodine handles all the Network related logic and the application can focus on the API and data it provides. -Upgrading an HTTP connection can be performed either using iodine's native WebSocket / EventSource (SSE) support with `env['rack.upgrade?']` or by implementing your own protocol directly over the TCP/IP layer - be it a WebSocket flavor or something completely different - using `env['upgrade.tcp']`. - -#### EventSource / SSE +Of course, Hijacking the socket is still possible (for now), but highly discouraged. -Iodine treats EventSource / SSE connections as if they were a half-duplex WebSocket connection, using the exact same API and callbacks as WebSockets. +#### Using Rack for WebSocket/SSE -When an EventSource / SSE request is received, iodine will set the Rack Hash's upgrade property to `:sse`, so that: `env['rack.upgrade?'] == :sse`. +With Rack, the `env['rack.upgrade?']` will be set to either `:websocket` or `:sse`, allowing the Rack application to either distinguish or unify the behavior desired. This is a simple broadcasting example: -The rest is detailed in the WebSocket support section. - -#### WebSockets +```ruby +module App + def self.call(env) + txt = [] + if env['rack.upgrade?'] + env['rack.upgrade'] = self + else + env.each {|k,v| txt << "#{k}: #{v}\r\n" } + end + [200, {}, txt] + end + def self.on_open(e) + e.subscribe :broadcast + end + def self.on_message(e, m) + Iodine.publish :broadcast, m + end +end +run App +``` -When a WebSocket connection request is received, iodine will set the Rack Hash's upgrade property to `:websocket`, so that: `env['rack.upgrade?'] == :websocket` +#### Using NeoRack for WebSocket/SSE -To "upgrade" the HTTP request to the WebSockets protocol (or SSE), simply provide iodine with a WebSocket Callback Object instance or class: `env['rack.upgrade'] = MyWebsocketClass` or `env['rack.upgrade'] = MyWebsocketClass.new(args)` +The [NeoRack WebSocket](https://github.com/boazsegev/neorack/blob/master/extensions/websockets.md) and [SSE](https://github.com/boazsegev/neorack/blob/master/extensions/sse.md) specification drafts offers much more control over the path taken by a WebSocket request. -Iodine will adopt the object, providing it with network functionality (methods such as `write`, `defer` and `close` will become available) and invoke it's callbacks on network events. +This control is available also to Rack Applications if they implement the proper callback methods(in addition to the `call` method). -Here is a simple chat-room example we can run in the terminal (`irb`) or easily paste into a `config.ru` file: +i.e.: ```ruby -require 'iodine' -module WebsocketChat - def on_open client - # Pub/Sub directly to the client (or use a block to process the messages) - client.subscribe :chat - # Writing directly to the socket - client.write "You're now in the chatroom." +module MyNeoRackApp + # this is fairly similar to the Rack example above. + def self.on_http(e) + out = "path: #{e.path}\r\nquery: #{e.query}\r\n" + out += "method: #{e.method}\r\nversion: #{e.version}\r\n" + out += "from: #{e.from} (#{e.peer_addr})\r\n" + e.headers.each {|k,v| out += "#{k}: #{v}\r\n" } + # echo request body to the response + while(l = e.gets) + out += l + end + # write the data and finish (this should avoid streaming overhead). + e.finish out end - def on_message client, data - # Strings and symbol channel names are equivalent. - client.publish "chat", data + + # basically the default implementation when either `on_open`/`on_message` are defined. + def self.on_authenticate(e) + true end - extend self -end -APP = Proc.new do |env| - if env['rack.upgrade?'.freeze] == :websocket - env['rack.upgrade'.freeze] = WebsocketChat - [0,{}, []] # It's possible to set cookies for the response. - elsif env['rack.upgrade?'.freeze] == :sse - puts "SSE connections can only receive data from the server, the can't write." - env['rack.upgrade'.freeze] = WebsocketChat - [0,{}, []] # It's possible to set cookies for the response. - else - [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Welcome Home"] ] + # called when either an SSE or a WebSocket connection is open. + def self.on_open(e) + e.subscribe :broadcast + end + # Called an Event Source (SSE) client send a re-connection request with the ID of the last message received. + def self.on_eventsource_reconnect(sse, last_message_id) + puts "Reconnecting SSE client #{sse.object_id}, should send everything after message ID #{last_message_id}" + end + # Only WebSocket connection should be able to receive messages from clients. + def self.on_message(e, m) + Iodine.publish :broadcast, m + end + # Iodine allows clients to send properly formatted events, should you want to cheat a little. + # This is usually more useful when using Iodine as an SSE client. + def self.on_eventsource(sse, message) + puts "Normally only an SSE client would receive sse messages... See Iodine::Connection.new\r\n" + puts "id: #{message.id}" + puts "event: #{message.event}" + puts "data: #{message.data}" end end -# Pus/Sub can be server oriented as well as connection bound -Iodine.subscribe(:chat) {|ch, msg| puts msg if Iodine.master? } -# By default, Pub/Sub performs in process cluster mode. -Iodine.workers = 4 -# # in irb: -Iodine.listen service: :http, public: "www/public", handler: APP -Iodine.start -# # or in config.ru -run APP + +run MyNeoRackApp ``` +#### Iodine as a WebSocket/SSE Client + +Iodine can also attempt to connect to an external server as a WebSocket or SSE client. + +Simply call `Iodine::Connection.new(url, handler: MyClient)` using the same callback methods defined in the NeoRack [WebSocket](https://github.com/boazsegev/neorack/blob/master/extensions/websockets.md) and [SSE](https://github.com/boazsegev/neorack/blob/master/extensions/sse.md) specifications. + +There's an example `client` CLI application in the examples folder. + ### Native Pub/Sub with *optional* Redis scaling +**Note**: not yet implemented in versions `0.8.x`, but the documentation hadn't been removed with hopes of a soon-to-be implementation. + Iodine's core, `facil.io` offers a native Pub/Sub implementation that can be scaled across machine boundaries using Redis. The default implementation covers the whole process cluster, so a single cluster doesn't need Redis @@ -309,7 +291,7 @@ The default implementation covers the whole process cluster, so a single cluster Once a single iodine process cluster isn't enough, horizontal scaling for the Pub/Sub layer is as simple as connecting iodine to Redis using the `-r ` from the command line. i.e.: ```bash -$ iodine -w -1 -t 8 -r redis://localhost +iodine -w -1 -t 8 -r redis://localhost ``` It's also possible to initialize the iodine<=>Redis link using Ruby, directly from the application's code: @@ -333,7 +315,23 @@ if(Iodine::PubSub.default.is_a? Iodine::PubSub::Redis) end ``` -**Pub/Sub Details and Limitations:** +#### Pub/Sub Details and Limitations + +Iodine's internal Pub/Sub Letter Exchange Protocol (inherited from facil.io) imposes the following limitations on message exchange: + +* Distribution Channel Names are limited to 2^16 bytes (65,536 bytes). + +* Message payload is limited to 2^24 bytes (16,777,216 bytes == about 16Mb). + +* Empty messages (no numerical filters, no channel, no message payload, no flags) are ignored. + +* Subscriptions match delivery matches by both channel name (or pattern) and the numerical filter. + +Redis Support Limitations: + +* Redis support **has yet to be implemented** in the Iodine `0.8.x` versions. + + Note: It is possible that it won't make it into the final release, as Redis is less OpenSource than it was and I just don't have the desire to code for something I stopped using... but I would welcome a PR implementing this in the [facil.io C STL repo](https://github.com/facil-io/cstl). * Iodine's Redis client does *not* support multiple databases. This is both because [database scoping is ignored by Redis during pub/sub](https://redis.io/topics/pubsub#database-amp-scoping) and because [Redis Cluster doesn't support multiple databases](https://redis.io/topics/cluster-spec). This indicated that multiple database support just isn't worth the extra effort and performance hit. @@ -347,6 +345,8 @@ Iodine will "hot-restart" the application by shutting down and re-spawning the w This will clear away any memory fragmentation concerns and other issues that might plague a long running worker process or ruby application. +This could be used for hot-reloading or hot-swapping of the Web Application code itself, but only if the code is lazily in worker processes (never loaded by the root process). + To hot-restart iodine, send the `SIGUSR1` signal to the root process. The following code will hot-restart iodine every 4 hours when iodine is running in cluster mode: @@ -359,41 +359,7 @@ end Since the master / root process doesn't handle any requests (it only handles pub/sub and house-keeping), it's memory map and process data shouldn't be as affected and the new worker processes should be healthier and more performant. -**Note**: This will **not** re-load the application (any changes to the Ruby code require an actual restart). - -### Optimized HTTP logging - -By default, iodine is pretty quiet. Some messages are logged to `stderr`, but not many. - -However, HTTP requests can be logged using iodine's optimized logger to `stderr`. Iodine will optimize the log output by caching the output time string which updates every second rather than every request. - -This can be performed by setting the `-v` flag during startup, i.e.: - -```bash -bundler exec iodine -p $PORT -t 16 -w 4 -v -www /my/public/folder -``` - -The log output can be redirected to a file: - -```bash -bundler exec iodine -p $PORT -v 2>my_log.log -``` - -The log output can also be redirected to a `stdout`: - -```bash -bundler exec iodine -p $PORT -v 2>&1 -``` - -### Built-in support for Sequel and ActiveRecord - -It's a well known fact that [Database connections require special attention when using `fork`-ing servers (multi-process servers)](https://devcenter.heroku.com/articles/concurrency-and-database-connections#multi-process-servers) such as Puma, Passenger (Pro) and iodine. - -However, it's also true that [these issues go unnoticed by many developers](https://stackoverflow.com/a/45570999/4025095), since application developers are (rightfully) focused on the application rather than the infrastructure. - -With iodine, there's no need to worry. - -Iodine provides built-in `fork` handling for both ActiveRecord and [Sequel](https://github.com/jeremyevans/sequel), in order to protect against these possible errors. +**Note**: This will **not** re-load the application (any changes to the Ruby code require an actual restart) unless the application code was never loaded in the root process. ### Client Support @@ -444,68 +410,28 @@ class EchoClient end Iodine.threads = 1 -Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40 -Iodine.start -``` - -### TLS >= 1.2 support - -> Requires OpenSSL >= `1.1.0`. On Heroku, requires `heroku-18`. - -Iodine supports secure connections fore TLS version 1.2 **and up** (depending on the OpenSSL version). - -A self signed certificate is available using the `-tls` flag from the command-line. - -PEM encoded certificates (which is probably the most common format) can be loaded from the command-line (`-tls-cert` and `-tls-key`) or dynamically (using `Iodine::TLS`). - -The TLS API is simplified but powerful, supporting the ALPN extension and peer verification (which client connections really should leverage). - -When enabling peer verification for server connections (using `Iodine::TLS#trust`), clients will be required to submit a trusted certificate in order to connect to the server. - -### TCP/IP (raw) sockets - -Upgrading to a custom protocol (i.e., in order to implement your own WebSocket protocol with special extensions) is available when neither WebSockets nor SSE connection upgrades were requested. In the following (terminal) example, we'll use an echo server without direct socket echo: - -```ruby -require 'iodine' -class MyProtocol - def on_message client, data - # regular socket echo - NOT websockets - client.write data - end -end -APP = Proc.new do |env| - if env["HTTP_UPGRADE".freeze] =~ /echo/i.freeze - env['upgrade.tcp'.freeze] = MyProtocol.new - # an HTTP response will be sent before changing protocols. - [101, { "Upgrade" => "echo" }, []] - else - [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Welcome Home"] ] - end -end -# # in irb: -Iodine.listen service: :http, public: "www/public", handler: APP -Iodine.threads = 1 +Iodine.workers = 0 +Iodine::Connection.new "wss://echo.websocket.org", handler: EchoClient.new, ping: 40 Iodine.start -# # or in config.ru -run APP ``` ### How does it compare to other servers? -In my tests, pitching Iodine against Puma, Iodine was anywhere between x1.5 and more than x10 faster than Puma (depending on use-case and settings). +Although Puma significantly improved since the first Iodine release, my tests show that Iodine is still significantly faster both in terms or latency and requests per second. + +In my tests I avoided using NeoRack, as it wouldn't be fair. NeoRack by itself adds a significant performance boost due its design. For example, NeoRack it minimizes conversions between data formats (i.e., we don't append `HTTP_` to header names). -Such a big difference is suspect and I recommend that you test it yourself - even better if you test performance using your own application and a number of possible different settings (how many threads per CPU core? how many worker processes? middleware vs. server request logging, etc'). +I am excited to have you test it for yourself - even better if you test performance using your own application and a number of possible different settings (how many threads per CPU core? how many worker processes? middleware vs. server request logging, etc'). -I recommend benchmarking the performance for yourself using `wrk` or `ab`: +I recommend benchmarking the performance for yourself using tools such as `wrk`, i.e.: ```bash $ wrk -c200 -d4 -t2 http://localhost:3000/ -# or -$ ab -n 100000 -c 200 -k http://127.0.0.1:3000/ ``` -The best application to use for benchmarking is your actual application. Or, you could create a simple `config.ru` file with a __hello world__ app: +The best application to use for benchmarking is your actual application. + +You could create a simple `config.ru` file with a **hello world** app, and even though this will really showcase the server's performance, it probably won't matter for your specific use-case: ```ruby App = Proc.new do |env| @@ -521,32 +447,20 @@ run App Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes, 4 threads): ```bash -$ RACK_ENV=production iodine -p 3000 -t 4 -w 4 +$ RACK_ENV=production iodine -p 3000 -t 4 -w 4 -v # vs. -$ RACK_ENV=production puma -p 3000 -t 4 -w 4 -# Review the `iodine -?` help for more command line options. +$ RACK_ENV=production puma -p 3000 -t 4 -w 4 -v +# Review the `iodine -h` help for more command line options. ``` -It's recommended that the servers (Iodine/Puma) and the client (`wrk`/`ab`) run on separate machines. - -It is worth noting that iodine can also speed up logging by replacing the logging middleware with `iodine -v`. This approach uses less memory and improves performance at the expense of fuzzy timing and some string caching. - -On my machine, testing with the logging functionality enabled, iodine was more then 10 times faster than puma (60.9K req/sec vs. 5.3K req/sec) - -### A few notes - -Iodine's upgrade / callback design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems), etc. This also allows us to use middleware without interfering with connection upgrades and provides backwards compatibility. - -Iodine's HTTP server imposes a few restrictions for performance and security reasons, such as limiting each header line to 8Kb. These restrictions shouldn't be an issue and are similar to limitations imposed by Apache or Nginx. - -If you still want to use Rack's `hijack` API, iodine will support you - but be aware that you will need to implement your own reactor and thread pool for any sockets you hijack, as well as a socket buffer for non-blocking `write` operations (why do that when you can write a protocol object and have the main reactor manage the socket?). +It's recommended that the servers (Iodine/Puma) and the client (i.e. `wrk`) run on separate machines. ## Installation To install iodine, simply install the the `iodine` gem: ```bash -$ gem install iodine +gem install iodine ``` Iodine is written in C and allows some compile-time customizations, such as: @@ -556,7 +470,7 @@ Iodine is written in C and allows some compile-time customizations, such as: * `FIO_MAX_SOCK_CAPACITY` - limits iodine's maximum client capacity. Defaults to 131,072 clients. * `FIO_USE_RISKY_HASH` - replaces SipHash with RiskyHash for iodine's internal hash maps. - + Since iodine hash maps have internal protection against collisions and hash flooding attacks, it's possible for iodine to leverage RiskyHash, which is faster than SipHash. By default, SipHash will be used. This is a community related choice, since the community seems to believe a hash function should protect the hash map rather than it being enough for a hash map implementation to be attack resistance. @@ -611,11 +525,10 @@ end In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it could work well for lighter loads. -The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything... +The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything... ...but single threaded mode should probably be avoided. - It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's short tasks on the same thread. The thread pool is there to help slow user code. @@ -633,7 +546,7 @@ Iodine is **free** and **open source**, so why not take it out for a spin? It's installable just like any other gem on Ruby MRI, run: ``` -$ gem install iodine +gem install iodine ``` If building the native C extension fails, please note that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (`ruby.h` and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get them. @@ -642,7 +555,7 @@ If you have the development headers but still can't compile the iodine extension ## Mr. Sandman, write me a server -Iodine allows custom TCP/IP server authoring, for those cases where we need raw TCP/IP (UDP isn't supported just yet). +Iodine allows custom TCP/IP server authoring, for those cases where we need raw TCP/IP (UDP isn't supported just yet). Here's a short and sweet echo server - No HTTP, just use `telnet`: @@ -747,13 +660,14 @@ Iodine.workers = 1 Iodine.start ``` -### Why not EventMachine? +### Heap Fragmentation Protection -EventMachine attempts to give the developer access to the network layer while Iodine attempts to abstract the network layer away and offer the developer a distraction free platform. +Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC). + +This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space. -You can go ahead and use EventMachine if you like. They're doing amazing work on that one and it's been used a lot in Ruby-land... really, tons of good developers and people on that project. +It is still recommended to consider [jemalloc](http://jemalloc.net) or other allocators that also help mitigate heap fragmentation issues. -But why not take iodine out for a spin and see for yourself? ## Can I contribute? @@ -763,7 +677,7 @@ Yes, please, here are some thoughts: * PRs or issues related to [the `facil.io` C framework](https://github.com/boazsegev/facil.io) should be placed in [the `facil.io` repository](https://github.com/boazsegev/facil.io). -* Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine. +* Bug reports and pull requests are welcome on GitHub at . * If we can write a Java wrapper for [the `facil.io` C framework](https://github.com/boazsegev/facil.io), it would be nice... but it could be as big a project as the whole gem, as a lot of minor details are implemented within the bridge between these two languages. diff --git a/Rakefile b/Rakefile index 3d503c03..8afcddcf 100644 --- a/Rakefile +++ b/Rakefile @@ -1,23 +1,10 @@ -require "bundler/gem_tasks" -require "rake/extensiontask" - -begin - require 'rspec/core/rake_task' - RSpec::Core::RakeTask.new(:rspec) -rescue LoadError -end +# frozen_string_literal: true -task :spec => [:compile, :rspec] +require "bundler/gem_tasks" +task default: %i[] -task :default => [:compile, :spec] +require "rake/extensiontask" Rake::ExtensionTask.new "iodine" do |ext| ext.lib_dir = "lib/iodine" end - -# Rake::ExtensionTask.new "iodine_http" do |ext| -# ext.name = 'iodine_http' -# ext.lib_dir = "lib/iodine" -# ext.ext_dir = 'ext/iodine' -# ext.config_script = 'extconf-http.rb' -# end diff --git a/SPEC-PubSub-Draft.md b/SPEC-PubSub-Draft.md deleted file mode 100644 index ce36ae66..00000000 --- a/SPEC-PubSub-Draft.md +++ /dev/null @@ -1,159 +0,0 @@ -# Ruby pub/sub API Specification Draft - -### Draft State - -This draft is under discussion and will be implemented by iodine starting with the 0.8.x versions. - ---- - -## Purpose - -This document details a Rack specification extension for publish/subscribe (pub/sub) modules that can extend WebSocket / EventSource Rack servers. - -The purpose of this specification is: - -1. To keep separation of concerns by avoiding inter-process-communication logic (IPC) in the application code base. - - This is important since IPC is often implemented using pipes / sockets, which could introduce network and IO concerns into the application code. - -2. To specify a common publish/subscribe (pub/sub) API for servers and pub/sub modules, allowing applications to easily switch between conforming implementations and servers. - - Since pub/sub design is idiomatic to WebSocket and EventSource approaches, as well as other reactive programming techniques, it is better if applications aren't locked in to a specific server / implementation. - -3. To provide common considerations and guidelines for pub/sub implementors to consider when implementing their pub/sub modules. - - Some concerns are common for pub/sub implementors, such as integrating third party message brokers (Redis, RabbitMQ, Cassandra) - -## Pub/Sub Instance Methods - -Conforming pub/sub implementations **MUST** implement the following pub/sub instance methods: - -* `pubsub?` **MUST** return the pub/sub API version as an integer number. Currently, set to `0` (development version). - -* `subscribe(to, is_pattern = false) { |from, message| optional_block }` where: - - * `to` is a named **channel** (or **pattern**). - - The implementation **MAY** support pattern matching for named channels (`to`). The pattern matching algorithm used, if any, **SHOULD** be documented. - - If the implementation does **NOT** support pattern matching and `is_pattern` is truthful, the implementation **MUST** raise and exception. - - `to` **SHOULD** be a String, but implementations **MAY** support Symbols as aliases to Strings (in which case `:my_channel` is the same `'my_channel'`). - - * `block` is optional and accepts (if provided) two arguments (`from` which is equal to `to` and `message` which contains the data's content). - - `block` (if provided) **MUST** be called when a publication was received by the named channel. - - If no `block` is provided: - - * If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft) data should be directly sent to the `client`. - - * If the pub/sub isn't linked with a `client` / connection, an exception **MUST** be raised. - - If a subscription to `to` already exists for the same pub/sub instance, it should be *replaced* by the new subscription (the old subscription should be canceled / unsubscribed). - - If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft), the subscription **MUST** be closed automatically when the connection is closed (when `on_close` is called). - - Otherwise, the subscription **MUST** be closed automatically when the pub/sub object goes out of scope and is garbage collected (if this ever happens). - - The `subscribe` method **MUST** return `nil` on a known failure (i.e., when the connection is already closed), or any truthful value on success. - -* `unsubscribe(from, is_pattern = false)` should cancel a subscription to the `from` named channel / pattern. - -* `publish(to, message, engine = nil)` where: - - * `to` is a named channel, same as detailed in `subscribe`. - - Implementations **MAY** support pattern based publishing. This **SHOULD** be documented as well as how patterns are detected (as opposed to named channels). Note that pattern publishing isn't supported by common backends (such as Redis) and introduces complex privacy and security concerns. - - * `message` a String containing the data to be published. - - If `message` is NOT a String, the implementation **MAY** convert the data silently to JSON. Otherwise, the implementation **MUST** raise an exception. - - `message` encoding (binary / UTZ-8) **MAY** be altered during publication, but any change **MUST** result in valid encoding. - - * `engine` routes the publish method to the specified pub/sub Engine (see later on). If none is specified, the default engine should be used. If `false` is specified, the message **MUST** be forwarded to all subscribed clients on the **same process**. - - The `publish` method **MUST** return `true` if a publication was scheduled (not necessarily performed). If it's already known that the publication would fail, the method should return `false`. - - An implementation **MUST** call the relevant PubSubEngine's `publish` method after performing any internal book keeping logic. If `engine` is `nil`, the default PubSubEngine should be called. If `engine` is `false`, the implementation **MUST** forward the published message to the actual clients (if any). - - A global alias for this method (allowing it to be accessed from outside active connections) **MAY** be defined as `Rack::PubSub.publish`. - -## Integrating a Pub/Sub module into a WebSocket / EventSource (SSE) `client` object - -A conforming pub/sub module **MUST** be designed so that it can be integrated into WebSocket / EventSource (SSE) `client` objects be `include`-ing their class. - -This **MUST** result in a behavior where subscriptions are destroyed / canceled once the `client` object state changes to "closed" - i.e., either when the `on_close` callback is called, or the first time the method `client.open?` returns `false`. - -The idiomatic way to add a pub/sub module to a `client`'s class is: - -```ruby -client.class.prepend MyPubSubModule unless client.pubsub? -``` - -The pub/sub module **MUST** expect and support this behavior. - -**Note**: the use of `prepend` (rather than `include`) is chosen so that it's possible to override the `client`'s instance method of `pubsub?`. - -## Connecting to External Backends (pub/sub Engines) - -It is common for scaling applications to require an external message broker backend such as Redis, RabbitMQ, etc'. The following requirements set a common interface for such "engine" implementation and integration. - -Pub/sub implementations **MUST** implement the following module / class methods in one of their public classes / modules (iodine implements these under `Iodine::PubSub`): - -* `attach(engine)` where `engine` is a `PubSubEngine` object, as described in this specification. - - When a pub/sub engine is attached, the implementation **MUST** inform the engine of any existing or future subscriptions. - - The implementation **MUST** call the engine's `subscribe` callback for each existing (and future) subscription. - - The implementation **MUST** allow multiple "engines" to be attached when multiple calls to `attach` are made. - -* `detach(engine)` where `engine` is a PubSubEngine object as described in this specification. - - The implementation **MUST** remove the engine from the attached engine list. The opposite of `attach`. - -* `default=(engine)` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification. - - Implementations **MUST** forward any `publish` method calls to the default pub/sub engine, unless an `engine` is specified in arguments passes to the `publish` method. - -* `default` returns the current default pub/sub engine, where the engine is a PubSubEngine object as described in this specification. - -* `reset(engine)` where `engine` is a PubSubEngine object as described in this specification. - - Implementations **MUST** behave as if the engine was newly registered and (re)inform the engine of any existing subscriptions by calling engine's `subscribe` callback for each existing subscription. - -A `PubSubEngine` instance object **MUST** implement the following methods: - -* `subscribe(to, is_pattern = false)` this method informs the engine that a subscription to the specified channel / pattern exists for the calling the process. It **MUST ONLY** be called **once** for all existing and future subscriptions to that channel within the process. - - The method **MUST** return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail. - - This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client. - - This method **MUST NOT** raise an exception. - -* `unsubscribe(from, is_pattern = false)` this method informs an engine that there are no more subscriptions to the named channel / pattern for the calling process. - - The method's semantics are similar to `subscribe` only is performs the opposite action. - - This method **MUST NOT** raise an exception. - - This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client. - -* `publish(channel, message)` where both `channel` is either a Symbol or a String (both being equivalent) and `message` **MUST** be a String. - - This method will be called by the server when a message is published using the engine. - - This method will be called by the pub/sub implementation (for each registered engine). - - The engine **MUST** assume that the method **MAY** be called directly by an application / client. - -In order for a PubSubEngine instance object to publish messages to all subscribed clients on a particular process, it **SHOULD** call the implementation's global `publish` method with the engine set to `false`. - -i.e., if the implementation's global `publish` method is in a class called `Iodine`: - -```ruby -Iodine.publish channel, message, false -``` diff --git a/SPEC-WebSocket-Draft.md b/SPEC-WebSocket-Draft.md deleted file mode 100644 index bca9a35e..00000000 --- a/SPEC-WebSocket-Draft.md +++ /dev/null @@ -1,239 +0,0 @@ -### Draft State - -This draft is also implemented by [the Agoo server](https://github.com/ohler55/agoo) according to the specifications stated in [Rack PR#1272](https://github.com/rack/rack/pull/1272). - ---- -## Purpose - -This document details a Rack specification extension for WebSocket / EventSource servers. - -The purpose of this specification is: - -1. To improve application safety by phasing out the use of `hijack` and replacing it with the use of application object callbacks. - - This should make it easer for applications to accept WebSocket and EventSource (SSE) connections without exposing themselves to risks and errors related to IO / network logic (such as slow client attacks, buffer flooding, etc'). - -2. To improve separation of concerns between servers and applications, moving the IO / network logic related to WebSocket and EventSource (SSE) back to the server. - - Simply put, when using a server that support this extension, the application / framework doesn’t need to have any knowledge about networking, transport protocols, IO streams, polling, etc'. - -## Rack WebSockets / EventSource - -Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value **MUST** follow the requirements set in this document. - -This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`. - -A conforming server **MUST** set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections. - -If a connection is not "upgradeable", a conforming server **SHOULD** set `env['rack.upgrade?']` to either `nil` or `false`. Setting the `env['rack.upgrade?']` to either `false` or `nil` should make it easier for applications to test for server support during a normal HTTP request. - -If the connection is upgradeable and a client application set a value for `env['rack.upgrade']`: - -* the server **MUST** use that value as a WebSocket / EventSource Callback Object unless the response status code `>= 300` (redirection / error status code). - -* The response `body` **MUST NOT** be sent when switching to a Callback Object. - -If a connection is **NOT** upgradeable and a client application set a value for `env['rack.upgrade']`: - -* The server **SHOULD** ignore the Callback Object and process the response as if it did not exist. - -* A server **MAY** use the Callback Object to allow a client to "hijack" the data stream as raw data stream. Such behavior **MUST** be documented. - -### The WebSocket / EventSource Callback Object - -WebSocket and EventSource connection upgrade and handling is performed using a Callback Object. - -The Callback Object could be a any object which implements any (of none) of the following callbacks: - -* `on_open(client)` **MUST** be called once the connection had been established and/or the Callback Object had been linked to the `client` object. - -* `on_message(client, data)` **MUST** be called when incoming WebSocket data is received. - - This callback is ignored for EventSource connections. - - `data` **MUST** be a String with an encoding of UTF-8 for text messages and `binary` encoding for non-text messages (as specified by the WebSocket Protocol). - - The *callback object* **MUST** assume that the `data` String will be a **recyclable buffer** and that it's content will be corrupted the moment the `on_message` callback returns. - - Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional, is *not* required and might result in issues in cases where the client code is less than pristine. - -* `on_drained(client)` **MAY** be called when the `client.write` buffer becomes empty. **If** `client.pending` ever returns a non-zero value (see later on), the `on_drained` callback **MUST** be called once the write buffer becomes empty. - -* `on_shutdown(client)` **MAY** be called during the server's graceful shutdown process, _before_ the connection is closed and in addition to the `on_close` function (which is called _after_ the connection is closed. - -* `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc') or the Callback Object was replaced by another Callback Object. - - -The server **MUST** provide the Callback Object with a `client` object, that supports the following methods (this approach promises applications could be server agnostic): - -* `env` **MUST** return the Rack `env` hash related to the originating HTTP request. Some changes to the `env` hash (such as removal of the IO hijacking support) **MAY** be implemented by the server. - -* `write(data)` **MUST** schedule **all** the data to be sent. `data` **MUST** be a String. Servers **MAY** silently convert non-String objects to JSON if an application attempts to `write` a non-String value, otherwise servers **SHOULD** throw an exception. - - A call to `write` only promises that the data is scheduled to be sent. Implementation details may differ across servers. - - `write` shall return `true` on success and `false` if the connection is closed. - - For WebSocket connections only (irrelevant for EventSource connections): - - * If `data` is UTF-8 encoded, the data will be sent as text. - - * If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol). - - A server **SHOULD** document its concurrency model, allowing developers to know whether `write` will block or not, whether buffered IO is implemented, etc'. - - For example, evented servers are encouraged to avoid blocking and return immediately, deferring the actual `write` operation for later. However, (process/thread/fiber) per-connection based servers **MAY** choose to return only after all the data was sent. Documenting these differences will allows applications to choose the model that best fits their needs and environments. - -* `close` closes the connection once all the data scheduled using `write` was sent. If `close` is called while there is still data to be sent, `close` **SHOULD** return immediately and only take effect once the data was sent. - - `close` shall always return `nil`. - -* `open?` **MUST** return `false` **if** the connection was never open, is known to be closed or marked to be closed. Otherwise `true` **MUST** be returned. - -* `pending` **MUST** return -1 if the connection is closed. Otherwise, `pending` **SHOULD** return the number of pending writes (messages in the `write` queue\*) that need to be processed before the next time the `on_drained` callback is called. - - Servers **MAY** choose to always return the value `0` **ONLY IF** they never call the `on_drained` callback and the connection is open. - - Servers that return a positive number **MUST** call the `on_drained` callback when a call to `pending` would return the value `0`. - - \*Servers that divide large messages into a number of smaller messages (implement message fragmentation) **MAY** count each fragment separately, as if the fragmentation was performed by the user and `write` was called more than once per message. - -* `pubsub?` **MUST** return `false` **unless** the pub/sub extension is supported. - - Pub/Sub patterns are idiomatic for WebSockets and EventSource connections but their API is out of scope for this extension. - -* `class` **MUST** return the client's Class, allowing it be extended with additional features (such as Pub/Sub, etc'). - - **Note**: Ruby adds this method automatically to every class, no need to do a thing. - -The server **MAY** support the following (optional) methods for the `client` object: - -* `handler` if implemented, **MUST** return the callback object linked to the `client` object. - -* `handler=` if implemented, **MUST** set a new Callback Object for `client`. - - This allows applications to switch from one callback object to another (i.e., in case of credential upgrades). - - Once a new Callback Object was set, the server **MUST** call the old handler's `on_close` callback and **afterwards** call the new handler's `on_open` callback. - - It is **RECOMMENDED** (but not required) that this also updates the value for `env['rack.upgrade']`. - -* `timeout` / `timeout=` allows applications to get / set connection timeouts dynamically and separately for each connection. Servers **SHOULD** provide a global setting for the default connection timeout. It is **RECOMMENDED** (but not required) that a global / default timeout setting be available from the command line (CLI). - -* `protocol` if implemented, **MUST** return the same value that was originally set by `env['rack.upgrade?']`. - - -WebSocket `ping` / `pong`, timeouts and network considerations **SHOULD** be implemented by the server. It is **RECOMMENDED** (but not required) that the server send `ping`s to prevent connection timeouts and to detect network failure. Clients **SHOULD** also consider sending `ping`s to detect network errors (dropped connections). - -Server settings **MAY** be provided to allow for customization and adaptation for different network environments or WebSocket extensions. It is **RECOMMENDED** that any settings be available as command line arguments and **not** incorporated into the application's logic. - ---- - -## Implementation Examples - -### Server-Client Upgrade to WebSockets / EventSource - -* **Server**: - - When a regular HTTP request arrives (non-upgradeable), the server will set the `env['rack.upgrade?']` flag to `false`, indicating that: 1. this specific request is NOT upgradable; and 2. the server supports this specification for either WebSocket and/or EventSource connections. - - When a WebSocket upgrade request arrives, the server will set the `env['rack.upgrade?']` flag to `:websocket`, indicating that: 1. this specific request is upgradable; and 2. the server supports this specification for WebSocket connections. - - When an EventSource request arrives, the server will set the `env['rack.upgrade?']` flag to `:sse`, indicating that: 1. this specific request is an EventSource request; and 2. the server supports this specification for EventSource connections. - -* **Client**: - - If a client decides to upgrade a request, they will place an appropriate Callback Object in the `env['rack.upgrade']` Hash key. - -* **Server**: - - 1. If the application's response status indicates an error or a redirection (status code `>= 300`), the server shall ignore the Callback Object and/or remove it from the `env` Hash, ignoring the rest of the steps that follow. - - 2. The server will review the `env` Hash *before* sending the response. If the `env['rack.upgrade']` was set, the server will perform the upgrade. - - 3. The server will send the correct response status and headers, as well as any headers present in the response object. The server will also perform any required housekeeping, such as closing the response body, if it exists. - - The response status provided by the response object shall be ignored and the correct response status shall be set by the server. - - 4. Once the upgrade had completed, the server will call the `on_open` callback. - - No other callbacks shall be called until the `on_open` callback had returned. - - WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message` **SHOULD NOT** be executed concurrently for the same connection. - - The `on_close` callback **MUST NOT** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc'). - - The `on_drained` callback **MAY** be called concurrently with the `on_message` callback, allowing data to be sent even while incoming data is being processed. Multi-threading considerations apply. - -## Example Usage - -The following is an example WebSocket echo server implemented using this specification: - -```ruby -module WSConnection - def on_open(client) - puts "WebSocket connection established (#{client.object_id})." - end - def on_message(client, data) - client.write data # echo the data back - puts "on_drained MUST be implemented if #{ pending } != 0." - end - def on_drained(client) - puts "If this line prints out, on_drained is supported by the server." - end - def on_shutdown(client) - client.write "The server is going away. Goodbye." - end - def on_close(client) - puts "WebSocket connection closed (#{client.object_id})." - end - extend self -end - -module App - def self.call(env) - if(env['rack.upgrade?'.freeze] == :websocket) - env['rack.upgrade'.freeze] = WSConnection - return [0, {}, []] - end - return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]] - end -end - -run App -``` - -The following example uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API is subject to a separate Pub/Sub API extension and isn't part of this specification (it is, however, supported by iodine): - -```ruby -module Chat - def on_open(client) - client.class.prepend MyPubSubModule unless client.pubsub? - client.subscribe "chat" - client.publish "chat", "#{env[:nickname]} joined the chat." - end - def on_message(client, data) - client.publish "chat", "#{env[:nickname]}: #{data}" - end - def on_close(client) - client.publish "chat", "#{env[:nickname]}: left the chat." - end - extend self -end - -module App - def self.call(env) - if(env['rack.upgrade?'.freeze]) - nickname = env['PATH_INFO'][1..-1] - nickname = "Someone" if nickname == "".freeze - env[:nickname] = nickname - return [0, {}, []] - end - return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]] - end -end - -run App -``` - -Note that SSE connections will only be able to receive messages (the `on_message` callback is never called). diff --git a/bin/console b/bin/console index e3bab481..968916c2 100755 --- a/bin/console +++ b/bin/console @@ -1,13 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -# this will compile Iodine and start a Ruby console with the Iodine gem loaded. - -Dir.chdir(File.expand_path(File.join('..', '..'), __FILE__)) -puts `rake clean` -puts `rake compile` - -require 'benchmark' -$LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__ ) require "bundler/setup" require "iodine" @@ -19,4 +12,4 @@ require "iodine" # Pry.start require "irb" -IRB.start +IRB.start(__FILE__) diff --git a/bin/info.md b/bin/info.md deleted file mode 100644 index 495b1f95..00000000 --- a/bin/info.md +++ /dev/null @@ -1,353 +0,0 @@ -# Ruby's Rack Push: Decoupling the real-time web application from the web - -![](https://bowild.files.wordpress.com/2018/05/6865783407_84f470ec02_o.jpg) - -Something exciting is coming. - -Everyone is talking about WebSockets and their older cousin EventSource / Server Sent Events (SSE). Faye and ActionCable are all the rage and real-time updates are becoming easier than ever. - -But it's all a mess. It's hard to set up, it's hard to maintain. The performance is meh. In short, the existing design is expensive - it's expensive in developer hours and it's expensive in hardware costs. - -However, [a new PR in the Rack repository](https://github.com/rack/rack/pull/1272) promises to change all that in the near future. - -This PR is a huge step towards simplifying our code base, improving real-time performance and lowering the overall cost of real-time web applications. - -In a sentence, it's an important step towards [decoupling](https://softwareengineering.stackexchange.com/a/244478/224017) the web application from the web. - -Remember, Rack is the interface Ruby frameworks (such and Rails and Sinatra) and web applications use to communicate with the Ruby application servers. It's everywhere. So this is a big deal. - -## The Problem in a Nutshell - -The problem with the current standard approach, in a nutshell, is that each real-time application process has to run two servers in order to support real-time functionality. - -The two servers might be listening on the same port, they might be hidden away in some gem, but at the end of the day, two different IO event handling units have to run side by side. - -"Why?" you might ask. Well, since you asked, I'll tell you (if you didn't ask, skip to the solution). - -### The story of the temporary `hijack` - -This is the story of a quick temporary solution coming up on it's 5th year as the only "standard" Rack solution available. - -At some point in our history, the Rack specification needed a way to support long polling and other HTTP techniques. Specifically, Rails 4.0 needed something for their "live stream" feature. - -For this purpose, [the Rack team came up with the `hijack` API approach](https://github.com/rack/rack/pull/481#issue-9702395). - -This approach allowed for a quick fix to a pressing need. was meant to be temporary, something quick until Rack 2.0 was released (5 years later, the Rack protocol is still at version 1.3). - -The `hijack` API offers applications complete control of the socket. Just hijack the socket away from the server and voilá, instant long polling / SSE support... sort of. - -That's where things started to get messy. - -To handle the (now "free") socket, a lot of network logic had to be copied from the server layer to the application layer (buffering `write` calls, handling incoming data, protocol management, timeout handling, etc'). - -This is an obvious violation of the "**S**" in S.O.L.I.D (single responsibility), as it adds IO handling responsibilities to the application / framework. - -It also violates the DRY principle, since the IO handling logic is now duplicated (once within the server and once within the application / framework). - -Additionally, this approach has issues with HTTP/2 connections, since the network protocol and the application are now entangled. - -### The obvious `hijack` price - -The `hijack` approach has many costs, some hidden, some more obvious. - -The most easily observed price is memory, performance and developer hours. - -Due to code duplication and extra work, the memory consumption for `hijack` based solutions is higher and their performance is slower (more system calls, more context switches, etc'). - -Using `require 'faye'` will add WebSockets to your application, but it will take almost 9Mb just to load the gem (this is before any actual work was performed). - -On the other hand, using the `agoo` or `iodine` HTTP servers will add both WebScokets and SSE to your application without any extra memory consumption. - -To be more specific, using `iodine` will consume about 2Mb of memory, marginally less than Puma, while providing both HTTP and real-time capabilities. - -### The hidden `hijack` price - -A more subtle price is higher hardware costs and a lower clients-per-machine ratio when using `hijack`. - -Why? - -Besides the degraded performance, the `hijack` approach allows some HTTP servers to lean on the `select` system call, (Puma used `select` last time I took a look). - -This system call [breaks down at around the 1024 open file limit](http://man7.org/linux/man-pages/man2/select.2.html#BUGS), possibly limiting each process to 1024 open connections. - -When a connection is hijacked, the sockets don't close as fast as the web server expects, eventually leading to breakage and possible crashes if the 1024 open file limit is exceeded. - -## The Solution - Callbacks and Events - -[The new proposed Rack Push PR](https://github.com/rack/rack/pull/1272) offers a wonderful effective way to implement WebSockets and SSE while allowing an application to remain totally server agnostic. - -This new proposal leaves the responsibility for the network / IO handling with the server, simplifying the application's code base and decoupling it from the network protocol. - -By using a callback object, the application is notified of any events. Leaving the application free to focus on the data rather than the network layer. - -The callback object doesn't even need to know anything about the server running the application or the underlying protocol. - -~~The callback object is automatically linked to the correct API using Ruby's `extend` approach, allowing the application to remain totally server agnostic.~~ **EDIT**: the PR was updated, replacing the `extend` approach with an extra `client` object. - -### How it works - -Every Rack server uses a Hash type object to communicate with a Rack application. - -This is how Rails is built, this is how Sinatra is built and this is how every Rack application / framework is built. It's in [the current Rack specification](https://github.com/rack/rack/blob/master/SPEC). - -A simple Hello world using Rack would look like this (placed in a file called `config.ru`): - -```ruby -# normal HTTP response -RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '12' }, [ 'Hello World!' ] ] -# note the `env` variable -APP = Proc.new {|env| RESPONSE } -# The Rack DSL used to run the application -run APP -``` - -This new proposal introduces the `env['rack.upgrade?']` variable. - -Normally, this variable is set to `nil` (or missing from the `env` Hash). - -However, for WebSocket connection, the `env['rack.upgrade?']` variable is set to `:websocket` and for EventSource (SSE) connections the variable is set to `:sse`. - -To set a callback object, the `env['rack.upgrade']` is introduced (notice the *missing* question mark). - -Now the design might look like this: - -```ruby -# Place in config.ru -RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '12' }, [ 'Hello World!' ] ] -# an example Callback class -class MyCallbacks - def on_open client - puts "* Push connection opened." - end - def on_message client, data - puts "* Incoming data: #{data}" - client.write "Roger that, \"#{data}\"" - end - def on_close client - puts "* Push connection closed." - end -end -# note the `env` variable -APP = Proc.new do |env| - if(env['rack.upgrade?']) - env['rack.upgrade'] = MyCallbacks.new - [200, {}, []] - else - RESPONSE - end -end -# The Rack DSL used to run the application -run APP -``` - -Run this application with the Agoo or Iodine servers and let the magic sparkle. - -For example, using Iodine: - -```bash -# install iodine, version 0.6.0 and up -gem install iodine -# start in single threaded mode -iodine -t 1 -``` - -Now open the browser, visit [localhost:3000](http://localhost:3000) and open the browser console to test some JavaScript. - -First try an EventSource (SSE) connection (run in browser console): - -```js -// An SSE example -var source = new EventSource("/"); -source.onmessage = function(msg) { - console.log(msg.id); - console.log(msg.data); -}; -``` - -Sweet! nothing happened just yet (we aren't sending notifications), but we have an open SSE connection! - -What about WebSockets (run in browser console): - -```js -// A WebSocket example -ws = new WebSocket("ws://localhost:3000/"); -ws.onmessage = function(e) { console.log(e.data); }; -ws.onclose = function(e) { console.log("closed"); }; -ws.onopen = function(e) { e.target.send("Hi!"); }; - -``` - -Wow! Did you look at the Ruby console - we have working WebSockets, it's that easy. - -And this same example will run perfectly using the Agoo server as well (both Agoo and Iodine already support the Rack Push proposal). - -Try it: - -```bash -# install the agoo server, version 2.1.0 and up -gem install agoo -# start it up -rackup -s agoo -p 3000 -``` - -Notice, no gems, no extra code, no huge memory consumption, just the Ruby server and raw Rack (I didn't even use a framework just yet). - -### The amazing push - -So far, it's so simple, it's hard to notice how powerful this is. - -Consider implementing a stock ticker, or in this case, a timer: - -```ruby -# Place in config.ru -RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '12' }, [ 'Hello World!' ] ] - -# A global live connection storage -module LiveList - @list = [] - @lock = Mutex.new - def <<(connection) - @lock.synchronize { @list << connection } - end - def >>(connection) - @lock.synchronize { @list.delete connection } - end - def any? - # remove connection to the "live list" - @lock.synchronize { @list.any? } - end - # this will send a message to all the connections that share the same process. - # (in cluster mode we get partial broadcasting only and this doesn't scale) - def broadcast(data) - # copy the list so we don't perform long operations in the critical section - tmp = nil # place tmp in this part of the scope - @lock.synchronize do - tmp = @list.dup # copy list into tmp - end - # iterate list outside of critical section - tmp.each {|c| c.write data } - end - extend self -end - -# Broadcast the time very second... but... -# Threads will BREAK in cluster mode. -@thread = Thread.new do - while(LiveList.any?) do - sleep(1) - LiveList.broadcast "The time is: #{Time.now}" - end -end - -# an example static Callback module -module MyCallbacks - def on_open client - # add connection to the "live list" - LiveList << client - end - def on_message(client, data) - # Just an example broadcast - LiveList.broadcast "Special Announcement: #{data}" - end - def on_close client - # remove connection to the "live list" - LiveList >> client - end - extend self -end - -# The Rack application -APP = Proc.new do |env| - if(env['rack.upgrade?']) - env['rack.upgrade'] = MyCallbacks - [200, {}, []] - else - RESPONSE - end -end -# The Rack DSL used to run the application -run APP -``` - -Run the iodine server in single process mode: `iodine -w 1` and the little timer is ticking. - -Honestly, I don't love the code I just wrote for the previous example. It's a little long, it's slightly iffy and we can't use iodine's cluster mode. - -For my next example, I'll author a chat room in 32 lines (including comments). - -I will use Iodine's pub/sub extension API to avoid the LiveList module and the timer thread. I don't want a timer, so I'll skip the [`Iodine.run_every` method](https://www.rubydoc.info/github/boazsegev/iodine/master/Iodine#run_every-class_method). - -Also, I'll limit the interaction to WebSocket clients. Why? to show I can. - -This will better demonstrate the power offered by the new `env['rack.upgrade']` approach and it will also work in cluster mode. - -Sadly, this means that the example won't run on Agoo for now. - -```ruby -# Place in config.ru -RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '12' }, [ 'Hello World!' ] ] -CHAT = "chat".freeze -# a Callback class -class MyCallbacks - def initialize env - @name = env["PATH_INFO"][1..-1] - @name = "unknown" if(@name.length == 0) - end - def on_open client - client.subscribe CHAT - client.publish CHAT, "#{@name} joined the chat." - end - def on_message client, data - client.publish CHAT, "#{@name}: #{data}" - end - def on_close client - client.publish CHAT, "#{@name} left the chat." - end -end -# The actual Rack application -APP = Proc.new do |env| - if(env['rack.upgrade?'] == :websocket) - env['rack.upgrade'] = MyCallbacks.new(env) - [200, {}, []] - else - RESPONSE - end -end -# The Rack DSL used to run the application -run APP -``` - -Start the application from the command line (in terminal): - -```bash -iodine -``` - -Now try (in the browser console): - -```js -ws = new WebSocket("ws://localhost:3000/Mitchel"); -ws.onmessage = function(e) { console.log(e.data); }; -ws.onclose = function(e) { console.log("Closed"); }; -ws.onopen = function(e) { e.target.send("Yo!"); }; -``` - -**EDIT**: Agoo 2.1.0 now implements pub/sub extensions, albeit, using slightly different semantics. I did my best so the same code would work on both servers. - -### Why didn't anyone think of this sooner? - -Actually, this isn't a completely new idea. - -Evens as the `hijack` API itself was suggested, [an alternative approach was suggested](https://github.com/rack/rack/pull/481#issuecomment-11916942). - -Another proposal was attempted [a few years ago](https://github.com/rack/rack/issues/1093). - -But it seems things are finally going to change, as two high performance server, [agoo](https://github.com/ohler55/agoo) and [iodine](https://github.com/boazsegev/iodine) already support this new approach. - -Things look promising. - -**UPDATE**: code examples were updated to reflect changes in theRack specification's PR. diff --git a/bin/mustache_bench.rb b/bin/mustache_bench.rb deleted file mode 100644 index 41de8c97..00000000 --- a/bin/mustache_bench.rb +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env ruby - -require 'benchmark/ips' -require 'mustache' -require 'iodine' - -def benchmark_mustache - # benchmark code was copied, in part, from: - # https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb - template = """ - {{#products}} -
- -
- {{/products}} - """ - - IO.write "test_template.mustache", template - filename = "test_template.mustache" - - data_1 = { - products: [ { - :external_index=>"This should've been \"properly\" escaped.", - :url=>"/products/7", - :image=>"products/product.jpg" - } ] - } - data_1000 = { - products: [] - } - - 1000.times do - data_1000[:products] << { - :external_index=>"product", - :url=>"/products/7", - :image=>"products/product.jpg" - } - end - - data_1000_escaped = { - products: [] - } - - 1000.times do - data_1000_escaped[:products] << { - :external_index=>"This should've been \"properly\" escaped.", - :url=>"/products/7", - :image=>"products/product.jpg" - } - end - - view = Mustache.new - view.template = template - view.render # Call render once so the template will be compiled - iodine_view = Iodine::Mustache.new(filename) - - puts "Ruby Mustache rendering (and HTML escaping) results in:", - view.render(data_1), "", - "Notice that Iodine::Mustache rendering (and HTML escaping) results in agressive escaping:", - iodine_view.render(data_1), "", "----" - - # return; - - Benchmark.ips do |x| - x.report("Ruby Mustache render list of 1000") do |times| - view.render(data_1000) - end - x.report("Iodine::Mustache render list of 1000") do |times| - iodine_view.render(data_1000) - end - - x.report("Ruby Mustache render list of 1000 with escaped data") do |times| - view.render(data_1000_escaped) - end - x.report("Iodine::Mustache render list of 1000 with escaped data") do |times| - iodine_view.render(data_1000_escaped) - end - - x.report("Ruby Mustache - no chaching - render list of 1000") do |times| - tmp = Mustache.new - tmp.template = template - tmp.render(data_1000) - end - x.report("Iodine::Mustache - no chaching - render list of 1000") do |times| - Iodine::Mustache.render(nil, data_1000, template) - end - - end -end - -benchmark_mustache diff --git a/bin/poc/Gemfile.lock b/bin/poc/Gemfile.lock deleted file mode 100644 index 34c61ae6..00000000 --- a/bin/poc/Gemfile.lock +++ /dev/null @@ -1,23 +0,0 @@ -GIT - remote: https://github.com/boazsegev/iodine.git - revision: 8b57a5f2008b9c35f2cf589be7ed121823a84582 - specs: - iodine (0.2.0) - rack - rake-compiler - -GEM - specs: - rack (2.0.1) - rake (11.2.2) - rake-compiler (0.9.5) - rake - -PLATFORMS - ruby - -DEPENDENCIES - iodine! - -BUNDLED WITH - 1.12.5 diff --git a/bin/poc/README.md b/bin/poc/README.md deleted file mode 100644 index 9ff70d0b..00000000 --- a/bin/poc/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# A proof of concept for Rack's `env['rack.websocket']` - -This is a proof of concept for Rack based Websocket connections, showing how the Rack API can be adjusted to support server native real-time connections. - -The chosen proof of concept was the ugliest chatroom I could find. - -Although my hope is that Rack will adopt the concept and make `env['rack.websocket?']` and `env['rack.websocket']` part of it's standard, at the moment it's an Iodine specific feature, implemented using `env['iodine.websocket']`. - -## Install - -Install required gems using: - -```sh -bundler install -``` - -## Run - -Run this application single threaded: - -```sh -bundler exec iodine -- -www ./www -``` - -Or both multi-threaded and forked (you'll notice that memory barriers for forked processes prevent websocket broadcasting from reaching websockets connected to a different process). - -```sh -bundler exec iodine -- -www ./www -t 16 -w 4 -``` - -## Further reading - -* https://github.com/rack/rack/issues/1093 - -* https://bowild.wordpress.com/2016/07/31/the-dark-side-of-the-rack - -* https://github.com/boazsegev/iodine diff --git a/bin/poc/config.ru b/bin/poc/config.ru deleted file mode 100644 index 975482f2..00000000 --- a/bin/poc/config.ru +++ /dev/null @@ -1,66 +0,0 @@ -# The Rack Application container -module MyRackApplication - # Rack applications use the `call` callback to handle HTTP requests. - def self.call(env) - # if upgrading... - if env['HTTP_UPGRADE'.freeze] =~ /websocket/i - # We can assign a class or an instance that implements callbacks. - # We will assign an object, passing it the request information (`env`) - env['iodine.websocket'.freeze] = MyWebsocket.new(env) - # Rack responses must be a 3 item array - # [status, {http: :headers}, ["response body"]] - return [0, {}, []] - end - # a semi-regualr HTTP response - out = File.open File.expand_path('../www/index.html', __FILE__) - [200, { 'X-Sendfile' => File.expand_path('../www/index.html', __FILE__), - 'Content-Length' => out.size }, out] - end -end - -# The Websocket Callback Object -class MyWebsocket - # this is optional, but I wanted the object to have the nickname provided in - # the HTTP request - def initialize(env) - # we need to change the ASCI Rack encoding to UTF-8, - # otherwise everything with the nickname will be a binary "blob" in the - # Javascript layer - @nickname = env['PATH_INFO'][1..-1].force_encoding 'UTF-8' - end - - # A classic websocket callback, called when the connection is opened and - # linked to this object - def on_open - puts 'We have a websocket connection' - end - - # A classic websocket callback, called when the connection is closed - # (after disconnection). - def on_close - puts "Bye Bye... #{count} connections left..." - end - - # A server-side niceness, called when the server if shutting down, - # to gracefully disconnect (before disconnection). - def on_shutdown - write 'The server is shutting down, goodbye.' - end - - def on_message(data) - puts "got message: #{data} encoded as #{data.encoding}" - # data is a temporary string, it's buffer cleared as soon as we return. - # So we make a copy with the desired format. - tmp = "#{@nickname}: #{data}" - # The `write` method was added by the server and writes to the current - # connection - write tmp - puts "broadcasting #{tmp.bytesize} bytes with encoding #{tmp.encoding}" - # `each` was added by the server and excludes this connection - # (each except self). - each { |h| h.write tmp } - end -end - -# `run` is a Rack API command, telling Rack where the `call(env)` callback is located. -run MyRackApplication diff --git a/bin/poc/gemfile b/bin/poc/gemfile deleted file mode 100755 index c51eb2df..00000000 --- a/bin/poc/gemfile +++ /dev/null @@ -1 +0,0 @@ -gem 'iodine', git: 'https://github.com/boazsegev/iodine.git' diff --git a/bin/poc/www/index.html b/bin/poc/www/index.html deleted file mode 100644 index 0d5cca2e..00000000 --- a/bin/poc/www/index.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - -

The Ugly Chatroom POC

-
- - -
- -
    - - diff --git a/bin/setup b/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/examples/async_task.ru b/examples/async_task.ru deleted file mode 100644 index eacc8dc6..00000000 --- a/examples/async_task.ru +++ /dev/null @@ -1,92 +0,0 @@ -# This is a task scheduling WebSocket push example application. -# -# Benchmark HTTPP with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients): -# -# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/ -# wrk -c2000 -d5 -t12 http://localhost:3000/ -# -# Test websocket tasks using the browser. For example: -# ws = new WebSocket("ws://localhost:3000/userID"); ws.onmessage = function(e) {console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; -# ws.onopen = function(e) {ws.send(JSON.stringify({'task': 'echo', 'data': 'Hello!'}));}; -require 'iodine' -require 'json' - -TASK_PUBLISHING_ENGINE = Iodine::PubSub::PROCESS - -# This module handles tasks and send them back to the frontend -module TaskHandler - def echo msg - msg = Iodine::JSON.parse(msg, symbolize_names: true) - publish_to = msg.delete(:from) - Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to - puts "performed 'echo' task" - rescue => e - puts "JSON task message error? #{e.message} - under attack?" - end - - def add msg - msg = Iodine::JSON.parse(msg, symbolize_names: true) - raise "addition task requires an array of numbers" unless msg[:data].is_a?(Array) - msg[:data] = msg[:data].inject(0){|sum,x| sum + x } - publish_to = msg.delete(:from) - Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to - puts "performed 'add' task" - rescue => e - puts - "JSON task message error? #{e.message} - under attack?" - end - - def listen2tasks - Iodine.subscribe(:echo) {|ch,msg| TaskHandler.echo(msg) } - Iodine.subscribe(:add) {|ch,msg| TaskHandler.add(msg) } - end - - extend self -end - -module WebsocketClient - def on_open client - # Pub/Sub directly to the client (or use a block to process the messages) - client.subscribe client.env['PATH_INFO'.freeze] - end - def on_message client, data - # Strings and symbol channel names are equivalent. - msg = Iodine::JSON.parse(data, symbolize_names: true) - raise "no valid task" unless ["echo".freeze, "add".freeze].include? msg[:task] - msg[:from] = client.env['PATH_INFO'.freeze] - client.publish msg[:task], msg.to_json, TASK_PUBLISHING_ENGINE - rescue => e - puts "JSON message error? #{e.message}\n\t#{data}\n\t#{msg}" - end - extend self -end - -APP = Proc.new do |env| - if env['rack.upgrade?'.freeze] == :websocket - env['rack.upgrade'.freeze] = WebsocketClient - [0,{}, []] # It's possible to set cookies for the response. - elsif env['rack.upgrade?'.freeze] == :sse - puts "SSE connections can only receive data from the server, the can't write." - env['rack.upgrade'.freeze] = WebsocketClient - [0,{}, []] # It's possible to set cookies for the response. - else - [200, {"Content-Type" => "text/plain"}, ["Send messages with WebSockets using JSON.\ni.e.: {\"task\":\"add\", \"data\":[1,2]}"]] - end -end - -# test automatically for Redis extensions. -if(Iodine::PubSub.default.is_a? Iodine::PubSub::Redis) - TASK_PUBLISHING_ENGINE = Iodine::PubSub.default - if(ARGV.include? "worker") - TaskHandler.listen2tasks - Iodine.workers = 1 - Iodine.threads = 16 if Iodine.threads == 0 - Iodine.start - exit(0) - end -else - TaskHandler.listen2tasks -end - -# # or in config.ru -run APP diff --git a/examples/bates/README.md b/examples/bates/README.md deleted file mode 100644 index 5b3389d1..00000000 --- a/examples/bates/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Bates numbering with CombinePDF and Rack - -This folder holds a demo application combining Iodine's `X-Sendfile` support with the [`combine_pdf` gem](https://github.com/boazsegev/combine_pdf) to create a "bates numbering" tool that is common to the one used by some court systems (such as Israeli courts). diff --git a/examples/bates/config.ru b/examples/bates/config.ru deleted file mode 100644 index 526af487..00000000 --- a/examples/bates/config.ru +++ /dev/null @@ -1,342 +0,0 @@ -require 'combine_pdf' -require 'rack' -require 'base64' -# require_relative 'pdf_controller' - -module BatesAPP - # This is the HTTP response object according to the Rack specification. - STATIC_RESPONSE = [200, { 'Content-Type' => 'text/html', 'X-Sendfile' => File.expand_path('./public/index.html') }.freeze, []].freeze - WS_RESPONSE = [403, { 'Content-Type' => 'text/html' }, "Forbidden"] - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - req = Rack::Request.new(env) - # check if this is an upgrade request (WebsSocket / SSE). - return WS_RESPONSE if(env['rack.upgrade?'.freeze]) - # simply return the RESPONSE object, no matter what request was received. - return STATIC_RESPONSE if req.params.empty? - return bates(req.params) - end - - - def self.bates(params) - params['file'] = params['file'].values # convert to Array - # catch any exception and tell the user which PDF caused the exception. - begin - # set the file_name variable - # this will be used in case an exception is caught - # to state which file caused the exception. - file_name = '' - - # container for the complete pdf - # the complete pdf container will hold all - # the merged pdf data. - completed_pdf = CombinePDF.new - - # get the output file name - # this will be used to name the download for the client's browser - output_name = params['bates']['output'].to_s + '.pdf' - output_name = "unknown.pdf" if output_name == '.pdf' - - # get some paramaters that will be used while combining pages - params['bates'] ||= {} - first_page_number = params['bates']['first_page_number'].to_i - first_index_number = params['bates']['first_index_number'].to_i - - # we will add an option for the stamping to ignore the first pdf - # this is useful for the court cases that use bates numbering - # (the "cover PDF" will be the briefs of submissions that contain exhibits to be bates stamped) - ignore_first_file = params['bates']['first_pdf_is_cover'] - first_page = nil - - # we will pick some data up while running combining the different pdf files. - # this will be used for the table of contents later on. - pdfs_pages_count = [] - pdf_dates = [] - pdf_titles = [] - - # we will be creating title pages before each PDF file. - # the title pages will be sized using the mediabox variable - # wich will be set with the dimentions of each pdf file's first page. - mediabox = nil - - # register UNICODE fonts if necessary - # in this example I will register the Hebrew font David from an existing PDF file. - unless CombinePDF::Fonts.get_font :my_new_david - # if your running Rails, consider Rails.root instead of Root - fonts = CombinePDF.new(File.expand_path('./david+bold.pdf').to_s).fonts(true) - # I know the first font is David regular, after using the - # ruby console and looking at the fonts array for the file. - CombinePDF.register_font_from_pdf_object :my_new_david, fonts[0] - # the second font of the array was the latin font for a newline and space... useless - # the third was the david bold. I will now add that font. - CombinePDF.register_font_from_pdf_object :my_new_david_bold, fonts[2] - end - - # iterate through the different files sent from the client's browser's web form - params['file'].each do |v| - # set the file_name variable in case an exception will be raised - file_name = v['name'] - - # parse the pdf data - # we will use the CombinePDF.parse method which allows us - # to parse data without saving the PDF to the file system. - # our javascript encoded the file data using base64, which we will need to decode. - # (this is specific to my form which uses the HTML5 File API in this specific manner) - # begin - puts "parsing file: #{file_name}" - pdf_file = CombinePDF.parse( - Base64.urlsafe_decode64( - v['data'].slice('data:application/pdf;base64,'.length, - v['data'].length) - ) - ) - # rescue - # puts "Failed when parsing #{file_name} with data:\n#{v['data'].inspect}" - # raise - # end - - # we will use the pages array a few times, so in order to avoid - # recomputing the array every time, we will save it to a local variable. - pdf_file_pages = pdf_file.pages - - # we will add the page count to the page count array, - # used by the table of contents - pdfs_pages_count << pdf_file_pages.length - - ###### - # create and add title page to arrays. - - if ignore_first_file && first_page.nil? - # if the first PDF file is a "cover page" PDF, - # we will not add a title, nor add the file. - # instead we will save the data in a variable to add it after we're done - pdf_dates << '' - pdf_titles << '' - first_page = pdf_file - else - # set title page mediabox size - # the mediabox data (page size) is contained in the page's - # :CropBox or :MediaBox keys (pages are Hash objects). - mediabox ||= pdf_file_pages[0][:CropBox] || pdf_file_pages[0][:MediaBox] - - # create a title page unless we're only merging or there is no indexing - if params['bates']['should_index'] && params['bates']['numbering'] != 3 - - # create an empty page object - title_page = CombinePDF.create_page mediabox - - # write the content to the title page. - # we will be using the I18n.t shortcut to write some of the data. - # the rest of the data, like the title, we got from the form. - title_page.textbox("#{params['bates']['title_type']} #{pdfs_pages_count.length + first_index_number - (ignore_first_file ? 2 : 1)}", - max_font_size: 34, - font: :my_new_david, - y: (mediabox[3] - mediabox[1]) / 2) unless params['bates']['title_type'].to_s.empty? - title_page.textbox v['title'].to_s, max_font_size: 36, font: :my_new_david_bold - title_page.textbox v['date'].to_s, max_font_size: 24, font: :my_new_david, height: (mediabox[3] - mediabox[1]) / 2 - - # we will add the page object to the completed pdf object. - # notice that page objects are created as "floating" pages, - # not attached to any specific PDF file/object. - completed_pdf << title_page - - # we will add some data that will be used to create the - # table of contents at a later stage. - page_count = pdfs_pages_count.pop + 1 - pdfs_pages_count << page_count - pdf_dates << v['date'].to_s - pdf_titles << v['title'].to_s - end - - # now we are ready to add the pdf file data to the completed pdf object. - # there is no need to add each page, we can add the pdf as a whole. - # (it's actually faster, as the PDF page catalog isn't recomputed for each page) - completed_pdf << pdf_file - end - end - - ########## - # create the index pdf... - # ...unless we're only merging or there is no indexing - if params['bates']['should_index'] && params['bates']['numbering'].to_i != 3 - - # set the fonts and formatting for the table of contents. - # - # also, add an empty array for the table data. - # - # the table data array will contain arrays of String objects, each one - # corresponding to a row in the table. - table_options = { font: :my_new_david, - header_font: :my_new_david_bold, - max_font_size: 12, - column_widths: (params['bates']['date_header'].to_s.empty? ? [3, 40, 4] : [3, 10, 30, 4]), - table_data: [] } - - # set the table header array. - # this is an array of strings. - # to chose the localized strings - table_options[:headers] = [params['bates']['number_header'], - (params['bates']['date_header'].to_s.empty? ? nil : params['bates']['date_header']), - params['bates']['title_header'].to_s, - params['bates']['page_header'].to_s] - table_options[:headers].compact! - - # by default, there are 25 rows per page for table pdf created by CombinePDF - # we can override this in the formatting (but we didn't). - # - # the 25 rows include 1 header row per page - so there are only 24 effective rows. - # - # we will calculate how many pages the table of contents pdf will have once completes, - # so we can add the count to the page numbers in the index. - index_page_length = pdfs_pages_count.length / 24 - index_page_length += 1 if pdfs_pages_count.length % 24 > 0 - - # set the page number for the first entry in the table of contents. - page_number = first_page_number + index_page_length - - # set the index count to 0, we will use it to change the index for each entry. - # we need a different variable in case the first PDF file is a "cover page". - index_count = 0 - - # iterate over the data we collected before and add it to the table data. - pdfs_pages_count.each_index do |i| - # add the data unless it is set to be ignored - unless ignore_first_file - - # add an array of strings to the :table_data array, - # representing a row in our table. - # remember there might not be a date column. - if params['bates']['date_header'].to_s.empty? - table_options[:table_data] << [(first_index_number + index_count).to_s, - pdf_titles[i], page_number] - else - table_options[:table_data] << [(first_index_number + index_count).to_s, - pdf_dates[i], - pdf_titles[i], page_number] - end - - # if the data was added to the index table, bump the index count - index_count += 1 - - end - - # make sure future data will not be ignored - ignore_first_file = false - - # add the page count to the page number, so that the next - # index's page number is up to date. - page_number += pdfs_pages_count[i] - end - - # if out current locale is hebrew, which is a right to left language, - # set the direction for the table to Right-To-left (:rtl). - # - # notice that RTL text should be automatically recognized, but that - # feature isn't available (and shouldn't be available) for tables. - table_options[:direction] = :rtl if params['bates']['dir'] == 'rtl' - - # if there is table data, we will create an index pdf. - unless table_options[:table_data].empty? - - # create the index PDF from the table data and options we have. - index_pdf = CombinePDF.create_table table_options - - # We will now add the words "Table of Contents" (or the I18n equivilant) - # to the first page of our new index_pdf PDF object. - # - # the table PDF object was created by CombinePDF using writable PDF pages, - # so we have properties like .mediabox and methods like .textbox - # at our disposal. - - # get the first page of the index_pdf object, we will use this reference a lot. - title_page = index_pdf.pages[0] - - # write the textbox, using the mediabox page data [x,y,width,height] to place - # the text we want to write. - # - # we will use the I18n.t shortcut to choose the text to write down. - title_page.textbox params['bates']['index_title'], - y: ((title_page.mediabox[3] - title_page.mediabox[1]) * 0.91), - height: ((title_page.mediabox[3] - title_page.mediabox[1]) * 0.03), - font: :my_new_david, - max_font_size: 36, - text_valign: :bottom - - # now we will add the index_pdf to the BEGINING of the completed pdf. - # for this we will use the >> operator instead of the << operator. - completed_pdf >> index_pdf - - end - - end - - # add first file if it was skipped - completed_pdf >> first_page unless first_page.nil? - - ##### - # number pages - # unless no numbering - if params['bates']['numbering'].to_i != 3 - # list the numbering options - numbering_options = [[:top, :bottom], [:top_left, :bottom_left], [:top_right, :bottom_right]] - - # set the first visible page number to the page where numbering starts - # this assumes that the bates numbering include the numbering of the "cover page", - # yet at the same time the numbering isn't visible on the "cover page" - first_page_number += pdfs_pages_count[0] if params['bates']['first_pdf_is_cover'] && params['bates']['skip_cover'] - - # call the page numbering method and - # add the special properties we want for the textbox - completed_pdf.number_pages(start_at: first_page_number, - page_range: params['bates']['skip_cover'] ? (pdfs_pages_count[0].to_i..-1) : nil, - font_name: :my_new_david, - font_size: 14, - font_color: [0, 0, 0.4], - box_color: [0.8, 0.8, 0.8], - border_width: 1, - border_color: [0.3, 0.3, 0.3], - box_radius: 8, - number_location: numbering_options[params['bates']['numbering'].to_i], - opacity: 0.75) - end - - # send the completed PDF to the client. - # if the completed PDF is empty, raise an error. - if completed_pdf.pages.empty? - # inform the client there was an unknown error. - response = STATIC_RESPONSE.dup - response[1] = response[1].dup - response[1]['set-cookie'] = "Unknown error - 0 pages." - return response - end - - # make sure the PDF version is high enough for the opacity we used in the page numbering. - completed_pdf.version = [completed_pdf.version, 1.6].max - - # we will format the PDF to a pdf file WITHOUT saving it to the file system, - # using the .to_pdf method (instead of the .save method). - # we will send the raw PDF data stream. - return [200, {'content-type' => "application/pdf", - 'content-disposition' => "attachment; filename=\"#{output_name}\""}, completed_pdf.to_pdf] - - rescue Exception => e - puts e.message, e.backtrace - puts "The file causing the exception is: #{file_name}" - # if an exception was raised, tell the user which PDF caused the exception - response = STATIC_RESPONSE.dup - response[1] = response[1].dup - response[1]['set-cookie'] = "notice=Unsupported or error reading file: #{file_name}" - return response - end - end -end - -Iodine.workers = 1 -Iodine.threads = 5 -Iodine::DEFAULT_SETTINGS[:public] ||= './public' - -p Iodine::DEFAULT_SETTINGS -Iodine::DEFAULT_SETTINGS[:max_body] = 70 if Iodine::DEFAULT_SETTINGS[:max_body].to_i < 5 - -run BatesAPP diff --git a/examples/bates/david+bold.pdf b/examples/bates/david+bold.pdf deleted file mode 100644 index 07443660..00000000 Binary files a/examples/bates/david+bold.pdf and /dev/null differ diff --git a/examples/bates/public/drop-pdf.png b/examples/bates/public/drop-pdf.png deleted file mode 100644 index da19fd10..00000000 Binary files a/examples/bates/public/drop-pdf.png and /dev/null differ diff --git a/examples/bates/public/index.html b/examples/bates/public/index.html deleted file mode 100644 index c88fe9b5..00000000 --- a/examples/bates/public/index.html +++ /dev/null @@ -1,600 +0,0 @@ - - - Bates Numbering for Court - - - - - -

    Bates Numbering - PDF file merge and number

    - -
    - -
    -

    Drag PDF files here

    -

    -

      -
    • -
      File Name
      -
      Date
      -
      Title
      -
    • -
    - - -
      - -

      -

      Settings

      - -
      Output file name:
      First page number: -
      First index number: -
      Numbering: -
      -
      -
      Language: -
      Index Title: - -
      "Number" Header: - -
      "Date" Header (Leave empty to ignore): - -
      "Title" Header: - -
      "Page Number" Header: - -
      Cover Page Title: - - - - - - diff --git a/examples/client b/examples/client new file mode 100755 index 00000000..1b5c0617 --- /dev/null +++ b/examples/client @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'iodine' + +Iodine.verbosity = 3 # only log warnings or worse + +# A demo client class +module Client + + # a helper to print response data + def self.print_response(h) + puts "#{h.version} #{h.status}" + h.headers.each {|k,v| puts("#{k}: #{v}\r") if k.is_a? String } + puts "\r" + while(s = h.gets) + puts s + end + end + + # Called Once per HTTP request. Each HTTP request starts with a new HTTP handle / Event object (`h`). + def self.on_http(h) + print_response(h) + end + + # Called when a long-running connection opens. + # + # This will be called for Event Source, WebSocket, TCP/IP and Raw Unix Socket connections. + def self.on_open(ws) + print_response(ws) + puts "Opening #{ws.websocket? ? "WebSocket" : "SSE" } Connection" + ws.subscribe(:stdio) if ws.websocket? # SSE connections don't send stuff + end + # Called when incoming data arrives and its behavior depends on the underlying protocol. + def self.on_message(ws, data) + puts "Got WebSocket Message: #{data}" + end + + # Called a long running connection was closed (WebSocket / SSE / TCP/IP / Raw Unix Socket). + def self.on_close(ws) + puts "Closed #{ws.websocket? ? "WebSocket" : "SSE" } Connection" + end + # Called after `on_http`/`on_closed` or if a client failed to connect. + def self.on_finish(h) + Server.stop + end + # Called when an Event Source (SSE) event has been received (when acting as an Event Source client). + def self.on_eventsource(sse, message) + puts "id: #{message.id}" + puts "event: #{message.event}" + puts "data: #{message.data}" + end +end + +# I will cheat a little, using Iodine's CLI parser... +opt = Iodine::Base::CLI.parse(false) +opt[:bind] ||= opt[0] # allow the first unnamed parameter to be the URL to connect to +raise "ERROR: this app requires a URL to connect to!" unless opt[:bind] + +Server.threads = 0 +Server.workers = 0 + +# create the service instance, the block returns a connection handler. +Iodine::Connection.new(opt[:bind], handler: Client) + +# Read STDIO while running +Thread.new do + while true + Server.publish channel: :stdio, message: STDIN.readline + end +end + +# Start the server +Server.start diff --git a/examples/config-hello.nru b/examples/config-hello.nru new file mode 100644 index 00000000..c0b9a055 --- /dev/null +++ b/examples/config-hello.nru @@ -0,0 +1,9 @@ +# A simple Hello World NeoRack application +module App + HELLO_RESPONSE = "Hello World!" + def self.on_http(e) + e.finish HELLO_RESPONSE + end +end + +run App diff --git a/examples/config-hello.ru b/examples/config-hello.ru new file mode 100644 index 00000000..12be5247 --- /dev/null +++ b/examples/config-hello.ru @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# A simple Hello World Rack application +module App + HELLO_RESPONSE = [200, {"content-length" => 12}, ["Hello World!"]] + def self.call(e) + HELLO_RESPONSE + end +end + +run App diff --git a/examples/config-hijack.ru b/examples/config-hijack.ru new file mode 100644 index 00000000..33318a17 --- /dev/null +++ b/examples/config-hijack.ru @@ -0,0 +1,13 @@ +# A simple Rack application that prints out the `env` variable to the browser. +module EchoApp + def self.call(e) + io = e['rack.hijack'].call + txt = ["Hijacked!\r\n"] + e.each {|k,v| txt << "#{k}: #{v}\r\n" } + txt = txt.join() + Thread.new {sleep(0.5); io.write("HTTP/1.1 200 OK\r\nContent-Length: #{txt.bytesize}\r\n\r\n#{txt}"); io.close; puts("Sent"); } + [200, {}, []] + end +end + +run EchoApp diff --git a/examples/config.nru b/examples/config.nru new file mode 100755 index 00000000..721a6429 --- /dev/null +++ b/examples/config.nru @@ -0,0 +1,132 @@ +# This is both a NeoRack and a Rack application. +# +# Iodine can run both Rack and NeoRack applications. +# +# In this case the `on_http` implementation will be prioritized over the `call` implementation. +# +# Benchmark with keep-alive: +# +# ab -c 200 -t 4 -n 1000000 -k http://127.0.0.1:3000/ +# wrk -c200 -d4 -t2 http://localhost:3000/ +# +# Connect to chat server with WebSockets: +# +# ws = new WebSocket("ws://" + document.location.host + document.location.pathname); +# ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; +# ws.onclose = function(e) {console.log("closed")}; +# ws.onopen = function(e) {ws.send("hi");}; +# +# Listen to chat messages with Server Sent Events (EventSource / SSE): +# +# const listener = new EventSource(document.location.href); +# listener.onmessage = (e) => { console.log(e); } +# listener.onevent = (e) => { console.log(e); } +# +module App + def self.call(e) + txt = [] + if e['rack.upgrade?'] + e['rack.upgrade'] = self + else + e.each {|k,v| txt << "#{k}: #{v}\r\n" } + end + [200, {}, txt] + end + # this approach streams the data, adding HTTP overhead + # However, this show-cases the possibilities for longer responses. + def self.on_http_stream(e) + # we write without finishing and without setting the content-length header... + # servers should automatically stream the response (i.e., using chunked encoding) + e.write "path: #{e.path}\r\n" + e.write "query: #{e.query}\r\n" + e.write "method: #{e.method}\r\n" + e.write "version: #{e.version}\r\n" + e.write "from: #{e.from} (real: #{e.peer_addr})\r\n" + # e.write "thread: #{Thread.current.to_s}\r\n" + e.headers.each {|k,v| e.write "#{k}: #{v}\r\n" } + # echo request body to the response + while(l = e.gets) + e.write l + end + e.finish + end + def self.on_http_single_packet(e) + # this is similar to Rack, compiling the whole response before sending it + # this should optimally result in `content-length` being set by the server + out = "path: #{e.path}\r\nquery: #{e.query}\r\nmethod: #{e.method}\r\nversion: #{e.version}\r\n" + out += "from: #{e.from} (#{e.peer_addr})\r\n" + # out += "thread: #{Thread.current.to_s}\r\n" + e.headers.each {|k,v| out += "#{k}: #{v}\r\n" } + # echo request body to the response + while(l = e.gets) + out += l + end + # finally we write the data. + # efficient servers will see this is the first `write` and set `content-length` + e.finish out + end + def self.on_http(e) + # change here to see the difference. + e.write_header :server, 'iodine' + e.write_header :performs, [['echo', 'ws-chat'], 2] + e.headers['LAST-VISIT'] = "#{Time.now.to_i - e.cookie(:been).to_i}s ago" if(e.cookie(:been)) + e.set_cookie :been, Time.now.to_i.to_s + on_http_single_packet(e) + end + # # Called after `on_http` (or `on_closed` when implementing the WebSocket/SSE extensions) + # def self.on_finish(e) + # # perform cleanup + # end + + + def self.on_open(e) + e.subscribe :broadcast + end + def self.on_message(e, m) + Iodine.publish :broadcast, m + end +end + + +class LogPubSub < Iodine::PubSub::Engine + def subscribe ch + puts "(#{Process.pid}) Someone subscribed to #{ch}" + end + def psubscribe ch + puts "(#{Process.pid}) Someone subscribed to #{ch} (pattern)" + end + def unsubscribe ch + puts "(#{Process.pid}) Someone unsubscribed to #{ch}" + end + def punsubscribe ch + puts "(#{Process.pid}) Someone unsubscribed to #{ch} (pattern)" + end + def publish m + puts "(#{Process.pid}) Someone published to #{m.channel}" + Iodine.publish channel: m.channel, message: m.message, filter: m.filter, engine: Iodine::PubSub::CLUSTER + end +end + +Iodine::PubSub.default = LogPubSub.new + +run App + +# Benchmark with keep-alive: +# +# ab -c 200 -t 4 -n 1000000 -k http://127.0.0.1:3000/ +# wrk -c200 -d4 -t2 http://localhost:3000/ +# +# Connect to chat server with WebSockets: +# +# ws = new WebSocket("ws://" + document.location.host + document.location.pathname); +# ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; +# ws.onclose = function(e) {console.log("closed")}; +# ws.onopen = function(e) {ws.send("hi");}; +# +# Listen to chat messages with Server Sent Events (EventSource / SSE): +# +# const listener = new EventSource(document.location.href); +# listener.onmessage = (e) => { console.log(e); } +# listener.addEventListener("time", (e) => { console.log(e); }) +# listener.addEventListener("event", (e) => { console.log(e); }) + diff --git a/examples/config.ru b/examples/config.ru index 3ae15480..0573625a 100644 --- a/examples/config.ru +++ b/examples/config.ru @@ -1,59 +1,20 @@ -# This is a WebSocket / SSE notification broadcasting application. -# -# Running this application from the command line is easy with: -# -# iodine -# -# Or, in single thread and single process: -# -# iodine -t 1 -w 1 -# -# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients): -# -# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/ -# wrk -c2000 -d5 -t12 http://localhost:3000/ -# -# Test websocket echo using the browser. For example: -# ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");}; - - -# A simple router - Checks for Websocket Upgrade and answers HTTP. -module MyHTTPRouter - # This is the HTTP response object according to the Rack specification. - HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '77' }, - ['Please connect using WebSockets or SSE (send messages only using WebSockets).']] - - WS_RESPONSE = [0, {}, []].freeze - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - # check if this is an upgrade request (WebsSocket / SSE). - if(env['rack.upgrade?'.freeze]) - env['rack.upgrade'.freeze] = BroadcastClient - return WS_RESPONSE - end - # simply return the RESPONSE object, no matter what request was received. - HTTP_RESPONSE - end -end - -# A simple Websocket Callback Object. -module BroadcastClient - # seng a message to new clients. - def on_open client - client.subscribe :broadcast - end - # send a message, letting the client know the server is suggunt down. - def on_shutdown client - client.write "Server shutting down. Goodbye." - end - # perform the echo - def on_message client, data - client.publish :broadcast, data - end - extend self +# A simple Rack application that prints out the `env` variable to the browser. +module App + def self.call(env) + txt = [] + if env['rack.upgrade?'] + env['rack.upgrade'] = self + else + env.each {|k,v| txt << "#{k}: #{v}\r\n" } + end + [200, {}, txt] + end + def self.on_open(e) + e.subscribe :broadcast + end + def self.on_message(e, m) + Iodine.publish :broadcast, m + end end -# this function call links our HelloWorld application with Rack -run MyHTTPRouter +run App diff --git a/examples/echo.ru b/examples/echo.ru deleted file mode 100644 index 314218cc..00000000 --- a/examples/echo.ru +++ /dev/null @@ -1,59 +0,0 @@ -# This is a Websocket echo application. -# -# Running this application from the command line is easy with: -# -# iodine echo.ru -# -# Or, in single thread and single process: -# -# iodine -t 1 -w 1 echo.ru -# -# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients): -# -# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/ -# wrk -c2000 -d5 -t12 http://localhost:3000/ -# -# Test websocket echo using the browser. For example: -# ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");}; - - -# A simple router - Checks for Websocket Upgrade and answers HTTP. -module MyHTTPRouter - # This is the HTTP response object according to the Rack specification. - HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '32' }, - ['Please connect using websockets.']] - - WS_RESPONSE = [0, {}, []].freeze - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - # check if this is an upgrade request. - if(env['rack.upgrade?'.freeze] == :websocket) - env['rack.upgrade'.freeze] = WebsocketEcho - return WS_RESPONSE - end - # simply return the RESPONSE object, no matter what request was received. - HTTP_RESPONSE - end -end - -# A simple Websocket Callback Object. -module WebsocketEcho - # seng a message to new clients. - def on_open client - client.write "Welcome to our echo service!" - end - # send a message, letting the client know the server is suggunt down. - def on_shutdown client - client.write "Server shutting down. Goodbye." - end - # perform the echo - def on_message client, data - client.write data - end - extend self -end - -# this function call links our HelloWorld application with Rack -run MyHTTPRouter diff --git a/examples/etag.ru b/examples/etag.ru deleted file mode 100644 index 14b85751..00000000 --- a/examples/etag.ru +++ /dev/null @@ -1,16 +0,0 @@ -# This example uses Rack::ETag to allow for response caching. - -require 'rack' -require 'iodine' - -App = Proc.new do |env| - [200, - { "Content-Type" => "text/html".freeze, - "Content-Length" => "16".freeze }, - ['Hello from Rack!'.freeze] ] -end - -use Rack::ConditionalGet -use Rack::ETag, 'public' - -run App diff --git a/examples/fake b/examples/fake new file mode 100755 index 00000000..855cd8d9 --- /dev/null +++ b/examples/fake @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +require 'iodine' + +# Iodine.verbosity = 5 +# Iodine.threads = 0 +# Iodine.workers = 0 + + +# Define the protocol for our service +module FakeHTTP + def on_message(client, data) + client.write "HTTP/1.1 200 OK\r\ndate: Mon, 22 Jul 2024 14:06:56 GMT\r\ncontent-length:#{data.bytesize}\r\n\r\n#{data}" + end + # We can use a singleton pattern, + # as the Protocol object doesn't contain any per-connection data. + extend self +end + +# create the service instance, the block returns a connection handler. +Iodine.listen(handler: FakeHTTP, service: :tcp) + +# Start the server +Iodine.start diff --git a/examples/hello.ru b/examples/hello.ru deleted file mode 100644 index ffcbbdfd..00000000 --- a/examples/hello.ru +++ /dev/null @@ -1,29 +0,0 @@ -# This is a simple Hello World Rack application -# -# Running this application from the command line is easy with: -# -# iodine hello.ru -# -# Or, in single thread and single process: -# -# iodine -t 1 -w 1 hello.ru -# -# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients): -# -# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/ -# wrk -c2000 -d5 -t12 http://localhost:3000/ - -module HelloWorld - # This is the HTTP response object according to the Rack specification. - RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '12' }, [ 'Hello World!' ] ] - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - # simply return the RESPONSE object, no matter what request was received. - RESPONSE - end -end - -# this function call links our HelloWorld application with Rack -run HelloWorld diff --git a/examples/hello_neorack b/examples/hello_neorack new file mode 100755 index 00000000..a0e31370 --- /dev/null +++ b/examples/hello_neorack @@ -0,0 +1,137 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + + +# Benchmark with keep-alive: +# +# ab -c 200 -t 4 -n 1000000 -k http://127.0.0.1:3000/ +# wrk -c200 -d4 -t2 http://localhost:3000/ +# +# Connect to chat server with WebSockets: +# +# ws = new WebSocket("ws://" + document.location.host + document.location.pathname); +# ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; +# ws.onclose = function(e) {console.log("closed")}; +# ws.onopen = function(e) {ws.send("hi");}; +# +# Listen to chat messages with Server Sent Events (EventSource / SSE): +# +# const listener = new EventSource(document.location.href); +# listener.onmessage = (e) => { console.log(e); } +# listener.addEventListener("time", (e) => { console.log(e); }) +# listener.addEventListener("event", (e) => { console.log(e); }) + +require 'iodine' + +Server.threads = 1 +Server.workers = 0 + +# Define the protocol for our service +# +# We can use a singleton pattern, as the Protocol object doesn't contain any per-connection data. +# +# See the [NeoRack](https://github.com/boazsegev/neorack) specification for details. +module MyNeoRackApp + # Called Once per HTTP request. Each HTTP request starts with a new HTTP handle / Event object (`h`). + def self.on_http(h) + h[:start] = Time.now + # This would be faster if we sent the whole response as a single String. + # + # However, this example demonstrates a Streaming response, so we will call `h.write` multiple times. + h.status = 200 + h.write("Hello NeoRack World!\r\n#{h.method} #{h.path} #{h.version}\r\n") + h.headers.each {|k,v| h.write("#{k}: #{v}\r\n") if k.is_a? String } + h.finish + end + + # Called after `on_http` / `on_closed` or if as a client Iodine failed to connect. + def self.on_finish(h); puts "#{h.method} #{h.path} - #{Time.now - h[:start]}s" end + + # This is ignored by NeoRack. + # + # It's called when receiving HTTP requests using a Rack server (or if on_http is removed). + # + # [Rack](https://github.com/rack/rack) is the classical server interface and it is supported by Iodine with minimal extensions. + def self.call(env); + env[:start] = Time.now + txt = [] + txt << "Hello Rack World!\r\n#{env['REQUEST_METHOD']} #{env['PATH_INFO']} #{env['HTTP_VERSION']}\r\n" + env.each {|k,v| txt << "#{k}: #{v}\r\n" if k.is_a? String } + [200, {}, txt] + end + + # When `on_authenticate is present, a missing `on_authenticate_sse` or missing `on_authenticate_websocket` + # will route to this callback, allowing authentication logic to be merged in one method. + # + # **Must** return `true` for the connection to be allowed. + # + # **Any other return value will cause the connection to be refused** (and disconnected). + # + # By default, if `on_message` or `on_open` are defined, than `on_authenticate` returns `true`. Otherwise, `on_authenticate` returns `false`. + def self.on_authenticate(h) + h[:start] = Time.now + puts "Authenticating client #{h.object_id}" + true + end + # def self.on_authenticate_sse(h); end + # def self.on_authenticate_websocket(h); end + + # Called when a long-running connection opens. + # + # This will be called for Event Source, WebSocket, TCP/IP and Raw Unix Socket connections. + def self.on_open(ws) + ws.subscribe(:time) if Server.extensions[:pubsub] + end + # Called when incoming data arrives and its behavior depends on the underlying protocol. + # + # For TCP/IP and Raw Unix connections, the data is a Binary encoded String with some (or all) of the data available in the incoming socket buffer. + # + # For WebSockets, the `data` element will contain a String with the WebSocket Message. + # + # If the WebSocket message is in text, the String encoding will be UTF-8. + # + # If the WebSocket message is binary, the String encoding will be Binary (ASCII). + def self.on_message(ws, data) + puts "Got WebSocket Message: #{data}" + ws.write("Echo: #{data}") + end + # Called when all calls to {Iodine::Connection#write} have been handled and the outgoing buffer is now empty. + def self.on_drained(ws) + puts "Finished sending data to #{ws.object_id}" + end + # Called when timeout has been reached for a TCP/IP / Raw Unix Socket connection. + def self.on_timeout(tcp) + puts "Timeout detected for #{tcp.object_id}" + end + # Called when the worker that manages this connection (or the root process, in non-cluster mode) starts shutting down. + def self.on_shutdown(ws) + ws.write "Server shutting down. Goodbye." + end + # Called a long running connection was closed (WebSocket / SSE / TCP/IP / Raw Unix Socket). + def self.on_close(ws) + puts "Closed #{ws.object_id}" + end + # Called when an Event Source (SSE) event has been received (when acting as an Event Source client). + def self.on_eventsource(sse, message) + puts "Normally only an SSE client would receive sse messages...\r\n#{message}" + end + # Called an Event Source (SSE) client send a re-connection request with the ID of the last message received. + def self.on_eventsource_reconnect(sse, last_message_id) + puts "Reconnecting SSE client #{sse.object_id}, should send everything after message ID #{last_message_id}" + end +end + +# create the service instance, the block returns a connection handler. +Server.listen(handler: MyNeoRackApp) + +# create the time pub/sub service +if Server.extensions[:pubsub] + Iodine.run_after(1000, -1) do + Server.publish(channel: :time, message: Iodine::Utils.time2str(Time.now)) + end +end + + + +# Start the server +Server.start diff --git a/examples/pubsub_engine.ru b/examples/pubsub_engine.ru deleted file mode 100644 index 51bf94b4..00000000 --- a/examples/pubsub_engine.ru +++ /dev/null @@ -1,81 +0,0 @@ -# This example implements a custom (noop) pub/sub engine according to the Iodine::PubSub::Engine specifications. -# -require 'uri' -require 'iodine' - -# creates an example Pub/Sub Engine that merely reports any pub/sub events to the system's terminal -class PubSubReporter < Iodine::PubSub::Engine - def initialize - # make sure engine setup is complete - super - # register engine and make it into the new default - @target = Iodine::PubSub.default - Iodine::PubSub.default = self - Iodine::PubSub.attach self - end - def subscribe to, as = nil - puts "* Subscribing to \"#{to}\" (#{as || "exact match"})" - end - def unsubscribe to, as = nil - puts "* Unsubscribing to \"#{to}\" (#{as || "exact match"})" - end - def publish to, msg - puts "* Publishing to \"#{to}\": #{msg.to_s[0..12]}..." - # we need to forward the message to the actual engine (the previous default engine), - # or it will never be received by any Pub/Sub client. - @target.publish to, msg - end -end - -PubSubReporter.new - -# A simple router - Checks for Websocket Upgrade and answers HTTP. -module MyHTTPRouter - # This is the HTTP response object according to the Rack specification. - HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '32' }, - ['Please connect using websockets.']] - - WS_RESPONSE = [0, {}, []] - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - # check if this is an upgrade request. - if(env['rack.upgrade?'.freeze]) - puts "SSE connections will not be able te send data, just listen." if(env['rack.upgrade?'.freeze] == :sse) - env['rack.upgrade'.freeze] = PubSubClient.new(env['PATH_INFO'] && env['PATH_INFO'].length > 1 ? env['PATH_INFO'][1..-1] : "guest") - return WS_RESPONSE - end - # simply return the RESPONSE object, no matter what request was received. - HTTP_RESPONSE - end -end - -# A simple Websocket Callback Object. -class PubSubClient - def initialize name - @name = name - end - # seng a message to new clients. - def on_open(client) - client.subscribe "chat" - # let everyone know we arrived - client.publish "chat", "#{@name} entered the chat." - end - # send a message, letting the client know the server is suggunt down. - def on_shutdown(client) - client.write "Server shutting down. Goodbye." - end - # perform the echo - def on_message(client, data) - client.publish "chat", "#{@name}: #{data}" - end - def on_close(client) - # let everyone know we left - client.publish "chat", "#{@name} left the chat." - # we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed. - end -end - -# this function call links our HelloWorld application with Rack -run MyHTTPRouter diff --git a/examples/rack3.ru b/examples/rack3.ru deleted file mode 100644 index 98943d0a..00000000 --- a/examples/rack3.ru +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true -# adjusted from the code suggested by @taku0 (GitHub issue #131) -require 'rack' -require 'iodine' - -run { |env| - response = Rack::Response.new(nil, 204) - response.set_cookie('aaa', 'aaa') - response.set_cookie('bbb', 'bbb') - response.finish -} diff --git a/examples/redis.ru b/examples/redis.ru deleted file mode 100644 index 6a5032f9..00000000 --- a/examples/redis.ru +++ /dev/null @@ -1,70 +0,0 @@ -# This example implements a Redis pub/sub engine according to the Iodine::PubSub::Engine specifications. -# -# Run this applications on two ports, in two terminals to see the synchronization is action: -# -# IODINE_REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3000 redis.ru -# IODINE_REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3030 redis.ru -# -# Or: -# -# iodine -t 1 -p 3000 redis.ru -redis redis://localhost:6379/0 -# iodine -t 1 -p 3030 redis.ru -redis redis://localhost:6379/0 -# -require 'iodine' -# initialize the Redis engine for each Iodine process. -if Iodine::DEFAULT_HTTP_ARGS[:redis_] - puts "* Redis support automatically detected." -else - puts "* No Redis, it's okay, pub/sub will support the process cluster." -end - -# A simple router - Checks for Websocket Upgrade and answers HTTP. -module MyHTTPRouter - # This is the HTTP response object according to the Rack specification. - HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html', - 'Content-Length' => '32' }, - ['Please connect using websockets.']] - - WS_RESPONSE = [0, {}, []] - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - # check if this is an upgrade request. - if(env['rack.upgrade?'.freeze]) - puts "SSE connections will not be able te send data, just listen." if(env['rack.upgrade?'.freeze] == :sse) - env['rack.upgrade'.freeze] = WS_RedisPubSub.new(env['PATH_INFO'] && env['PATH_INFO'].length > 1 ? env['PATH_INFO'][1..-1] : "guest") - return WS_RESPONSE - end - # simply return the RESPONSE object, no matter what request was received. - HTTP_RESPONSE - end -end - -# A simple Websocket Callback Object. -class WS_RedisPubSub - def initialize name - @name = name - end - # seng a message to new clients. - def on_open client - client.subscribe "chat" - # let everyone know we arrived - client.publish "chat", "#{@name} entered the chat." - end - # send a message, letting the client know the server is suggunt down. - def on_shutdown client - client.write "Server shutting down. Goodbye." - end - # perform the echo - def on_message client, data - client.publish "chat", "#{@name}: #{data}" - end - def on_close client - # let everyone know we left - client.publish "chat", "#{@name} left the chat." - # we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed. - end -end - -# this function call links our HelloWorld application with Rack -run MyHTTPRouter diff --git a/examples/shootout.ru b/examples/shootout.ru deleted file mode 100644 index 470fb27e..00000000 --- a/examples/shootout.ru +++ /dev/null @@ -1,73 +0,0 @@ -require 'iodine' -require 'json' -require 'rack' - -module ShootoutApp - # the default HTTP response - def self.call(env) - if(env['rack.upgrade?'.freeze] == :websocket) - env['rack.upgrade'.freeze] = ShootoutApp - return [0, {}, []] - end - out = [] - len = 0 - out << "ENV:\n" - len += 5 - env.each { |k, v| out << "#{k}: #{v}\n" ; len += out[-1].length } - request = Rack::Request.new(env) - out << "\nRequest Path: #{request.path_info}\n" - len += out[-1].length - unless request.params.empty? - out << "Params:\n" - len += out[-1].length - request.params.each { |k,v| out << "#{k}: #{v}\n" ; len += out[-1].length } - end - if(env['rack.input']) - env['rack.input'].rewind - out << "Body\n\n" - out << env['rack.input'].read - len += out[-1].length + 6 - out << "\n\nBody Length: #{out[-1].length}\n\n" - len += out[-1].length - end - [200, { 'Content-Length' => len.to_s, 'Content-Type' => 'text/plain; charset=UTF-8;' }, out] - end - # We'll base the shootout on the internal Pub/Sub service. - # It's slower than writing to every socket a pre-parsed message, but it's closer - # to real-life implementations. - def self.on_open client - client.subscribe(:shootout_b, as: :binary) # { |ch, msg| client.write(msg)} - client.subscribe(:shootout) # { |ch, msg| client.write(msg)} - end - def self.on_message client, data - if data[0] == 'b' # binary - client.publish :shootout_b, data - data[0] = 'r' - client.write data - return - end - cmd, payload = JSON(data).values_at('type', 'payload') - if cmd == 'echo' - client.write({type: 'echo', payload: payload}.to_json) - else - client.publish :shootout, {type: 'broadcast', payload: payload}.to_json - client.write({type: "broadcastResult", payload: payload}.to_json) - end - end -end - -run ShootoutApp -# -# def cycle -# puts `websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --server-type binary --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000` -# sleep(4) -# puts `wrk -c4000 -d15 -t2 http://localhost:3000/` -# true -# end -# sleep(10) while cycle - -# # Used when debugging: -# ON_IDLE = proc { Iodine::Base.db_print_protected_objects ; Iodine.on_idle(&ON_IDLE) } -# ON_IDLE.call -# Iodine.on_shutdown { Iodine::Base.db_print_protected_objects } - diff --git a/examples/sub-protocols.ru b/examples/sub-protocols.ru deleted file mode 100644 index 376334d9..00000000 --- a/examples/sub-protocols.ru +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -# This is a WebSocket / SSE notification example application. -# -# In this example, WebSocket sub-protocols are explored. -# -# Running this application from the command line is easy with: -# -# iodine -# -# Or, in a single thread and a single process: -# -# iodine -t 1 -w 1 -# -# Test using: -# -# var subprotocol = "echo"; // or "chat" -# ws = new WebSocket("ws://localhost:3000/Mitchel", subprotocol); -# ws.onmessage = function(e) { console.log(e.data); }; -# ws.onclose = function(e) { console.log("Closed"); }; -# ws.onopen = function(e) { e.target.send("Yo!"); }; - - -# Chat clients connect with the "chat" sub-protocol. -class ChatClient - def on_open client - @nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest" - client.subscribe :chat - client.publish :chat , "#{@nickname} joined the chat." - end - def on_close client - client.publish :chat , "#{@nickname} left the chat." - end - def on_shutdown client - client.write "Server is shutting down... disconnecting all clients. Goodbye." - end - def on_message client, message - client.publish :chat , "#{@nickname}: #{message}" - end -end - -# Echo clients connect with the "echo" sub-protocol. -class EchoClient - def on_open client - client.write "You established an echo connection." - end - def on_shutdown client - client.write "Server is shutting down... goodbye." - end - def on_message client, message - client.write message - end -end - -# Rack application module -module APP - # the allowed protocols - CHAT_PROTOCOL_NAME = "chat" - ECHO_PROTOCOL_NAME = "echo" - PROTOCOLS =[CHAT_PROTOCOL_NAME, ECHO_PROTOCOL_NAME] - - # the Rack application - def call env - return [200, {}, ["Hello World"]] unless env["rack.upgrade?"] - protocol = select_protocol(env) - case(protocol) - when CHAT_PROTOCOL_NAME - env["rack.upgrade"] = ChatClient.new - [101, { "Sec-Websocket-Protocol" => protocol }, []] - when ECHO_PROTOCOL_NAME - env["rack.upgrade"] = EchoClient.new - [101, { "Sec-Websocket-Protocol" => protocol }, []] - else - [400, {}, ["Unsupported protocol specified"]] - end - end - - def select_protocol(env) - request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"] - unless request_protocols.nil? - request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String) - request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol } - end # either `nil` or the result of `request_protocols.detect` are returned - end - - # make functions availble as singleton module - extend self -end - -run APP diff --git a/examples/tcp_client.rb b/examples/tcp_client.rb deleted file mode 100644 index 3d7e7ce3..00000000 --- a/examples/tcp_client.rb +++ /dev/null @@ -1,66 +0,0 @@ -#! ruby - -# A raw TCP/IP client example using iodine. -# -# The client will connect to a remote server and send a simple HTTP/1.1 GET request. -# -# Once some data was recieved, a delayed closure and shutdown signal will be sent to iodine. - -# use a secure connection? -USE_TLS = true - -# remote server details -$port = USE_TLS ? 443 : 80 -$address = "google.com" - - -# require iodine -require 'iodine' - -# Iodine runtime settings -Iodine.threads = 1 -Iodine.workers = 1 -Iodine.verbosity = 3 # warnings only - - -# a client callback handler -module Client - - def self.on_open(client) - # Set a connection timeout - client.timeout = 10 - # subscribe to the chat channel. - puts "* Sending request..." - client.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n" - end - - def self.on_message(client, data) - # publish the data we received - STDOUT.write data - # close the client after a second... we're not really parsing anything, so it's a guess. - Iodine.run_after(1000) { client.close } - end - - def self.on_close(client) - # stop iodine - Iodine.stop - puts "Done." - end - - # returns the callback object (self). - def self.call - self - end -end - - - -if(USE_TLS) - tls = Iodine::TLS.new - tls.on_protocol("http/1.1") { Client } -end -# we use can both the `handler` keyword or a block, anything that answers #call. -Iodine.connect(address: $address, port: $port, handler: Client, tls: tls) - -# start the iodine reactor -Iodine.start diff --git a/examples/x-sendfile.ru b/examples/x-sendfile.ru deleted file mode 100644 index bdd3aa9e..00000000 --- a/examples/x-sendfile.ru +++ /dev/null @@ -1,14 +0,0 @@ -app = proc do |env| - request = Rack::Request.new(env) - if request.path_info == '/source'.freeze - [200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []] - elsif request.path_info == '/file'.freeze - [200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)] - else - [200, { 'Content-Type' => 'text/html', - 'Content-Length' => request.path_info.length.to_s }, - [request.path_info]] - end -end - -run app diff --git a/exe/iodine b/exe/iodine index 1edd72d7..795ce012 100755 --- a/exe/iodine +++ b/exe/iodine @@ -1,280 +1,157 @@ #!/usr/bin/env ruby # frozen_string_literal: true -IODINE_PARSE_CLI = true require 'iodine' -# Load Rack if available (assume it will be used). -# -# Remember, code costs memory. Duplicating the Rack::Builder module and functions can be avoided. begin require 'rack' module Iodine - module Base - module Rack - Builder = ::Rack::Builder + # Iodine's {Iodine::Rack} module provides a Rack compliant interface (connecting Iodine to Rack) for an HTTP and Websocket Server. + module RackHandler + # Runs a Rack app, as par the Rack handler requirements. + def self.run(app, options_ = {}) + # nested applications... is that a thing? + Iodine.listen(nil, app) + # start Iodine + Iodine.start + true end - end - end -rescue LoadError - puts "WARNING: The gam `rack` wasn't loaded before iodine started loading the rack app, using custom loader." - module Iodine - module Base - module Rack - # The Rack::Builder code is used when Rack isn't available. - # - # The code was copied (with minor adjustments) from the Rack source code and is licensed under the MIT license. - # Copyright (C) 2007-2019 Leah Neukirchen - # - # ==== - # - # Rack::Builder implements a small DSL to iteratively construct Rack - # applications. - # - # Example: - # - # require 'rack/lobster' - # app = Rack::Builder.new do - # use Rack::CommonLogger - # use Rack::ShowExceptions - # map "/lobster" do - # use Rack::Lint - # run Rack::Lobster.new - # end - # end - # - # run app - # - # Or - # - # app = Rack::Builder.app do - # use Rack::CommonLogger - # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } - # end - # - # run app - # - # +use+ adds middleware to the stack, +run+ dispatches to an application. - # You can use +map+ to construct a Rack::URLMap in a convenient way. - class Builder - # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom - UTF_8_BOM = '\xef\xbb\xbf' - - def self.parse_file(config, opts = Hash.new) - if config =~ /\.ru$/ - return self.load_file(config, opts) - else - require config - app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join('')) - return app, {} - end - end - - def self.load_file(path, options = Hash.new) - cfgfile = ::File.read(path) - cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8 - - cfgfile.sub!(/^__END__\n.*\Z/m, '') - app = new_from_string cfgfile, path - - return app, options - end - - def self.new_from_string(builder_script, file = "(rackup)") - eval "Iodine::Base::Rack::Builder.new {\n" + builder_script + "\n}.to_app", - TOPLEVEL_BINDING, file, 0 - end - - def initialize(default_app = nil, &block) - @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false - instance_eval(&block) if block_given? - end - - def self.app(default_app = nil, &block) - self.new(default_app, &block).to_app - end - # Specifies middleware to use in a stack. - # - # class Middleware - # def initialize(app) - # @app = app - # end - # - # def call(env) - # env["rack.some_header"] = "setting an example" - # @app.call(env) - # end - # end - # - # use Middleware - # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } - # - # All requests through to this application will first be processed by the middleware class. - # The +call+ method in this example sets an additional environment key which then can be - # referenced in the application if required. - def use(middleware, *args, &block) - if @map - mapping, @map = @map, nil - @use << proc { |app| generate_map(app, mapping) } - end - @use << proc { |app| middleware.new(app, *args, &block) } - end - - # Takes an argument that is an object that responds to #call and returns a Rack response. - # The simplest form of this is a lambda object: - # - # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } - # - # However this could also be a class: - # - # class Heartbeat - # def self.call(env) - # [200, { "Content-Type" => "text/plain" }, ["OK"]] - # end - # end - # - # run Heartbeat - def run(app = nil, &block) - raise ArgumentError, "Both app and block given!" if app && block_given? - @run = app || block - end - - # Takes a lambda or block that is used to warm-up the application. - # - # warmup do |app| - # client = Rack::MockRequest.new(app) - # client.get('/') - # end - # - # use SomeMiddleware - # run MyApp - def warmup(prc = nil, &block) - @warmup = prc || block - end - - # Creates a route within the application. - # - # Rack::Builder.app do - # map '/' do - # run Heartbeat - # end - # end - # - # The +use+ method can also be used here to specify middleware to run under a specific path: - # - # Rack::Builder.app do - # map '/' do - # use Middleware - # run Heartbeat - # end - # end - # - # This example includes a piece of middleware which will run before requests hit +Heartbeat+. - # - def map(path, &block) - @map ||= {} - @map[path] = block - end - - # Freeze the app (set using run) and all middleware instances when building the application - # in to_app. - def freeze_app - @freeze_app = true - end - - def to_app - app = @map ? generate_map(@run, @map) : @run - fail "missing run or map statement" unless app - app.freeze if @freeze_app - app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } } - @warmup.call(app) if @warmup - app - end - - def call(env) - to_app.call(env) - end - - private - - def generate_map(default_app, mapping) - mapped = default_app ? { '/' => default_app } : {} - mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app } - URLMap.new(mapped) - end - end + # patches an assumption by Rack, issue #98 code donated by @Shelvak (Néstor Coppi) + def self.shutdown + Iodine.stop end end end + ENV['RACK_HANDLER'] ||= 'iodine' + begin + Iodine::Utils.monkey_patch Rack::Utils + ::Rack::Handler.register('iodine', 'Iodine::RackHandler') if defined?(::Rack::Handler) + ::Rack::Handler.register('Iodine', 'Iodine::RackHandler') if defined?(::Rack::Handler) + rescue StandardError + end +rescue LoadError end module Iodine - # The Iodine::Base namespace is reserved for internal use and is NOT part of the public API. - module Base - # Command line interface. The Ruby CLI might be changed in future versions. - module CLI - - def self.try_file filename - return nil unless File.exist? filename - result = Iodine::Base::Rack::Builder.parse_file filename - return result[0] if(result.is_a?(Array)) - return result + class Base + class NeoRackUp + module MiddleWare_Pre + def initialize(*args, &blk); @app = args[0]; super; end + private + def method_missing(method_name, *arguments) + m = self.define_singleton_method(method_name) {|*args| @app.send(method_name, *args) } + m.call(*arguments) + end end - - def self.get_app_opts - app, opt = nil, Hash.new - filename = Iodine::DEFAULT_SETTINGS[:filename_] - if filename - app = try_file(filename) - app = try_file("#{filename}.ru") unless app - unless app - puts "* Couldn't find #{filename}\n testing for config.ru\n" - app = try_file "config.ru" - end - else - app = try_file "config.ru"; + module MiddleWare_Def + def on_http(e); @app.on_http(e); end + def on_finish(e); @app.on_finish(e); end + def call(env); @app.call(env); end + def on_authenticate(e); @app.on_authenticate(e); end + def on_authenticate_sse(e); @app.on_authenticate_sse(e); end + def on_authenticate_websocket(e); @app.on_authenticate_websocket(e); end + def on_open(e); @app.on_open(e); end + def on_message(e, data); @app.on_message(e, data); end + def on_drained(e); @app.on_drained(e); end + def on_timeout(e); @app.on_timeout(e); end + def on_shutdown(e); @app.on_shutdown(e); end + def on_close(e); @app.on_close(e); end + def on_eventsource(e, message); @app.on_eventsource(e, message); end + def on_eventsource_reconnect(e,id); @app.on_eventsource_reconnect(e,id); end + end + UTF_8_BOM = '\xef\xbb\xbf' + def use(mw, *args, &block) + @middleware ||= [] + mw.prepend ::Iodine::Base::NeoRackUp::MiddleWare_Pre + mw.include ::Iodine::Base::NeoRackUp::MiddleWare_Def + @middleware <<[mw, args, block] + end + def run(a, &block) + a = block if(!a) + raise "Missing either an application handler or a block for the `run` instruction." unless a + raise "Missing both `on_http(e)` and `call(env)` in provided App." unless (a.respond_to?(:on_http) || a.respond_to?(:call)) + Iodine::Base.add_missing_handlar_methods(a) + while(@middleware && !@middleware.empty?) + m = @middleware.pop + a = m[0].new(a, *m[1], &m[2]) end - - unless app - puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}." - if Iodine::DEFAULT_SETTINGS[:public] - puts " Running only static file service." - app = Proc.new { [404, {}, "Not Found!"] } - else - puts "\nERROR: Couldn't run Ruby application, check command line arguments." - ARGV << "-?" - Iodine::Base::CLI.parse - exit(0); - end + @app = a + end + def app + @app + end + # validate name of Web Application configuration file. + def self.iodine___detect_filename + file_name = Iodine::Base::CLI.parse(true)[0]; + if(!file_name || File.directory?(file_name)) + file_name ||= './' + file_name += '/' unless file_name.end_with?('/') + defaults = ["#{file_name}config.ru", "#{file_name}config.nru"] + file_name = nil; + defaults.each {|n| file_name = n if(File.exist?(n))} + file_name ||= defaults[-1] end - return app, opt + file_name = nil if(!File.exist?(file_name)) + raise("Web App not found: config.ru") unless file_name || Iodine::Base::CLI['-www'] + file_name end - - def self.perform_warmup(app) - # load anything marked with `autoload`, since autoload is niether thread safe nor fork friendly. - Iodine.on_state(:on_start) do - Module.constants.each do |n| - begin - Object.const_get(n) - rescue StandardError => _e - end - end - Iodine::Base::Rack::Builder.new(app) do |r| - r.warmup do |a| - client = ::Rack::MockRequest.new(a) - client.get('/') - end - end + def self.iodine___load_rack fn + return nil unless (File.extname(fn) == '.ru') + begin + require 'rack' + rescue LoadError => e + return nil end + build = Rack::Builder.parse_file(fn) + build end - - def self.call - app, opt = get_app_opts - perform_warmup(app) if Iodine::DEFAULT_SETTINGS[:warmup_] - Iodine::Rack.run(app, opt) + def self.iodine___load_neorack fn + return nil unless fn + cfgfile = ::File.read(fn) + cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8 + cfgfile.sub!(/^__END__\n.*\Z/m, '') + builder = self.new + builder.instance_eval(cfgfile, fn, 0) + builder.app + end + def self.iodine___load_config + fn = ::Iodine::Base::NeoRackUp.iodine___detect_filename + app = ::Iodine::Base::NeoRackUp.iodine___load_rack(fn) + app ||= ::Iodine::Base::NeoRackUp.iodine___load_neorack(fn) + raise("Web App not found (no `run`?) in #{fn}") unless !fn || app + # read and execute configuration file using NeoRack DSL. + Iodine.listen nil, app + Iodine.start end end end end -Iodine::Base::CLI.call +### Load configuration file +if Iodine::Base::CLI['--config'] + p Iodine::Base::CLI['--config'] + require Iodine::Base::CLI['--config'] +end +### .pid file +if Iodine::Base::CLI['-pid'] + pid_filename = Iodine::Base::CLI['-pid'] + pid_filename << "iodine.pid" if(pid_filename[-1] == '/') + if File.exist?(pid_filename) + raise "pid filename shold point to a valid file name (not a folder)!" if(!File.file?(pid_filename)) + raise "pid filename error! (doesn't end with .pid)" if(File.extname(pid_filename) != '.pid') + File.delete(pid_filename) + end + Iodine.on_state(:pre_start) do + IO.binwrite(pid_filename, "#{Process.pid}\r\n") + end + Iodine.on_state(:on_finish) do + File.delete(pid_filename) + end +end +### TODO: Warmup +if Iodine::Base::CLI['-warmup'] +end + +Iodine::Base::NeoRackUp.iodine___load_config + + diff --git a/ext/iodine/extconf.rb b/ext/iodine/extconf.rb index e7d0417c..2294fd1a 100644 --- a/ext/iodine/extconf.rb +++ b/ext/iodine/extconf.rb @@ -1,110 +1,21 @@ -require 'mkmf' -require 'fileutils' +require "mkmf" -# Test polling -def iodine_test_polling_support - iodine_poll_test_kqueue = < -\#include -int main(void) { - int fd = kqueue(); -} -EOS +dir_config("openssl") - iodine_poll_test_epoll = < -\#include -\#include -\#include -\#include -\#include -int main(void) { - int fd = epoll_create1(EPOLL_CLOEXEC); -} -EOS - - iodine_poll_test_poll = < -\#include -int main(void) { - struct pollfd plist[18]; - memset(plist, 0, sizeof(plist[0]) * 18); - poll(plist, 1, 1); -} -EOS - - # Test for manual selection and then TRY_COMPILE with each polling engine - if Gem.win_platform? - puts "skipping polling tests, using WSAPOLL on Windows" - $defs << "-DFIO_ENGINE_WSAPOLL" - elsif ENV['FIO_POLL'] - puts "skipping polling tests, enforcing manual selection of: poll" - $defs << "-DFIO_ENGINE_POLL" - elsif ENV['FIO_FORCE_POLL'] - puts "skipping polling tests, enforcing manual selection of: poll" - $defs << "-DFIO_ENGINE_POLL" - elsif ENV['FIO_FORCE_EPOLL'] - puts "skipping polling tests, enforcing manual selection of: epoll" - $defs << "-DFIO_ENGINE_EPOLL" - elsif ENV['FIO_FORCE_KQUEUE'] - puts "* Skipping polling tests, enforcing manual selection of: kqueue" - $defs << "-DFIO_ENGINE_KQUEUE" - elsif try_compile(iodine_poll_test_epoll) - puts "detected `epoll`" - $defs << "-DFIO_ENGINE_EPOLL" - elsif try_compile(iodine_poll_test_kqueue) - puts "detected `kqueue`" - $defs << "-DFIO_ENGINE_KQUEUE" - elsif try_compile(iodine_poll_test_poll) - puts "detected `poll` - this is suboptimal fallback!" - $defs << "-DFIO_ENGINE_POLL" - else - puts "* WARNING: No supported polling engine! expecting compilation to fail." +if have_library('crypto') && have_library('ssl') + begin + require 'openssl' + if(OpenSSL::VERSION.to_i > 2) + puts "* Detected OpenSSL version > 3 (#{OpenSSL::VERSION}), setting the HAVE_OPENSSL flag." + $defs << "-DHAVE_OPENSSL" + end + rescue LoadError end end -iodine_test_polling_support() - -unless Gem.win_platform? - # Test for OpenSSL version equal to 1.0.0 or greater. - unless ENV['NO_SSL'] || ENV['NO_TLS'] || ENV["DISABLE_SSL"] - OPENSSL_TEST_CODE = < -\#include -\#include -\#if OPENSSL_VERSION_NUMBER < 0x10100000L -\#error "OpenSSL version too small" -\#endif -int main(void) { - SSL_library_init(); - SSL_CTX *ctx = SSL_CTX_new(TLS_method()); - SSL *ssl = SSL_new(ctx); - BIO *bio = BIO_new_socket(3, 0); - BIO_up_ref(bio); - SSL_set0_rbio(ssl, bio); - SSL_set0_wbio(ssl, bio); -} -EOS +$defs << "-DDEBUG" if ENV["DEBUG"] - dir_config("openssl") - begin - require 'openssl' - rescue LoadError - else - if have_library('crypto') && have_library('ssl') - puts "detected OpenSSL library, testing for version and required functions." - if try_compile(OPENSSL_TEST_CODE) - $defs << "-DHAVE_OPENSSL" - puts "confirmed OpenSSL to be version 1.1.0 or above (#{OpenSSL::OPENSSL_LIBRARY_VERSION})...\n* compiling with HAVE_OPENSSL." - else - puts "FAILED: OpenSSL version not supported (#{OpenSSL::OPENSSL_LIBRARY_VERSION} is too old)." - end - end - end - end -end +append_cflags("-Wno-undef"); +append_cflags("-Wno-missing-noreturn"); -create_makefile 'iodine/iodine_ext' +create_makefile "iodine/iodine" diff --git a/ext/iodine/fio-stl.h b/ext/iodine/fio-stl.h new file mode 100644 index 00000000..63ae7fbf --- /dev/null +++ b/ext/iodine/fio-stl.h @@ -0,0 +1,55726 @@ +/* ***************************************************************************** +Copyright: Boaz Segev, 2019-2024 +License: ISC / MIT (choose your license) + +Feel free to copy, use and enjoy according to the license provided. +******************************************************************************** + +******************************************************************************** + + + THE fio-stl.h FILE IS AUTO-GENERATED, DO NOT EDIT + + https://github.com/facil-io/cstl + + +***************************************************************************** */ + +/* ***************************************************************************** + +**Notes**: + +- The functions defined using this file default to `static` or `static + inline`. + + To create an externally visible API, define the `FIO_EXTERN`. Define the + `FIO_EXTERN_COMPLETE` macro to include the API's implementation as well. + +- Documentation can be found at https://github.com/facil-io/cstl where you will + be able to post PRs or submit issues. + +***************************************************************************** */ +#ifndef H___FIO_CSTL_COMBINED___H +#define H___FIO_CSTL_COMBINED___H +#endif /* H___FIO_CSTL_COMBINED___H */ +#ifndef FIO_INCLUDE_FILE +#define FIO_INCLUDE_FILE "fio-stl.h" +#endif +/* ***************************************************************************** + + + + + Core Header - Stuff required by everything + + +Note: + +The core header can't be well ordered due to cascading dependencies. +Please refer to the core documentation in the Markdown File. +***************************************************************************** */ +#ifndef H___FIO_CORE___H +#define H___FIO_CORE___H + +/** An empty macro, adding white space. Used to avoid function like macros. */ +#define FIO_NOOP + +/* ***************************************************************************** +Version Macros + +The facil.io C STL library follows [semantic versioning](https://semver.org) and +supports macros that will help detect and validate it's version. +***************************************************************************** */ + +/** MAJOR version: API/ABI breaking changes. */ +#define FIO_VERSION_MAJOR 0 +/** MINOR version: Deprecation, or significant features added. May break ABI. */ +#define FIO_VERSION_MINOR 8 +/** PATCH version: Bug fixes, minor features may be added. */ +#define FIO_VERSION_PATCH 0 +/** Build version: optional build info (string), i.e. "beta.02" */ +#define FIO_VERSION_BUILD "alpha.10" + +#ifdef FIO_VERSION_BUILD +/** Version as a String literal (MACRO). */ +#define FIO_VERSION_STRING \ + FIO_MACRO2STR(FIO_VERSION_MAJOR) \ + "." FIO_MACRO2STR(FIO_VERSION_MINOR) "." FIO_MACRO2STR( \ + FIO_VERSION_PATCH) "-" FIO_VERSION_BUILD +#else +/** Version as a String literal (MACRO). */ +#define FIO_VERSION_STRING \ + FIO_MACRO2STR(FIO_VERSION_MAJOR) \ + "." FIO_MACRO2STR(FIO_VERSION_MINOR) "." FIO_MACRO2STR(FIO_VERSION_PATCH) +#define FIO_VERSION_BUILD "" +#endif + +/* ***************************************************************************** +Settings - Behavioral defaults +***************************************************************************** */ + +#ifndef FIO_USE_THREAD_MUTEX +/** Selects between facio.io's spinlocks (false) and OS mutexes (true) */ +#define FIO_USE_THREAD_MUTEX 0 +#endif + +#ifndef FIO_UNALIGNED_ACCESS +/** Allows facil.io to attempt unaligned memory access on *some* CPU systems. */ +#define FIO_UNALIGNED_ACCESS 1 +#endif + +#ifndef FIO_LIMIT_INTRINSIC_BUFFER +/* limits register consumption on some pseudo-intrinsics, using more loops */ +#define FIO_LIMIT_INTRINSIC_BUFFER 1 +#endif + +#ifndef FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT +/* Memory allocations should be secure by default (facil.io allocators only) */ +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT 1 +#endif + +#ifndef FIO_MEM_PAGE_SIZE_LOG +#define FIO_MEM_PAGE_SIZE_LOG 12 /* assumes 4096 bytes per page */ +#endif + +#if defined(FIO_NO_LOG) && defined(FIO_LEAK_COUNTER) +#error FIO_NO_LOG and FIO_LEAK_COUNTER are exclusive, as memory leaks print to log. +#endif + +/* ***************************************************************************** +C++ extern start +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +extern "C" { +/* C++ keyword was deprecated */ +#ifndef register +#define register +#endif +/* C keyword - unavailable in C++ */ +#ifndef restrict +#define restrict +#endif +/* C keyword - unavailable in C++ */ +#ifndef _Bool +#define _Bool bool +#endif + +#endif + +/* ***************************************************************************** +Compiler detection, GCC / CLang features and OS dependent included files +***************************************************************************** */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#if !defined(__GNUC__) && !defined(__clang__) && !defined(GNUC_BYPASS) +#ifndef __attribute__ +#define __attribute__(...) +#endif +#ifndef __has_include +#define __has_include(...) 0 +#endif +#ifndef __has_builtin +#define __has_builtin(...) 0 +#endif +#ifndef __has_attribute +#define __has_attribute(...) 0 +#endif +#define GNUC_BYPASS 1 +#elif !defined(__clang__) && !defined(__has_builtin) +/* E.g: GCC < 6.0 doesn't support __has_builtin */ +#define __has_builtin(...) 0 +#define GNUC_BYPASS 1 +#endif + +#ifndef __has_include +#define __has_include(...) 0 +#define GNUC_BYPASS 1 +#endif + +/* ***************************************************************************** +Compiler Helpers - Deprecation, Alignment, Inlining, Memory Barriers +***************************************************************************** */ + +#ifndef DEPRECATED +#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 5)) +/* GCC < 4.5 doesn't support deprecation reason string */ +#define DEPRECATED(reason) __attribute__((deprecated)) +#else +#define DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define FIO_ALIGN(bytes) __attribute__((aligned(bytes))) +#elif defined(__INTEL_COMPILER) || defined(_MSC_VER) +#define FIO_ALIGN(bytes) +// #define FIO_ALIGN(bytes) __declspec(align(bytes)) +#else +#define FIO_ALIGN(bytes) +#endif + +#if _MSC_VER + +#undef _CRT_SECURE_NO_WARNINGS +/** We define this because Microsoft's naming scheme isn't portable */ +#define _CRT_SECURE_NO_WARNINGS 1 + +#define inline __inline +#define __thread __declspec(thread) +#elif !defined(__clang__) && !defined(__GNUC__) +#define __thread _Thread_local +#endif + +#if defined(__clang__) || defined(__GNUC__) +/** Clobber CPU registers and prevent compiler reordering optimizations. */ +#define FIO_COMPILER_GUARD __asm__ volatile("" ::: "memory") +#define FIO_COMPILER_GUARD_INSTRUCTION __asm__ volatile("" :::) +#elif defined(_MSC_VER) +#include +/** Clobber CPU registers and prevent compiler reordering optimizations. */ +#define FIO_COMPILER_GUARD _ReadWriteBarrier() +#define FIO_COMPILER_GUARD_INSTRUCTION _WriteBarrier() +#pragma message("Warning: Windows deprecated it's low-level C memory barrier.") +#else +#warning Unknown OS / compiler, some macros are poorly defined and errors might occur. +#define FIO_COMPILER_GUARD asm volatile("" ::: "memory") +#define FIO_COMPILER_GUARD_INSTRUCTION asm volatile("" :::) +#endif + +/* ***************************************************************************** +Address Sanitizer Detection +***************************************************************************** */ + +/* Address Sanitizer Detection */ +#if defined(__SANITIZE_ADDRESS__) +#define FIO___ASAN_DETECTED 1 +#elif defined(__has_feature) +#if __has_feature(address_sanitizer) +#define FIO___ASAN_DETECTED 1 +#endif +#endif /* address_sanitizer */ + +#ifdef FIO___ASAN_DETECTED +#if defined(_MSC_VER) +#define FIO___ASAN_AVOID __declspec(no_sanitize_address) +#else +#define FIO___ASAN_AVOID \ + __attribute__((no_sanitize_address)) __attribute__((no_sanitize("address"))) +#endif +#else +#define FIO___ASAN_AVOID +#endif + +/* ***************************************************************************** +Intrinsic Availability Flags +***************************************************************************** */ +#if !defined(DEBUG) /* Allow Intrinsic / SIMD / Neon ? */ +#if defined(__ARM_FEATURE_CRYPTO) && \ + (defined(__ARM_NEON) || defined(__ARM_NEON__)) && \ + __has_include("arm_acle.h") && __has_include("arm_neon.h") +#include +#include +#define FIO___HAS_ARM_INTRIN 1 +#elif defined(__x86_64) && __has_include("immintrin.h") /* x64 Intrinsics? */ +#define FIO___HAS_X86_INTRIN 1 +#include +#endif + +#endif + +/* ***************************************************************************** +Aligned Memory Access Selectors +***************************************************************************** */ + +#ifndef FIO_UNALIGNED_MEMORY_ACCESS_ENABLED +#if FIO_UNALIGNED_ACCESS && \ + (__amd64 || __amd64__ || __x86_64 || __x86_64__ || __i386 || \ + __aarch64__ || _M_IX86 || _M_X64 || _M_ARM64 || __ARM_FEATURE_UNALIGNED) +/** True when unaligned memory is allowed. */ +#define FIO_UNALIGNED_MEMORY_ACCESS_ENABLED 1 +#else +#define FIO_UNALIGNED_MEMORY_ACCESS_ENABLED 0 +#endif +#endif /* FIO_UNALIGNED_MEMORY_ACCESS_ENABLED */ + +/* ***************************************************************************** +OS Specific includes and Macros +***************************************************************************** */ + +#if defined(__unix__) || defined(__linux__) || defined(__APPLE__) +#define FIO_HAVE_UNIX_TOOLS 1 +#define FIO_OS_POSIX 1 +#define FIO___KILL_SELF() kill(0, SIGINT) +#define fio_getpid getpid + +#elif defined(_WIN32) || defined(_WIN64) || defined(WIN32) || \ + defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) +#define FIO_OS_WIN 1 +#define POSIX_C_SOURCE 200809L +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#undef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#undef _CRT_NONSTDC_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS 1 +#ifndef UNICODE +#define UNICODE 1 +#endif +#include +#endif /* WIN32_LEAN_AND_MEAN */ + +#include +#include +#include +#include + +#include +#include +#include +#include /* struct timeval is here... why? Microsoft. */ + +#define fio_getpid _getpid + +#define FIO___KILL_SELF() TerminateProcess(GetCurrentProcess(), 1) + +#if defined(__MINGW32__) +/* Mingw supports */ +#define FIO_HAVE_UNIX_TOOLS 2 +#define __USE_MINGW_ANSI_STDIO 1 +#define FIO___PRINTF_STYLE(string_index, check_index) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, check_index))) +#elif defined(__CYGWIN__) +/* TODO: cygwin support */ +#define FIO_HAVE_UNIX_TOOLS 3 +#define __USE_MINGW_ANSI_STDIO 1 +#define FIO___PRINTF_STYLE(string_index, check_index) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, check_index))) +#else +#define FIO_HAVE_UNIX_TOOLS 0 +typedef SSIZE_T ssize_t; +#endif /* __CYGWIN__ __MINGW32__ */ + +#if _MSC_VER +#pragma message("Warning: (Windows) some functionality enabled by patchwork.") +#else +#warning some functionality is enabled by patchwork. +#endif + +#else +#define FIO_HAVE_UNIX_TOOLS 0 +#warning Unknown OS / compiler, some macros are poorly defined and errors might occur. +#endif /* OS / Compiler detection */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 0 +#endif + +#if FIO_HAVE_UNIX_TOOLS +#include +#include +#include +#endif + +/* assume GCC / Clang style if no specific style provided. */ +#ifndef FIO___PRINTF_STYLE +#define FIO___PRINTF_STYLE(string_index, check_index) \ + __attribute__((format(printf, string_index, check_index))) +#endif + +/* ***************************************************************************** +Function Attributes +***************************************************************************** */ + +#ifndef FIO_SFUNC +/** Marks a function as `static` and possibly unused. */ +#define FIO_SFUNC static __attribute__((unused)) +#endif + +#ifndef FIO_IFUNC +/** Marks a function as `static`, `inline` and possibly unused. */ +#define FIO_IFUNC FIO_SFUNC inline +#endif + +#ifndef FIO_MIFN +#define FIO_MIFN FIO_IFUNC __attribute__((warn_unused_result)) +#endif + +#ifndef FIO_WEAK +/** Marks a function as weak */ +#define FIO_WEAK __attribute__((weak)) +#endif + +#ifndef FIO_IFUNC_DEF_GETSET +/** Defines a "get" function for a field within a struct / union. */ +#define FIO_IFUNC_DEF_GET(namespace, T_type, F_type, field_name) \ + /** Returns current value of property within the struct / union. */ \ + FIO_IFUNC F_type FIO_NAME(namespace, field_name)(T_type * o) { \ + FIO_ASSERT_DEBUG(o, "NULL " FIO_MACRO2STR(namespace) " pointer @ `get`!"); \ + return o->field_name; \ + } + +/** Defines a "set" function for a field within a struct / union. */ +#define FIO_IFUNC_DEF_SET(namespace, T_type, F_type, field_name) \ + /** Sets a new value, returning the old one */ \ + FIO_IFUNC F_type FIO_NAME(FIO_NAME(namespace, field_name), \ + set)(T_type * o, F_type new_value) { \ + FIO_ASSERT_DEBUG(o, "NULL " FIO_MACRO2STR(namespace) " pointer @ `set`!"); \ + F_type old_value = o->field_name; \ + o->field_name = new_value; \ + return old_value; \ + } + +/** Defines get/set functions for a field within a struct / union. */ +#define FIO_IFUNC_DEF_GETSET(namespace, T_type, F_type, field_name) \ + FIO_IFUNC_DEF_GET(namespace, T_type, F_type, field_name) \ + FIO_IFUNC_DEF_SET(namespace, T_type, F_type, field_name) + +#endif /* FIO_IFUNC_DEF_GETSET */ +/* ***************************************************************************** +Constructors and Destructors +***************************************************************************** */ + +#if _MSC_VER + +#define FIO___COUNTER_RUNNER() \ + __COUNTER__ + __COUNTER__ + __COUNTER__ + __COUNTER__ + __COUNTER__ + \ + __COUNTER__ + __COUNTER__ + __COUNTER__ + __COUNTER__ + __COUNTER__ +/* counter is used for ordering, so we need a consistent number of digits */ +FIO_SFUNC int fio___msv_run_counter_macro_to_3_digits(void) { + return FIO___COUNTER_RUNNER() + FIO___COUNTER_RUNNER() + + FIO___COUNTER_RUNNER() + FIO___COUNTER_RUNNER() + + FIO___COUNTER_RUNNER() + FIO___COUNTER_RUNNER() + + FIO___COUNTER_RUNNER() + FIO___COUNTER_RUNNER() + + FIO___COUNTER_RUNNER() + FIO___COUNTER_RUNNER(); +} +#undef FIO___COUNTER_RUNNER + +#pragma section(".CRT$XCU", read) +/** Marks a function as a constructor - if supported. */ +#if _WIN64 /* MSVC linker uses different name mangling on 32bit systems */ +/* clang-format off */ +#define FIO_CONSTRUCTOR(fname) \ + static void fname(void); \ + __declspec(allocate(".CRT$XCU")) void (*FIO_NAME(fio___constructor, __COUNTER__))(void) = fname; \ + static void fname(void) +#else +#define FIO_CONSTRUCTOR(fname) \ + static void fname(void); \ + __declspec(allocate(".CRT$XCU")) void (*FIO_NAME(fio___constructor, __COUNTER__))(void) = fname; \ + static void fname(void) +#endif /* _WIN64 */ +#define FIO_DESTRUCTOR(fname) \ + static void fname(void); \ + FIO_CONSTRUCTOR(fname##__hook) { atexit(fname); } \ + static void fname(void) +/* clang-format on */ + +#else +/** Marks a function as a constructor - if supported. */ +#define FIO_CONSTRUCTOR(fname) \ + static __attribute__((constructor)) void fname(void) +/** Marks a function as a destructor - if supported. Consider using atexit() */ +#define FIO_DESTRUCTOR(fname) static __attribute__((destructor)) void name(void) +#endif + +/* ***************************************************************************** +Conditional Likelihood +***************************************************************************** */ +#if defined(__clang__) || defined(__GNUC__) +#define FIO_LIKELY(cond) __builtin_expect((cond), 1) +#define FIO_UNLIKELY(cond) __builtin_expect((cond), 0) +#else +#define FIO_LIKELY(cond) (cond) +#define FIO_UNLIKELY(cond) (cond) +#endif + +/* ***************************************************************************** +Macro Stringifier +***************************************************************************** */ +#ifndef FIO_MACRO2STR +#define FIO_MACRO2STR_STEP2(macro) #macro +/** Converts a macro's content to a string literal. */ +#define FIO_MACRO2STR(macro) FIO_MACRO2STR_STEP2(macro) +#endif + +/* ***************************************************************************** +Naming Macros +***************************************************************************** */ +/* Used for naming functions and types */ +#define FIO_NAME_FROM_MACRO_STEP2(prefix, postfix, div) prefix##div##postfix +#define FIO_NAME_FROM_MACRO_STEP1(prefix, postfix, div) \ + FIO_NAME_FROM_MACRO_STEP2(prefix, postfix, div) + +/** Used for naming functions and variables resulting in: prefix_postfix */ +#define FIO_NAME(prefix, postfix) FIO_NAME_FROM_MACRO_STEP1(prefix, postfix, _) + +/** Sets naming convention for conversion functions, i.e.: foo2bar */ +#define FIO_NAME2(prefix, postfix) FIO_NAME_FROM_MACRO_STEP1(prefix, postfix, 2) + +/** Sets naming convention for boolean testing functions, i.e.: foo_is_true */ +#define FIO_NAME_BL(prefix, postfix) \ + FIO_NAME_FROM_MACRO_STEP1(prefix, postfix, _is_) + +/** Used internally to name test functions. */ +#define FIO_NAME_TEST(prefix, postfix) \ + FIO_NAME(fio___test, FIO_NAME(prefix, postfix)) + +/* ***************************************************************************** +Pointer Math +***************************************************************************** */ + +/** Masks a pointer's left-most bits, returning the right bits. */ +#define FIO_PTR_MATH_LMASK(T_type, ptr, bits) \ + ((T_type *)(((uintptr_t)(ptr)) & (((uintptr_t)1ULL << (bits)) - 1))) + +/** Masks a pointer's right-most bits, returning the left bits. */ +#define FIO_PTR_MATH_RMASK(T_type, ptr, bits) \ + ((T_type *)(((uintptr_t)(ptr)) & ((~(uintptr_t)0ULL) << (bits)))) + +/** Add offset bytes to pointer, updating the pointer's type. */ +#define FIO_PTR_MATH_ADD(T_type, ptr, offset) \ + ((T_type *)((uintptr_t)(ptr) + (uintptr_t)(offset))) + +/** Subtract X bytes from pointer, updating the pointer's type. */ +#define FIO_PTR_MATH_SUB(T_type, ptr, offset) \ + ((T_type *)((uintptr_t)(ptr) - (uintptr_t)(offset))) + +/** Find the root object (of a struct) from it's field (with sanitizer fix). */ +#define FIO_PTR_FROM_FIELD(T_type, field, ptr) \ + FIO_PTR_MATH_SUB(T_type, \ + ptr, \ + (uintptr_t)(&((T_type *)0xFF00)->field) - 0xFF00) + +/* ***************************************************************************** +Sleep / Thread Scheduling Macros +***************************************************************************** */ + +#ifndef FIO_THREAD_WAIT +#if FIO_OS_WIN +/** Calls NtDelayExecution with the requested nano-second count. */ +#define FIO_THREAD_WAIT(nano_sec) \ + do { \ + Sleep(((nano_sec) / 1000000) ? ((nano_sec) / 1000000) : 1); \ + } while (0) +// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep + +#elif FIO_OS_POSIX +/** Calls nanonsleep with the requested nano-second count. */ +#define FIO_THREAD_WAIT(nano_sec) \ + do { \ + const struct timespec tm = {.tv_sec = (time_t)((nano_sec) / 1000000000), \ + .tv_nsec = ((long)(nano_sec) % 1000000000)}; \ + nanosleep(&tm, (struct timespec *)NULL); \ + } while (0) + +#endif +#endif + +#ifndef FIO_THREAD_RESCHEDULE +#if (defined(__x86_64__) || defined(__i386__)) && \ + (defined(__GNUC__) || defined(__clang__)) +/** Yields the thread, hinting to the processor about spinlock loop. */ +#define FIO_THREAD_YIELD() __asm__ __volatile__("pause" ::: "memory") +#elif (defined(__aarch64__) || defined(__arm__)) && \ + (defined(__GNUC__) || defined(__clang__)) +/** Yields the thread, hinting to the processor about spinlock loop. */ +#define FIO_THREAD_YIELD() __asm__ __volatile__("yield" ::: "memory") +#elif defined(_MSC_VER) +#define FIO_THREAD_YIELD() YieldProcessor() +#else /* FIO_OS_POSIX */ +/** Yields the thread, hinting to the processor about spinlock loop. */ +#define FIO_THREAD_YIELD() sched_yield() +#endif + +/** + * Reschedules the thread by calling nanosleeps for nano-seconds. + * + * In practice, the thread will probably sleep for 60ns or more. + * + * Seems to be faster then thread_yield, perhaps it prevents de-prioritization + * of the thread. + */ +#define FIO_THREAD_RESCHEDULE() FIO_THREAD_WAIT(4) + +#endif /* FIO_THREAD_RESCHEDULE */ + +/* ***************************************************************************** + + + + + Atomic Operations + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ + +/* C11 Atomics are defined? */ +#if defined(__ATOMIC_RELAXED) +/** An atomic load operation, returns value in pointer. */ +#define fio_atomic_load(dest, p_obj) \ + do { \ + dest = __atomic_load_n((p_obj), __ATOMIC_SEQ_CST); \ + } while (0) + +// clang-format off + +/** An atomic compare and exchange operation, returns true if an exchange occured. `p_expected` MAY be overwritten with the existing value (system specific). */ +#define fio_atomic_compare_exchange_p(p_obj, p_expected, p_desired) __atomic_compare_exchange((p_obj), (p_expected), (p_desired), 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +/** An atomic exchange operation, returns previous value */ +#define fio_atomic_exchange(p_obj, value) __atomic_exchange_n((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic addition operation, returns previous value */ +#define fio_atomic_add(p_obj, value) __atomic_fetch_add((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic subtraction operation, returns previous value */ +#define fio_atomic_sub(p_obj, value) __atomic_fetch_sub((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic AND (&) operation, returns previous value */ +#define fio_atomic_and(p_obj, value) __atomic_fetch_and((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic XOR (^) operation, returns previous value */ +#define fio_atomic_xor(p_obj, value) __atomic_fetch_xor((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic OR (|) operation, returns previous value */ +#define fio_atomic_or(p_obj, value) __atomic_fetch_or((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic NOT AND ((~)&) operation, returns previous value */ +#define fio_atomic_nand(p_obj, value) __atomic_fetch_nand((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic addition operation, returns new value */ +#define fio_atomic_add_fetch(p_obj, value) __atomic_add_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic subtraction operation, returns new value */ +#define fio_atomic_sub_fetch(p_obj, value) __atomic_sub_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic AND (&) operation, returns new value */ +#define fio_atomic_and_fetch(p_obj, value) __atomic_and_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic XOR (^) operation, returns new value */ +#define fio_atomic_xor_fetch(p_obj, value) __atomic_xor_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic OR (|) operation, returns new value */ +#define fio_atomic_or_fetch(p_obj, value) __atomic_or_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/** An atomic NOT AND ((~)&) operation, returns new value */ +#define fio_atomic_nand_fetch(p_obj, value) __atomic_nand_fetch((p_obj), (value), __ATOMIC_SEQ_CST) +/* note: __ATOMIC_SEQ_CST may be safer and __ATOMIC_ACQ_REL may be faster */ + +/* Select the correct compiler builtin method. */ +#elif __has_builtin(__sync_add_and_fetch) || (__GNUC__ > 3) /* Atomic Implementation Selector */ +/** An atomic load operation, returns value in pointer. */ +#define fio_atomic_load(dest, p_obj) \ + do { \ + dest = *(p_obj); \ + } while (!__sync_bool_compare_and_swap((p_obj), dest, dest)) + + +/** An atomic compare and exchange operation, returns true if an exchange occured. `p_expected` MAY be overwritten with the existing value (system specific). */ +#define fio_atomic_compare_exchange_p(p_obj, p_expected, p_desired) __sync_bool_compare_and_swap((p_obj), (p_expected), *(p_desired)) +/** An atomic exchange operation, ruturns previous value */ +#define fio_atomic_exchange(p_obj, value) __sync_val_compare_and_swap((p_obj), *(p_obj), (value)) +/** An atomic addition operation, returns new value */ +#define fio_atomic_add(p_obj, value) __sync_fetch_and_add((p_obj), (value)) +/** An atomic subtraction operation, returns new value */ +#define fio_atomic_sub(p_obj, value) __sync_fetch_and_sub((p_obj), (value)) +/** An atomic AND (&) operation, returns new value */ +#define fio_atomic_and(p_obj, value) __sync_fetch_and_and((p_obj), (value)) +/** An atomic XOR (^) operation, returns new value */ +#define fio_atomic_xor(p_obj, value) __sync_fetch_and_xor((p_obj), (value)) +/** An atomic OR (|) operation, returns new value */ +#define fio_atomic_or(p_obj, value) __sync_fetch_and_or((p_obj), (value)) +/** An atomic NOT AND ((~)&) operation, returns new value */ +#define fio_atomic_nand(p_obj, value) __sync_fetch_and_nand((p_obj), (value)) +/** An atomic addition operation, returns previous value */ +#define fio_atomic_add_fetch(p_obj, value) __sync_add_and_fetch((p_obj), (value)) +/** An atomic subtraction operation, returns previous value */ +#define fio_atomic_sub_fetch(p_obj, value) __sync_sub_and_fetch((p_obj), (value)) +/** An atomic AND (&) operation, returns previous value */ +#define fio_atomic_and_fetch(p_obj, value) __sync_and_and_fetch((p_obj), (value)) +/** An atomic XOR (^) operation, returns previous value */ +#define fio_atomic_xor_fetch(p_obj, value) __sync_xor_and_fetch((p_obj), (value)) +/** An atomic OR (|) operation, returns previous value */ +#define fio_atomic_or_fetch(p_obj, value) __sync_or_and_fetch((p_obj), (value)) +/** An atomic NOT AND ((~)&) operation, returns previous value */ +#define fio_atomic_nand_fetch(p_obj, value) __sync_nand_and_fetch((p_obj), (value)) + + +/* Atomic Implementation Selector */ +#elif __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) +#include +#ifdef _MSC_VER +#pragma message ("Fallback to C11 atomic header, might be missing some features.") +#undef FIO_COMPILER_GUARD +#define FIO_COMPILER_GUARD atomic_thread_fence(memory_order_seq_cst) +#else +#warning Fallback to C11 atomic header, might be missing some features. +#endif /* _MSC_VER */ +/** An atomic load operation, returns value in pointer. */ +#define fio_atomic_load(dest, p_obj) (dest = atomic_load(p_obj)) + +/** An atomic compare and exchange operation, returns true if an exchange occured. `p_expected` MAY be overwritten with the existing value (system specific). */ +#define fio_atomic_compare_exchange_p(p_obj, p_expected, p_desired) atomic_compare_exchange_strong((p_obj), (p_expected), (p_desired)) +/** An atomic exchange operation, returns previous value */ +#define fio_atomic_exchange(p_obj, value) atomic_exchange((p_obj), (value)) +/** An atomic addition operation, returns previous value */ +#define fio_atomic_add(p_obj, value) atomic_fetch_add((p_obj), (value)) +/** An atomic subtraction operation, returns previous value */ +#define fio_atomic_sub(p_obj, value) atomic_fetch_sub((p_obj), (value)) +/** An atomic AND (&) operation, returns previous value */ +#define fio_atomic_and(p_obj, value) atomic_fetch_and((p_obj), (value)) +/** An atomic XOR (^) operation, returns previous value */ +#define fio_atomic_xor(p_obj, value) atomic_fetch_xor((p_obj), (value)) +/** An atomic OR (|) operation, returns previous value */ +#define fio_atomic_or(p_obj, value) atomic_fetch_or((p_obj), (value)) +/** An atomic NOT AND ((~)&) operation, returns previous value */ +#define fio_atomic_nand(p_obj, value) atomic_fetch_nand((p_obj), (value)) +/** An atomic addition operation, returns new value */ +#define fio_atomic_add_fetch(p_obj, value) (atomic_fetch_add((p_obj), (value)), atomic_load((p_obj))) +/** An atomic subtraction operation, returns new value */ +#define fio_atomic_sub_fetch(p_obj, value) (atomic_fetch_sub((p_obj), (value)), atomic_load((p_obj))) +/** An atomic AND (&) operation, returns new value */ +#define fio_atomic_and_fetch(p_obj, value) (atomic_fetch_and((p_obj), (value)), atomic_load((p_obj))) +/** An atomic XOR (^) operation, returns new value */ +#define fio_atomic_xor_fetch(p_obj, value) (atomic_fetch_xor((p_obj), (value)), atomic_load((p_obj))) +/** An atomic OR (|) operation, returns new value */ +#define fio_atomic_or_fetch(p_obj, value) (atomic_fetch_or((p_obj), (value)), atomic_load((p_obj))) + +#elif _MSC_VER +#pragma message ("Warning: WinAPI atomics have less features, but this is what this compiler has, so...") +#include +#define FIO___ATOMICS_FN_ROUTE(fn, ptr, ...) \ + ((sizeof(*ptr) == 1) \ + ? fn##8((int8_t volatile *)(ptr), __VA_ARGS__) \ + : (sizeof(*ptr) == 2) \ + ? fn##16((int16_t volatile *)(ptr), __VA_ARGS__) \ + : (sizeof(*ptr) == 4) \ + ? fn((int32_t volatile *)(ptr), __VA_ARGS__) \ + : fn##64((int64_t volatile *)(ptr), __VA_ARGS__)) + +#ifndef _WIN64 +#error Atomics on Windows require 64bit OS and compiler support. +#endif + +/** An atomic load operation, returns value in pointer. */ +#define fio_atomic_load(dest, p_obj) (dest = *(p_obj)) + +/** An atomic compare and exchange operation, returns true if an exchange occured. `p_expected` MAY be overwritten with the existing value (system specific). */ +#define fio_atomic_compare_exchange_p(p_obj, p_expected, p_desired) (FIO___ATOMICS_FN_ROUTE(_InterlockedCompareExchange, (p_obj),(*(p_desired)),(*(p_expected))), (*(p_obj) == *(p_desired))) +/** An atomic exchange operation, returns previous value */ +#define fio_atomic_exchange(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedExchange, (p_obj), (value)) + +/** An atomic addition operation, returns previous value */ +#define fio_atomic_add(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedExchangeAdd, (p_obj), (value)) +/** An atomic subtraction operation, returns previous value */ +#define fio_atomic_sub(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedExchangeAdd, (p_obj), (0ULL - (value))) +/** An atomic AND (&) operation, returns previous value */ +#define fio_atomic_and(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedAnd, (p_obj), (value)) +/** An atomic XOR (^) operation, returns previous value */ +#define fio_atomic_xor(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedXor, (p_obj), (value)) +/** An atomic OR (|) operation, returns previous value */ +#define fio_atomic_or(p_obj, value) FIO___ATOMICS_FN_ROUTE(_InterlockedOr, (p_obj), (value)) + +/** An atomic addition operation, returns new value */ +#define fio_atomic_add_fetch(p_obj, value) (fio_atomic_add((p_obj), (value)), (*(p_obj))) +/** An atomic subtraction operation, returns new value */ +#define fio_atomic_sub_fetch(p_obj, value) (fio_atomic_sub((p_obj), (value)), (*(p_obj))) +/** An atomic AND (&) operation, returns new value */ +#define fio_atomic_and_fetch(p_obj, value) (fio_atomic_and((p_obj), (value)), (*(p_obj))) +/** An atomic XOR (^) operation, returns new value */ +#define fio_atomic_xor_fetch(p_obj, value) (fio_atomic_xor((p_obj), (value)), (*(p_obj))) +/** An atomic OR (|) operation, returns new value */ +#define fio_atomic_or_fetch(p_obj, value) (fio_atomic_or((p_obj), (value)), (*(p_obj))) +#else +#error Required atomics not found (__STDC_NO_ATOMICS__) and older __sync_add_and_fetch is also missing. + +#endif /* Atomic Implementation Selector */ +// clang-format on + +/* ***************************************************************************** +Spin-Locks +***************************************************************************** */ + +#define FIO_LOCK_INIT 0 +#define FIO_LOCK_SUBLOCK(sub) ((uint8_t)(1U) << ((sub)&7)) +typedef volatile unsigned char fio_lock_i; + +#ifndef FIO___LOCK_RESCHDULE_EVERY_LOG +#define FIO___LOCK_RESCHDULE_EVERY_LOG 8 +#endif +/** + * Tries to lock a group of sublocks. + * + * Combine a number of sublocks using OR (`|`) and the FIO_LOCK_SUBLOCK(i) + * macro. i.e.: + * + * if(!fio_trylock_group(&lock, + * FIO_LOCK_SUBLOCK(1) | FIO_LOCK_SUBLOCK(2))) { + * // act in lock + * } + * + * Returns 0 on success and non-zero on failure. + */ +FIO_IFUNC uint8_t fio_trylock_group(fio_lock_i *lock, uint8_t group) { + if (!group) + group = 1; + FIO_COMPILER_GUARD; + uint8_t state = fio_atomic_or(lock, group); + if (!(state & group)) + return 0; + /* store the acquired locks in `state`. */ + state = ~((~state) & group); + /* release the locks we acquired */ + fio_atomic_and(lock, state); + return 1; +} + +/** + * Busy waits for a group lock to become available - not recommended. + * + * See `fio_trylock_group` for details. + */ +FIO_IFUNC void fio_lock_group(fio_lock_i *lock, uint8_t group) { + size_t i = 0; + while (fio_trylock_group(lock, group)) { + if ((i++ & (1U << FIO___LOCK_RESCHDULE_EVERY_LOG))) { + i = 0; + FIO_THREAD_RESCHEDULE(); + } + } +} + +/** Unlocks a sublock group, no matter which thread owns which sublock. */ +FIO_IFUNC void fio_unlock_group(fio_lock_i *lock, uint8_t group) { + if (!group) + group = 1; + fio_atomic_and(lock, (~group)); +} + +/** Tries to lock all sublocks. Returns 0 on success and 1 on failure. */ +FIO_IFUNC uint8_t fio_trylock_full(fio_lock_i *lock) { + return fio_trylock_group(lock, (uint8_t)~0); +} + +/** Busy waits for all sub lock to become available - not recommended. */ +FIO_IFUNC void fio_lock_full(fio_lock_i *lock) { + fio_lock_group(lock, (uint8_t)~0); +} + +/** Unlocks all sub locks, no matter which thread owns the lock. */ +FIO_IFUNC void fio_unlock_full(fio_lock_i *lock) { fio_atomic_and(lock, 0); } + +/** + * Tries to acquire the default lock (sublock 0). + * + * Returns 0 on success and 1 on failure. + */ +FIO_IFUNC uint8_t fio_trylock(fio_lock_i *lock) { + return fio_trylock_group(lock, (uint8_t)1); +} + +/** Busy waits for the default lock to become available - not recommended. */ +FIO_IFUNC void fio_lock(fio_lock_i *lock) { fio_lock_group(lock, (uint8_t)1); } + +/** Unlocks the default lock, no matter which thread owns the lock. */ +FIO_IFUNC void fio_unlock(fio_lock_i *lock) { + fio_unlock_group(lock, (uint8_t)1); +} + +/** Returns 1 if the lock is locked, 0 otherwise. */ +FIO_IFUNC uint8_t FIO_NAME_BL(fio, locked)(fio_lock_i *lock) { + return *lock & 1; +} + +/** Returns 1 if the lock is locked, 0 otherwise. */ +FIO_IFUNC uint8_t FIO_NAME_BL(fio, group_locked)(fio_lock_i *lock, + uint8_t group) { + return !!((*lock) & group); +} + +/* ***************************************************************************** +Atomic Bit access / manipulation +***************************************************************************** */ + +/** Gets the state of a bit in a bitmap. */ +FIO_IFUNC uint8_t fio_atomic_bit_get(void *map, size_t bit) { + return ((((uint8_t *)(map))[(bit) >> 3] >> ((bit)&7)) & 1); +} + +/** Sets the a bit in a bitmap (sets to 1). */ +FIO_IFUNC void fio_atomic_bit_set(void *map, size_t bit) { + fio_atomic_or((uint8_t *)(map) + ((bit) >> 3), (1UL << ((bit)&7))); +} + +/** Unsets the a bit in a bitmap (sets to 0). */ +FIO_IFUNC void fio_atomic_bit_unset(void *map, size_t bit) { + fio_atomic_and((uint8_t *)(map) + ((bit) >> 3), + (uint8_t)(~(1UL << ((bit)&7)))); +} + +/** Flips the a bit in a bitmap (sets to 0 if 1, sets to 1 if 0). */ +FIO_IFUNC void fio_atomic_bit_flip(void *map, size_t bit) { + fio_atomic_xor((uint8_t *)(map) + ((bit) >> 3), (1UL << ((bit)&7))); +} + +/* ***************************************************************************** +UNSAFE (good enough) Static Memory Allocation + +This is useful when attempting thread-safety controls through a round-robin +buffer that assumes both fast usage and a maximum number of concurrent calls, or +maximum number of threads, of `FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX`. + +This is supposed to provide both a safe alternative to `alloca` and allows the +memory address to be returned if needed (valid until concurrency max calls). +***************************************************************************** */ + +#ifndef FIO_STATIC_ALLOC_CONCURRENCY_MAX +/* The multiplier is used to set the maximum number of safe concurrent calls. */ +#define FIO_STATIC_ALLOC_CONCURRENCY_MAX 256 +#endif + +/** + * Defines a simple (almost naive) static memory allocator named `name`. + * + * This defines a memory allocation function named `name` that accepts a + * single input `count` and returns a `type_T` pointer (`type_T *`) containing + * `sizeof(type_T) * count * size_per_allocation` in correct memory alignment. + * + * static type_T *name(size_t allocation_count); + * + * That memory is statically allocated, allowing it be returned and never + * needing to be freed. + * + * The functions can safely allocate the following number of bytes before + * the function returns the same memory block to another caller: + * + * FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX * allocations_per_thread * + * sizeof(type_T) * size_per_allocation + * + * Example use: + * + * ```c + * // defined a static allocator for 32 byte long strings + * FIO_STATIC_ALLOC_DEF(numer2hex_allocator, char, 19, 1); + * // a function that returns an unsigned number as a 16 digit hex string + * char * ntos16(uint16_t n) { + * char * n = numer2hex_allocator(1); + * n[0] = '0'; n[1] = 'x'; + * fio_ltoa16u(n+2, n, 16); + * n[18] = 0; + * return n; + * } + * ``` + * + * A similar approach is use by `fiobj_num2cstr` in order to provide temporary + * conversions of FIOBJ to a C String that doesn't require memory management. + */ +#define FIO_STATIC_ALLOC_DEF(name, \ + type_T, \ + size_per_allocation, \ + allocations_per_thread) \ + FIO_SFUNC __attribute__((warn_unused_result)) type_T *name(size_t count) { \ + static type_T name##buffer[sizeof(type_T) * \ + FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX * \ + size_per_allocation * allocations_per_thread]; \ + static size_t pos; \ + size_t at = fio_atomic_add(&pos, count * size_per_allocation); \ + at %= FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX * size_per_allocation * \ + allocations_per_thread; \ + return at + name##buffer; \ + } + +/* ***************************************************************************** +Logging Primitives (no-op) +***************************************************************************** */ + +/* avoid printing a full / nested path when __FILE_NAME__ is available */ +#ifdef __FILE_NAME__ +#define FIO__FILE__ __FILE_NAME__ +#else +#define FIO__FILE__ __FILE__ +#endif + +/** Logging level of zero (no logging). */ +#define FIO_LOG_LEVEL_NONE 0 +/** Log fatal errors. */ +#define FIO_LOG_LEVEL_FATAL 1 +/** Log errors and fatal errors. */ +#define FIO_LOG_LEVEL_ERROR 2 +/** Log warnings, errors and fatal errors. */ +#define FIO_LOG_LEVEL_WARNING 3 +/** Log every message (info, warnings, errors and fatal errors). */ +#define FIO_LOG_LEVEL_INFO 4 +/** Log everything, including debug messages. */ +#define FIO_LOG_LEVEL_DEBUG 5 + +/** Sets the Logging Level */ +#define FIO_LOG_LEVEL_SET(new_level) (0) +/** Returns the Logging Level */ +#define FIO_LOG_LEVEL_GET() (0) + +// clang-format off +#define FIO___LOG_PRINT_LEVEL(level, ...) do { if ((level) <= FIO_LOG_LEVEL_GET()) {FIO_LOG2STDERR(__VA_ARGS__);} } while (0) +#define FIO_LOG_WRITE(...) FIO_LOG2STDERR("(" FIO__FILE__ ":" FIO_MACRO2STR(__LINE__) "): " __VA_ARGS__) +#define FIO_LOG_FATAL(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_FATAL, "\x1B[1m\x1B[7mFATAL:\x1B[0m " __VA_ARGS__) +#define FIO_LOG_ERROR(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_ERROR, "\x1B[1mERROR:\x1B[0m " __VA_ARGS__) +#define FIO_LOG_SECURITY(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_ERROR, "\x1B[1mSECURITY:\x1B[0m " __VA_ARGS__) +#define FIO_LOG_WARNING(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_WARNING, "\x1B[2mWARNING:\x1B[0m " __VA_ARGS__) +#define FIO_LOG_INFO(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_INFO, "INFO: " __VA_ARGS__) +#define FIO_LOG_DEBUG(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_DEBUG,"DEBUG: (" FIO__FILE__ ":" FIO_MACRO2STR(__LINE__) ") " __VA_ARGS__) +#define FIO_LOG_DEBUG2(...) FIO___LOG_PRINT_LEVEL(FIO_LOG_LEVEL_DEBUG, "DEBUG: " __VA_ARGS__) +// clang-format on + +#ifdef DEBUG +#define FIO_LOG_DDEBUG(...) FIO_LOG_DEBUG(__VA_ARGS__) +#define FIO_LOG_DDEBUG2(...) FIO_LOG_DEBUG2(__VA_ARGS__) +#define FIO_LOG_DERROR(...) FIO_LOG_ERROR(__VA_ARGS__) +#define FIO_LOG_DSECURITY(...) FIO_LOG_SECURITY(__VA_ARGS__) +#define FIO_LOG_DWARNING(...) FIO_LOG_WARNING(__VA_ARGS__) +#define FIO_LOG_DINFO(...) FIO_LOG_INFO(__VA_ARGS__) +#define FIO_ASSERT___PERFORM_SIGNAL() FIO___KILL_SELF(); +#else +#define FIO_LOG_DDEBUG(...) ((void)(0)) +#define FIO_LOG_DDEBUG2(...) ((void)(0)) +#define FIO_LOG_DERROR(...) ((void)(0)) +#define FIO_LOG_DSECURITY(...) ((void)(0)) +#define FIO_LOG_DWARNING(...) ((void)(0)) +#define FIO_LOG_DINFO(...) ((void)(0)) +#define FIO_ASSERT___PERFORM_SIGNAL() +#endif /* DEBUG */ + +#ifndef FIO_LOG_LENGTH_LIMIT +/** Defines a point at which logging truncates (limits stack memory use) */ +#define FIO_LOG_LENGTH_LIMIT 1024 +#endif + +/** Prints to STDERR, attempting to use only stack allocated memory. */ +#define FIO_LOG2STDERR(...) + +/* ***************************************************************************** +Assertions +***************************************************************************** */ + +/* Asserts a condition is true, or kills the application using SIGINT. */ +#define FIO_ASSERT(cond, ...) \ + do { \ + if (FIO_UNLIKELY(!(cond))) { \ + FIO_LOG_FATAL(__VA_ARGS__); \ + FIO_LOG_FATAL(" errno(%d): %s\n", errno, strerror(errno)); \ + FIO_ASSERT___PERFORM_SIGNAL(); \ + abort(); \ + } \ + } while (0) + +#ifndef FIO_ASSERT_ALLOC +/** Tests for an allocation failure. The behavior can be overridden. */ +#define FIO_ASSERT_ALLOC(ptr) FIO_ASSERT((ptr), "memory allocation failed.") +#endif + +#ifdef DEBUG +/** If `DEBUG` is defined, raises SIGINT if assertion fails, otherwise NOOP. */ +#define FIO_ASSERT_DEBUG(cond, ...) \ + do { \ + if (!(cond)) { \ + FIO_LOG_FATAL("(" FIO__FILE__ \ + ":" FIO_MACRO2STR(__LINE__) ") " __VA_ARGS__); \ + FIO_LOG_FATAL(" errno(%d): %s\n", errno, strerror(errno)); \ + FIO_ASSERT___PERFORM_SIGNAL(); \ + exit(-1); \ + } \ + } while (0) +#else +#define FIO_ASSERT_DEBUG(...) +#endif + +/* ***************************************************************************** +Static Assertions +***************************************************************************** */ +#if __STDC_VERSION__ >= 201112L +#define FIO_ASSERT_STATIC(cond, msg) _Static_assert((cond), msg) +#else +#define FIO_ASSERT_STATIC(cond, msg) \ + static const char *FIO_NAME(fio_static_assertion_failed, \ + __LINE__)[(((cond) << 1) - 1)] = {(char *)msg} +#endif + +typedef struct { + unsigned char data[2]; +} fio___padding_char_struct_test_s; + +FIO_ASSERT_STATIC(CHAR_BIT == 8, "facil.io requires an 8bit wide char"); +FIO_ASSERT_STATIC(sizeof(uint8_t) == 1, + "facil.io requires an 8bit wide uint8_t"); +FIO_ASSERT_STATIC(sizeof(uint16_t) == 2, + "facil.io requires a 16bit wide uint16_t"); +FIO_ASSERT_STATIC(sizeof(uint32_t) == 4, + "facil.io requires a 32bit wide uint32_t"); +FIO_ASSERT_STATIC(sizeof(uint64_t) == 8, + "facil.io requires a 64bit wide uint64_t"); +FIO_ASSERT_STATIC(sizeof(fio___padding_char_struct_test_s) == 2, + "compiler adds padding to fio___memcpyX, creating memory " + "alignment issues."); + +/* ***************************************************************************** +Static Endian Test +***************************************************************************** */ + +#if (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__) || \ + (defined(__BIG_ENDIAN__) && !__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) +#ifndef __BIG_ENDIAN__ +#define __BIG_ENDIAN__ 0 +#endif +#ifndef __LITTLE_ENDIAN__ +#define __LITTLE_ENDIAN__ 1 +#endif +#elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__) || \ + (defined(__LITTLE_ENDIAN__) && !__LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) +#ifndef __BIG_ENDIAN__ +#define __BIG_ENDIAN__ 1 +#endif +#ifndef __LITTLE_ENDIAN__ +#define __LITTLE_ENDIAN__ 0 +#endif +#elif !defined(__BIG_ENDIAN__) && !defined(__BYTE_ORDER__) && \ + !defined(__LITTLE_ENDIAN__) +#define FIO_LITTLE_ENDIAN_TEST 0x31323334UL +#define FIO_BIG_ENDIAN_TEST 0x34333231UL +#define FIO_ENDIAN_ORDER_TEST ('1234') +#if ENDIAN_ORDER_TEST == LITTLE_ENDIAN_TEST +#define __BIG_ENDIAN__ 0 +#define __LITTLE_ENDIAN__ 1 +#elif ENDIAN_ORDER_TEST == BIG_ENDIAN_TEST +#define __BIG_ENDIAN__ 1 +#define __LITTLE_ENDIAN__ 0 +#else +#error Could not detect byte order on this system. +#endif + +#endif /* predefined / test endianess */ + +/* ***************************************************************************** +Dynamic Endian Testing +***************************************************************************** */ + +FIO_IFUNC unsigned int fio_is_little_endian(void) { + union { + unsigned long ul; + unsigned char u8[sizeof(unsigned long)]; + } u = {.ul = 1}; + return (unsigned int)u.u8[0]; +} + +FIO_IFUNC unsigned int fio_is_big_endian(void) { + return !fio_is_little_endian(); +} + +/* ***************************************************************************** +Security Related macros +***************************************************************************** */ +#define FIO_MEM_STACK_WIPE(pages) \ + do { \ + volatile char stack_mem[(pages) << 12] = {0}; \ + (void)stack_mem; \ + } while (0) + +/* ***************************************************************************** +Settings - Memory Function Selectors +***************************************************************************** */ +#ifdef FIO_MEMALT +#ifndef FIO_MEMCPY +#define FIO_MEMCPY fio_memcpy +#endif +#ifndef FIO_MEMMOVE +#define FIO_MEMMOVE fio_memcpy +#endif +#ifndef FIO_MEMCMP +#define FIO_MEMCMP fio_memcmp +#endif +#ifndef FIO_MEMCHR +#define FIO_MEMCHR fio_memchr +#endif +#ifndef FIO_MEMSET +#define FIO_MEMSET fio_memset +#endif +#ifndef FIO_STRLEN +#define FIO_STRLEN fio_strlen +#endif +#endif /* FIO_MEMALT */ + +/* memcpy selectors / overriding */ +#ifndef FIO_MEMCPY +#if __has_builtin(__builtin_memcpy) +/** `memcpy` selector macro */ +#define FIO_MEMCPY __builtin_memcpy +#else +/** `memcpy` selector macro */ +#define FIO_MEMCPY memcpy +#endif +#endif /* FIO_MEMCPY */ + +/* memmove selectors / overriding */ +#ifndef FIO_MEMMOVE +#if __has_builtin(__builtin_memmove) +/** `memmov` selector macro */ +#define FIO_MEMMOVE __builtin_memmove +#else +/** `memmov` selector macro */ +#define FIO_MEMMOVE memmove +#endif +#endif /* FIO_MEMMOVE */ + +/* memset selectors / overriding */ +#ifndef FIO_MEMSET +#if __has_builtin(__builtin_memset) +/** `memset` selector macro */ +#define FIO_MEMSET __builtin_memset +#else +/** `memset` selector macro */ +#define FIO_MEMSET memset +#endif +#endif /* FIO_MEMSET */ + +/* memchr selectors / overriding */ +#ifndef FIO_MEMCHR +#if __has_builtin(__builtin_memchr) +/** `memchr` selector macro */ +#define FIO_MEMCHR __builtin_memchr +#else +/** `memchr` selector macro */ +#define FIO_MEMCHR memchr +#endif +#endif /* FIO_MEMCHR */ + +/* strlen selectors / overriding */ +#ifndef FIO_STRLEN +#if __has_builtin(__builtin_strlen) +/** `strlen` selector macro */ +#define FIO_STRLEN __builtin_strlen +#else +/** `strlen` selector macro */ +#define FIO_STRLEN strlen +#endif +#endif /* FIO_STRLEN */ + +/* memcmp selectors / overriding */ +#ifndef FIO_MEMCMP +#if __has_builtin(__builtin_memcmp) +/** `memcmp` selector macro */ +#define FIO_MEMCMP __builtin_memcmp +#else +/** `memcmp` selector macro */ +#define FIO_MEMCMP memcmp +#endif +#endif /* FIO_MEMCMP */ + +/* ***************************************************************************** +Memory Copying Primitives (the basis for unaligned memory access for numbers) +***************************************************************************** */ + +/* memcpy selectors / overriding */ +#if __has_builtin(__builtin_memcpy) +#define FIO___MAKE_MEMCPY_FIXED(bytes) \ + FIO_SFUNC void *fio_memcpy##bytes(void *restrict d, \ + const void *restrict s) { \ + return __builtin_memcpy(d, s, bytes); \ + } +#else +#define FIO___MAKE_MEMCPY_FIXED(bytes) \ + FIO_SFUNC void *fio_memcpy##bytes(void *restrict d, \ + const void *restrict s) { \ + void *const r = (char *)d + bytes; \ + for (size_t i = 0; i < bytes; ++i) /* compiler, please vectorize */ \ + ((char *)d)[i] = ((const char *)s)[i]; \ + return r; \ + } +#endif /* __has_builtin(__builtin_memcpy) */ + +/** No-op (completes the name space). */ +FIO_SFUNC void *fio_memcpy0(void *restrict d, const void *restrict s) { + ((void)s); + return d; +} +/** Copies 1 byte from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(1) +/** Copies 2 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(2) +/** Copies 3 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(3) +/** Copies 4 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(4) +/** Copies 5 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(5) +/** Copies 6 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(6) +/** Copies 7 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(7) +/** Copies 8 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(8) +/** Copies 16 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(16) +/** Copies 32 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(32) +/** Copies 64 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(64) +/** Copies 128 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(128) +/** Copies 256 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(256) +/** Copies 512 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(512) +/** Copies 1024 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(1024) +/** Copies 2048 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(2048) +/** Copies 4096 bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MAKE_MEMCPY_FIXED(4096) +#undef FIO___MAKE_MEMCPY_FIXED + +/** an unsafe memcpy (no checks + no overlapping memory regions) up to 63B */ +FIO_IFUNC void *fio___memcpy_unsafe_63x(void *restrict d_, + const void *restrict s_, + size_t l) { + char *restrict d = (char *restrict)d_; + const char *restrict s = (const char *restrict)s_; +#define FIO___MEMCPY_XX_GROUP(bytes) \ + do { \ + fio_memcpy##bytes((void *)d, (void *)s); \ + d += l & (bytes - 1); \ + s += l & (bytes - 1); \ + fio_memcpy##bytes((void *)d, (void *)s); \ + return (void *)(d += bytes); \ + } while (0) + if (l > 31) + FIO___MEMCPY_XX_GROUP(32); + if (l > 15) + FIO___MEMCPY_XX_GROUP(16); + if (l > 7) + FIO___MEMCPY_XX_GROUP(8); +#undef FIO___MEMCPY_XX_GROUP + if ((l & 4)) { + fio_memcpy4(d, s); + (d += 4), (s += 4); + } + if ((l & 2)) { + fio_memcpy2(d, s); + (d += 2), (s += 2); + } + if ((l & 1)) + *d++ = *s; + return (void *)d; +} +/** an unsafe memcpy (no checks + assumes no overlapping memory regions) */ +FIO_SFUNC void *fio___memcpy_unsafe_x(void *restrict d_, + const void *restrict s_, + size_t l) { + char *restrict d = (char *restrict)d_; + const char *restrict s = (const char *restrict)s_; + if (l < 64) + return fio___memcpy_unsafe_63x(d_, s_, l); +#define FIO___MEMCPY_UNSAFE_STEP(bytes) \ + do { \ + fio_memcpy##bytes((void *)d, (void *)s); \ + (l -= bytes), (d += bytes), (s += bytes); \ + } while (0) + +#if FIO_LIMIT_INTRINSIC_BUFFER + while (l > 127) + FIO___MEMCPY_UNSAFE_STEP(128); +#else + while (l > 255) + FIO___MEMCPY_UNSAFE_STEP(256); + if (l & 128) + FIO___MEMCPY_UNSAFE_STEP(128); +#endif + if (l & 64) + FIO___MEMCPY_UNSAFE_STEP(64); +#undef FIO___MEMCPY_UNSAFE_STEP + d -= 64; + s -= 64; + d += l & 63U; + s += l & 63U; + fio_memcpy64((void *)d, (void *)s); + return (void *)(d += 64); +} + +#define FIO___MEMCPYX_MAKER(lim, fn) \ + FIO_IFUNC void *fio_memcpy##lim##x(void *restrict d, \ + const void *restrict s, \ + size_t l) { \ + return fn(d, s, (l & lim)); \ + } + +/** No-op (completes the name space). */ +FIO_SFUNC void *fio_memcpy0x(void *d, const void *s, size_t l) { + ((void)s), ((void)l); + return d; +} +/** Copies up to (len & 7) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(7, fio___memcpy_unsafe_63x) +/** Copies up to (len & 15) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(15, fio___memcpy_unsafe_63x) +/** Copies up to (len & 31) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(31, fio___memcpy_unsafe_63x) +/** Copies up to (len & 63) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(63, fio___memcpy_unsafe_63x) +/** Copies up to (len & 127) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(127, fio___memcpy_unsafe_x) +/** Copies up to (len & 255) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(255, fio___memcpy_unsafe_x) +/** Copies up to (len & 511) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(511, fio___memcpy_unsafe_x) +/** Copies up to (len & 1023) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(1023, fio___memcpy_unsafe_x) +/** Copies up to (len & 2047) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(2047, fio___memcpy_unsafe_x) +/** Copies up to (len & 4095) bytes from `src` (`s`) to `dest` (`d`). */ +FIO___MEMCPYX_MAKER(4095, fio___memcpy_unsafe_x) +#undef FIO___MEMCPYX_MAKER + +/* ***************************************************************************** +Swapping byte's order (`bswap` variations) +***************************************************************************** */ + +/* avoid special cases by defining for all sizes */ +#define fio_bswap8(i) (i) + +/** Byte swap a 16 bit integer, inlined. */ +#if __has_builtin(__builtin_bswap16) +#define fio_bswap16(i) __builtin_bswap16((uint16_t)(i)) +#else +FIO_IFUNC uint16_t fio_bswap16(uint16_t i) { + return ((((i)&0xFFU) << 8) | (((i)&0xFF00U) >> 8)); +} +#endif + +/** Byte swap a 32 bit integer, inlined. */ +#if __has_builtin(__builtin_bswap32) +#define fio_bswap32(i) __builtin_bswap32((uint32_t)(i)) +#else +FIO_IFUNC uint32_t fio_bswap32(uint32_t i) { + return ((((i)&0xFFUL) << 24) | (((i)&0xFF00UL) << 8) | + (((i)&0xFF0000UL) >> 8) | (((i)&0xFF000000UL) >> 24)); +} +#endif + +/** Byte swap a 64 bit integer, inlined. */ +#if __has_builtin(__builtin_bswap64) +#define fio_bswap64(i) __builtin_bswap64((uint64_t)(i)) +#else +FIO_IFUNC uint64_t fio_bswap64(uint64_t i) { + return ((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | + (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | + (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | + (((i)&0xFF000000000000ULL) >> 40) | + (((i)&0xFF00000000000000ULL) >> 56)); +} +#endif + +#ifdef __SIZEOF_INT128__ +#if __has_builtin(__builtin_bswap128) +#define fio_bswap128(i) __builtin_bswap128((__uint128_t)(i)) +#else +FIO_IFUNC __uint128_t fio_bswap128(__uint128_t i) { + return ((__uint128_t)fio_bswap64(i) << 64) | fio_bswap64(i >> 64); +} +#endif +#endif /* __SIZEOF_INT128__ */ + +/* ***************************************************************************** +Switching Endian Ordering +***************************************************************************** */ + +#define fio_ltole8(i) (i) /* avoid special cases by defining for all sizes */ +#define fio_lton8(i) (i) /* avoid special cases by defining for all sizes */ +#define fio_ntol8(i) (i) /* avoid special cases by defining for all sizes */ + +#if __BIG_ENDIAN__ + +/** Local byte order to Network byte order, 16 bit integer */ +#define fio_lton16(i) (i) +/** Local byte order to Network byte order, 32 bit integer */ +#define fio_lton32(i) (i) +/** Local byte order to Network byte order, 62 bit integer */ +#define fio_lton64(i) (i) + +/** Local byte order to Little Endian byte order, 16 bit integer */ +#define fio_ltole16(i) fio_bswap16((i)) +/** Local byte order to Little Endian byte order, 32 bit integer */ +#define fio_ltole32(i) fio_bswap32((i)) +/** Local byte order to Little Endian byte order, 62 bit integer */ +#define fio_ltole64(i) fio_bswap64((i)) + +/** Network byte order to Local byte order, 16 bit integer */ +#define fio_ntol16(i) (i) +/** Network byte order to Local byte order, 32 bit integer */ +#define fio_ntol32(i) (i) +/** Network byte order to Local byte order, 62 bit integer */ +#define fio_ntol64(i) (i) + +#ifdef __SIZEOF_INT128__ +/** Network byte order to Local byte order, 128 bit integer */ +#define fio_ntol128(i) (i) +/** Local byte order to Little Endian byte order, 128 bit integer */ +#define fio_ltole128(i) fio_bswap128((i)) + +/** An endianess dependent shift operation, moves bytes forwards. */ +#define FIO_SHIFT_FORWARDS(i, bits) ((i) >> (bits)) +/** An endianess dependent shift operation, moves bytes backwards. */ +#define FIO_SHIFT_BACKWARDS(i, bits) ((i) << (bits)) + +#endif /* __SIZEOF_INT128__ */ + +#else /* Little Endian */ + +/** Local byte order to Network byte order, 16 bit integer */ +#define fio_lton16(i) fio_bswap16((i)) +/** Local byte order to Network byte order, 32 bit integer */ +#define fio_lton32(i) fio_bswap32((i)) +/** Local byte order to Network byte order, 62 bit integer */ +#define fio_lton64(i) fio_bswap64((i)) + +/** Local byte order to Little Endian byte order, 16 bit integer */ +#define fio_ltole16(i) (i) +/** Local byte order to Little Endian byte order, 32 bit integer */ +#define fio_ltole32(i) (i) +/** Local byte order to Little Endian byte order, 62 bit integer */ +#define fio_ltole64(i) (i) + +/** Network byte order to Local byte order, 16 bit integer */ +#define fio_ntol16(i) fio_bswap16((i)) +/** Network byte order to Local byte order, 32 bit integer */ +#define fio_ntol32(i) fio_bswap32((i)) +/** Network byte order to Local byte order, 62 bit integer */ +#define fio_ntol64(i) fio_bswap64((i)) + +#ifdef __SIZEOF_INT128__ +/** Local byte order to Network byte order, 128 bit integer */ +#define fio_lton128(i) fio_bswap128((i)) +/** Network byte order to Local byte order, 128 bit integer */ +#define fio_ntol128(i) fio_bswap128((i)) +/** Local byte order to Little Endian byte order, 128 bit integer */ +#define fio_ltole128(i) (i) +#endif /* __SIZEOF_INT128__ */ + +/** An endianess dependent shift operation, moves bytes forwards. */ +#define FIO_SHIFT_FORWARDS(i, bits) ((i) << (bits)) +/** An endianess dependent shift operation, moves bytes backwards. */ +#define FIO_SHIFT_BACKWARDS(i, bits) ((i) >> (bits)) + +#endif /* __BIG_ENDIAN__ */ + +/* ***************************************************************************** +Unaligned memory read / write operations +***************************************************************************** */ + +/** Converts an unaligned byte stream to an 8 bit number. */ +FIO_IFUNC uint8_t fio_buf2u8u(const void *c) { return *(const uint8_t *)c; } +/** Writes a local 8 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf8u(void *buf, uint8_t i) { *((uint8_t *)buf) = i; } +/** Converts an unaligned byte stream to an 8 bit number. */ +FIO_IFUNC uint8_t fio_buf2u8_le(const void *c) { return *(const uint8_t *)c; } +/** Writes a local 8 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf8_le(void *buf, uint8_t i) { *((uint8_t *)buf) = i; } +/** Converts an unaligned byte stream to an 8 bit number. */ +FIO_IFUNC uint8_t fio_buf2u8_be(const void *c) { return *(const uint8_t *)c; } +/** Writes a local 8 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf8_be(void *buf, uint8_t i) { *((uint8_t *)buf) = i; } + +#define FIO___U2U_NOOP(i) (i) +#define FIO___MEMBUF_FN(bytes, n_bits, bits, wrapper, postfix) \ + /** Converts an unaligned byte stream to a bits bit number. */ \ + FIO_IFUNC uint##bits##_t fio_buf2u##n_bits##postfix(const void *c) { \ + uint##bits##_t tmp; \ + fio_memcpy##bytes(&tmp, c); \ + return wrapper(tmp); \ + } \ + /** Writes a bits bit number to an unaligned buffer. */ \ + FIO_IFUNC void fio_u2buf##n_bits##postfix(void *buf, uint##bits##_t i) { \ + i = wrapper(i); \ + fio_memcpy##bytes(buf, &i); \ + } +/* unspecified byte order (native ordering) */ +FIO___MEMBUF_FN(2, 16, 16, FIO___U2U_NOOP, u) +FIO___MEMBUF_FN(4, 32, 32, FIO___U2U_NOOP, u) +FIO___MEMBUF_FN(8, 64, 64, FIO___U2U_NOOP, u) +/* little endian byte order (native ordering) */ +FIO___MEMBUF_FN(2, 16, 16, fio_ltole16, _le) +FIO___MEMBUF_FN(4, 32, 32, fio_ltole32, _le) +FIO___MEMBUF_FN(8, 64, 64, fio_ltole64, _le) +/* big / network endian byte order (native ordering) */ +FIO___MEMBUF_FN(2, 16, 16, fio_lton16, _be) +FIO___MEMBUF_FN(4, 32, 32, fio_lton32, _be) +FIO___MEMBUF_FN(8, 64, 64, fio_lton64, _be) +#undef FIO___MEMBUF_FN + +/** Converts an unaligned byte stream to a 24 bit number. */ +FIO_IFUNC uint32_t fio_buf2u24u(const void *c) { + uint32_t tmp = 0; + fio_memcpy3(&tmp, c); +#if __BIG_ENDIAN__ + c = c >> 8; +#endif + return tmp; +} /** Writes a 24 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf24u(void *buf, uint32_t i) { +#if __BIG_ENDIAN__ + i = i << 8; +#endif + fio_memcpy3(buf, &i); +} + +/** Converts an unaligned byte stream to a 24 bit number. */ +FIO_IFUNC uint32_t fio_buf2u24_le(const void *c) { + uint32_t tmp = ((uint32_t)((uint8_t *)c)[0]) | + ((uint32_t)((uint8_t *)c)[1] << 8) | + ((uint32_t)((uint8_t *)c)[2] << 16); + return tmp; +} /** Writes a 24 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf24_le(void *buf, uint32_t i) { + ((uint8_t *)buf)[0] = i & 0xFFU; + ((uint8_t *)buf)[1] = (i >> 8) & 0xFFU; + ((uint8_t *)buf)[2] = (i >> 16) & 0xFFU; +} +/** Converts an unaligned byte stream to a 24 bit number. */ +FIO_IFUNC uint32_t fio_buf2u24_be(const void *c) { + uint32_t tmp = ((uint32_t)((uint8_t *)c)[0] << 16) | + ((uint32_t)((uint8_t *)c)[1] << 8) | + ((uint32_t)((uint8_t *)c)[2]); + return tmp; +} /** Writes a 24 bit number to an unaligned buffer. */ +FIO_IFUNC void fio_u2buf24_be(void *buf, uint32_t i) { + ((uint8_t *)buf)[0] = (i >> 16) & 0xFFU; + ((uint8_t *)buf)[1] = (i >> 8) & 0xFFU; + ((uint8_t *)buf)[2] = (i)&0xFFU; +} + +/* ***************************************************************************** +Vector Math, Shuffle & Reduction on native types, for up to 2048 bits +***************************************************************************** */ +#define FIO____SHFL_FN(T, prefx, len) \ + FIO_IFUNC void fio_##prefx##x##len##_reshuffle(T *v, uint8_t indx[len]) { \ + T tmp[len]; \ + for (size_t i = 0; i < len; ++i) { \ + tmp[i] = v[indx[i] & (len - 1)]; \ + } \ + for (size_t i = 0; i < len; ++i) { \ + v[i] = tmp[i]; \ + } \ + } +#define FIO____REDUCE_FN(T, prefx, len, opnm, op) \ + FIO_MIFN T fio_##prefx##x##len##_reduce_##opnm(T *v) { \ + T r = v[0]; \ + for (size_t i = 1; i < len; ++i) { \ + r = r op v[i]; \ + } \ + return r; \ + } \ + FIO_IFUNC void fio_##prefx##x##len##_##opnm(T *dest, T *a, T *b) { \ + for (size_t i = 0; i < len; ++i) \ + dest[i] = a[i] op b[i]; \ + } +#define FIO____REDUCE_MINMAX(T, prefx, len) \ + FIO_MIFN T fio_##prefx##x##len##_reduce_max(T *v) { \ + T r = v[0]; \ + for (size_t i = 1; i < len; ++i) { \ + r = r < v[i] ? v[i] : r; \ + } \ + return r; \ + } \ + FIO_MIFN T fio_##prefx##x##len##_reduce_min(T *v) { \ + T r = v[0]; \ + for (size_t i = 1; i < len; ++i) { \ + r = r > v[i] ? v[i] : r; \ + } \ + return r; \ + } + +#define FIO____SHFL_REDUCE(T, prefx, len) \ + FIO____SHFL_FN(T, prefx, len) \ + FIO____REDUCE_FN(T, prefx, len, add, +) \ + FIO____REDUCE_FN(T, prefx, len, sub, -) \ + FIO____REDUCE_FN(T, prefx, len, mul, *) \ + FIO____REDUCE_FN(T, prefx, len, and, &) \ + FIO____REDUCE_FN(T, prefx, len, or, |) \ + FIO____REDUCE_FN(T, prefx, len, xor, ^) \ + FIO____REDUCE_MINMAX(T, prefx, len) + +FIO____SHFL_REDUCE(uint8_t, u8, 4) +FIO____SHFL_REDUCE(uint8_t, u8, 8) +FIO____SHFL_REDUCE(uint8_t, u8, 16) +FIO____SHFL_REDUCE(uint8_t, u8, 32) +FIO____SHFL_REDUCE(uint8_t, u8, 64) +FIO____SHFL_REDUCE(uint8_t, u8, 128) +FIO____SHFL_REDUCE(uint8_t, u8, 256) +FIO____SHFL_REDUCE(uint16_t, u16, 2) +FIO____SHFL_REDUCE(uint16_t, u16, 4) +FIO____SHFL_REDUCE(uint16_t, u16, 8) +FIO____SHFL_REDUCE(uint16_t, u16, 16) +FIO____SHFL_REDUCE(uint16_t, u16, 32) +FIO____SHFL_REDUCE(uint16_t, u16, 64) +FIO____SHFL_REDUCE(uint16_t, u16, 128) +FIO____SHFL_REDUCE(uint32_t, u32, 2) +FIO____SHFL_REDUCE(uint32_t, u32, 4) +FIO____SHFL_REDUCE(uint32_t, u32, 8) +FIO____SHFL_REDUCE(uint32_t, u32, 16) +FIO____SHFL_REDUCE(uint32_t, u32, 32) +FIO____SHFL_REDUCE(uint32_t, u32, 64) +FIO____SHFL_REDUCE(uint64_t, u64, 2) +FIO____SHFL_REDUCE(uint64_t, u64, 4) +FIO____SHFL_REDUCE(uint64_t, u64, 8) +FIO____SHFL_REDUCE(uint64_t, u64, 16) +FIO____SHFL_REDUCE(uint64_t, u64, 32) + +#undef FIO____SHFL_REDUCE +#define FIO____SHFL_REDUCE(T, prefx, len) \ + FIO____SHFL_FN(T, prefx, len) \ + FIO____REDUCE_FN(T, prefx, len, add, +) \ + FIO____REDUCE_FN(T, prefx, len, mul, *) \ + FIO____REDUCE_MINMAX(T, prefx, len) + +FIO____SHFL_REDUCE(float, float, 2) +FIO____SHFL_REDUCE(float, float, 4) +FIO____SHFL_REDUCE(float, float, 8) +FIO____SHFL_REDUCE(float, float, 16) +FIO____SHFL_REDUCE(float, float, 32) +FIO____SHFL_REDUCE(float, float, 64) +FIO____SHFL_REDUCE(double, dbl, 2) +FIO____SHFL_REDUCE(double, dbl, 4) +FIO____SHFL_REDUCE(double, dbl, 8) +FIO____SHFL_REDUCE(double, dbl, 16) +FIO____SHFL_REDUCE(double, dbl, 32) +#undef FIO____REDUCE_FN +#undef FIO____REDUCE_MINMAX +#undef FIO____SHFL_FN +#undef FIO____SHFL_REDUCE + +/* clang-format off */ +#define fio_u8x4_reshuffle(v, ...) fio_u8x4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_u8x8_reshuffle(v, ...) fio_u8x8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_u8x16_reshuffle(v, ...) fio_u8x16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_u8x32_reshuffle(v, ...) fio_u8x32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +#define fio_u8x64_reshuffle(v, ...) fio_u8x64_reshuffle(v, (uint8_t[64]){__VA_ARGS__}) +#define fio_u8x128_reshuffle(v, ...) fio_u8x128_reshuffle(v, (uint8_t[128]){__VA_ARGS__}) +#define fio_u8x256_reshuffle(v, ...) fio_u8x256_reshuffle(v, (uint8_t[256]){__VA_ARGS__}) +#define fio_u16x2_reshuffle(v, ...) fio_u16x2_reshuffle(v, (uint8_t[2]){__VA_ARGS__}) +#define fio_u16x4_reshuffle(v, ...) fio_u16x4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_u16x8_reshuffle(v, ...) fio_u16x8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_u16x16_reshuffle(v, ...) fio_u16x16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_u16x32_reshuffle(v, ...) fio_u16x32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +#define fio_u16x64_reshuffle(v, ...) fio_u16x64_reshuffle(v, (uint8_t[64]){__VA_ARGS__}) +#define fio_u16x128_reshuffle(v,...) fio_u16x128_reshuffle(v, (uint8_t[128]){__VA_ARGS__}) +#define fio_u32x2_reshuffle(v, ...) fio_u32x2_reshuffle(v, (uint8_t[2]){__VA_ARGS__}) +#define fio_u32x4_reshuffle(v, ...) fio_u32x4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_u32x8_reshuffle(v, ...) fio_u32x8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_u32x16_reshuffle(v, ...) fio_u32x16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_u32x32_reshuffle(v, ...) fio_u32x32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +#define fio_u32x64_reshuffle(v, ...) fio_u32x64_reshuffle(v, (uint8_t[64]){__VA_ARGS__}) +#define fio_u64x2_reshuffle(v, ...) fio_u64x2_reshuffle(v, (uint8_t[2]){__VA_ARGS__}) +#define fio_u64x4_reshuffle(v, ...) fio_u64x4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_u64x8_reshuffle(v, ...) fio_u64x8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_u64x16_reshuffle(v, ...) fio_u64x16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_u64x32_reshuffle(v, ...) fio_u64x32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +#define fio_floatx2_reshuffle(v, ...) fio_floatx2_reshuffle(v, (uint8_t[2]){__VA_ARGS__}) +#define fio_floatx4_reshuffle(v, ...) fio_floatx4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_floatx8_reshuffle(v, ...) fio_floatx8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_floatx16_reshuffle(v, ...) fio_floatx16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_floatx32_reshuffle(v, ...) fio_floatx32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +#define fio_floatx64_reshuffle(v, ...) fio_floatx64_reshuffle(v, (uint8_t[64]){__VA_ARGS__}) +#define fio_dblx2_reshuffle(v, ...) fio_dblx2_reshuffle(v, (uint8_t[2]){__VA_ARGS__}) +#define fio_dblx4_reshuffle(v, ...) fio_dblx4_reshuffle(v, (uint8_t[4]){__VA_ARGS__}) +#define fio_dblx8_reshuffle(v, ...) fio_dblx8_reshuffle(v, (uint8_t[8]){__VA_ARGS__}) +#define fio_dblx16_reshuffle(v, ...) fio_dblx16_reshuffle(v, (uint8_t[16]){__VA_ARGS__}) +#define fio_dblx32_reshuffle(v, ...) fio_dblx32_reshuffle(v, (uint8_t[32]){__VA_ARGS__}) +/* clang-format on */ + +/* ***************************************************************************** +Linked Lists Persistent Macros and Types +***************************************************************************** */ + +/** A linked list arch-type */ +typedef struct fio_list_node_s { + struct fio_list_node_s *next; + struct fio_list_node_s *prev; +} fio_list_node_s; + +/** A linked list node type */ +#define FIO_LIST_NODE fio_list_node_s +/** A linked list head type */ +#define FIO_LIST_HEAD fio_list_node_s + +/** Allows initialization of FIO_LIST_HEAD objects. */ +#define FIO_LIST_INIT(obj) \ + (fio_list_node_s) { .next = &(obj), .prev = &(obj) } + +#ifndef FIO_LIST_EACH +/** Loops through every node in the linked list except the head. */ +#define FIO_LIST_EACH(type, node_name, head, pos) \ + for (type *pos = FIO_PTR_FROM_FIELD(type, node_name, (head)->next), \ + *next____p_ls_##pos = \ + FIO_PTR_FROM_FIELD(type, node_name, (head)->next->next); \ + pos != FIO_PTR_FROM_FIELD(type, node_name, (head)); \ + (pos = next____p_ls_##pos), \ + (next____p_ls_##pos = \ + FIO_PTR_FROM_FIELD(type, \ + node_name, \ + next____p_ls_##pos->node_name.next))) +/** Loops through every node in the linked list except the head. */ +#define FIO_LIST_EACH_REVERSED(type, node_name, head, pos) \ + for (type *pos = FIO_PTR_FROM_FIELD(type, node_name, (head)->prev), \ + *next____p_ls_##pos = \ + FIO_PTR_FROM_FIELD(type, node_name, (head)->next->prev); \ + pos != FIO_PTR_FROM_FIELD(type, node_name, (head)); \ + (pos = next____p_ls_##pos), \ + (next____p_ls_##pos = \ + FIO_PTR_FROM_FIELD(type, \ + node_name, \ + next____p_ls_##pos->node_name.prev))) +#endif + +/** UNSAFE macro for pushing a node to a list. */ +#define FIO_LIST_PUSH(head, n) \ + do { \ + (n)->prev = (head)->prev; \ + (n)->next = (head); \ + (head)->prev->next = (n); \ + (head)->prev = (n); \ + } while (0) + +/** UNSAFE macro for removing a node from a list. */ +#define FIO_LIST_REMOVE(n) \ + do { \ + (n)->prev->next = (n)->next; \ + (n)->next->prev = (n)->prev; \ + } while (0) + +/** UNSAFE macro for removing a node from a list. Resets node data. */ +#define FIO_LIST_REMOVE_RESET(n) \ + do { \ + (n)->prev->next = (n)->next; \ + (n)->next->prev = (n)->prev; \ + (n)->next = (n)->prev = (n); \ + } while (0) + +/** UNSAFE macro for popping a node to a list. */ +#define FIO_LIST_POP(type, node_name, dest_ptr, head) \ + do { \ + (dest_ptr) = FIO_PTR_FROM_FIELD(type, node_name, ((head)->next)); \ + FIO_LIST_REMOVE(&(dest_ptr)->node_name); \ + } while (0) + +/** UNSAFE macro for testing if a list is empty. */ +#define FIO_LIST_IS_EMPTY(head) \ + ((!(head)) || ((!(head)->next) | ((head)->next == (head)))) + +/* ***************************************************************************** +Indexed Linked Lists Persistent Macros and Types + +Indexed Linked Lists can be used to create a linked list that uses is always +relative to some root pointer (usually the root of an array). This: + +1. Allows easy reallocation of the list without requiring pointer updates. + +2. Could be used for memory optimization if the array limits are known. + +The "head" index is usually validated by reserving the value of `-1` to indicate +an empty list. +***************************************************************************** */ +#ifndef FIO_INDEXED_LIST_EACH + +/** A 32 bit indexed linked list node type */ +typedef struct fio_index32_node_s { + uint32_t next; + uint32_t prev; +} fio_index32_node_s; + +/** A 16 bit indexed linked list node type */ +typedef struct fio_index16_node_s { + uint16_t next; + uint16_t prev; +} fio_index16_node_s; + +/** An 8 bit indexed linked list node type */ +typedef struct fio_index8_node_s { + uint8_t next; + uint8_t prev; +} fio_index8_node_s; + +/** A 32 bit indexed linked list node type */ +#define FIO_INDEXED_LIST32_NODE fio_index32_node_s +#define FIO_INDEXED_LIST32_HEAD uint32_t +/** A 16 bit indexed linked list node type */ +#define FIO_INDEXED_LIST16_NODE fio_index16_node_s +#define FIO_INDEXED_LIST16_HEAD uint16_t +/** An 8 bit indexed linked list node type */ +#define FIO_INDEXED_LIST8_NODE fio_index8_node_s +#define FIO_INDEXED_LIST8_HEAD uint8_t + +/** UNSAFE macro for pushing a node to a list. */ +#define FIO_INDEXED_LIST_PUSH(root, node_name, head, i) \ + do { \ + register const size_t n__ = (i); \ + (root)[n__].node_name.prev = (root)[(head)].node_name.prev; \ + (root)[n__].node_name.next = (head); \ + (root)[(root)[(head)].node_name.prev].node_name.next = (n__); \ + (root)[(head)].node_name.prev = (n__); \ + } while (0) + +/** UNSAFE macro for adding a node to the begging of the list. */ +#define FIO_INDEXED_LIST_UNSHIFT(root, node_name, head, i) \ + do { \ + register const size_t n__ = (i); \ + (root)[n__].node_name.next = (root)[(head)].node_name.next; \ + (root)[n__].node_name.prev = (head); \ + (root)[(root)[(head)].node_name.next].node_name.prev = (n__); \ + (root)[(head)].node_name.next = (n__); \ + (head) = (n__); \ + } while (0) + +/** UNSAFE macro for removing a node from a list. */ +#define FIO_INDEXED_LIST_REMOVE(root, node_name, i) \ + do { \ + register const size_t n__ = (i); \ + (root)[(root)[n__].node_name.prev].node_name.next = \ + (root)[n__].node_name.next; \ + (root)[(root)[n__].node_name.next].node_name.prev = \ + (root)[n__].node_name.prev; \ + } while (0) + +/** UNSAFE macro for removing a node from a list. Resets node data. */ +#define FIO_INDEXED_LIST_REMOVE_RESET(root, node_name, i) \ + do { \ + register const size_t n__ = (i); \ + (root)[(root)[n__].node_name.prev].node_name.next = \ + (root)[n__].node_name.next; \ + (root)[(root)[n__].node_name.next].node_name.prev = \ + (root)[n__].node_name.prev; \ + (root)[n__].node_name.next = (root)[n__].node_name.prev = (n__); \ + } while (0) + +/** Loops through every index in the indexed list, assuming `head` is valid. */ +#define FIO_INDEXED_LIST_EACH(root, node_name, head, pos) \ + for (size_t pos = (head), \ + stooper___hd = (head), \ + stopper___ils___ = 0, \ + pos##___nxt = (root)[(head)].node_name.next; \ + !stopper___ils___; \ + (stopper___ils___ = ((pos = pos##___nxt) == stooper___hd)), \ + pos##___nxt = (root)[pos].node_name.next) + +/** Loops through every index in the indexed list, assuming `head` is valid. */ +#define FIO_INDEXED_LIST_EACH_REVERSED(root, node_name, head, pos) \ + for (size_t pos = ((root)[(head)].node_name.prev), \ + pos##___nxt = \ + ((root)[((root)[(head)].node_name.prev)].node_name.prev), \ + stooper___hd = (head), \ + stopper___ils___ = 0; \ + !stopper___ils___; \ + ((stopper___ils___ = (pos == stooper___hd)), \ + (pos = pos##___nxt), \ + (pos##___nxt = (root)[pos##___nxt].node_name.prev))) +#endif + +/* ***************************************************************************** +Constant-Time Selectors +***************************************************************************** */ + +/** Returns 1 if the expression is true (input isn't zero). */ +FIO_IFUNC uintmax_t fio_ct_true(uintmax_t cond) { + // promise that the highest bit is set if any bits are set, than shift. + return ((cond | (0 - cond)) >> ((sizeof(cond) << 3) - 1)); +} + +/** Returns 1 if the expression is false (input is zero). */ +FIO_IFUNC uintmax_t fio_ct_false(uintmax_t cond) { + // fio_ct_true returns only one bit, XOR will inverse that bit. + return fio_ct_true(cond) ^ 1; +} + +/** Returns `a` if `cond` is boolean and true, returns b otherwise. */ +FIO_IFUNC uintmax_t fio_ct_if_bool(uintmax_t cond, uintmax_t a, uintmax_t b) { + // b^(a^b) cancels b out. 0-1 => sets all bits. + return (b ^ (((uintmax_t)0ULL - (cond & 1)) & (a ^ b))); +} + +/** Returns `a` if `cond` isn't zero (uses fio_ct_true), returns b otherwise. + */ +FIO_IFUNC uintmax_t fio_ct_if(uintmax_t cond, uintmax_t a, uintmax_t b) { + // b^(a^b) cancels b out. 0-1 => sets all bits. + return fio_ct_if_bool(fio_ct_true(cond), a, b); +} + +/** Returns `a` if a >= `b`. */ +FIO_IFUNC intmax_t fio_ct_max(intmax_t a_, intmax_t b_) { + // if b - a is negative, a > b, unless both / one are negative. + const uintmax_t a = a_, b = b_; + return ( + intmax_t)fio_ct_if_bool(((a - b) >> ((sizeof(a) << 3) - 1)) & 1, b, a); +} + +/** Returns `a` if a >= `b`. */ +FIO_IFUNC intmax_t fio_ct_min(intmax_t a_, intmax_t b_) { + // if b - a is negative, a > b, unless both / one are negative. + const uintmax_t a = a_, b = b_; + return ( + intmax_t)fio_ct_if_bool(((a - b) >> ((sizeof(a) << 3) - 1)) & 1, a, b); +} + +/** Returns absolute value. */ +FIO_IFUNC uintmax_t fio_ct_abs(intmax_t i_) { + // if b - a is negative, a > b, unless both / one are negative. + const uintmax_t i = i_; + return (intmax_t)fio_ct_if_bool((i >> ((sizeof(i) << 3) - 1)), 0 - i, i); +} + +/* ***************************************************************************** +Constant-Time Comparison Test +***************************************************************************** */ + +/** A timing attack resistant memory comparison function. */ +FIO_SFUNC _Bool fio_ct_is_eq(const void *a_, const void *b_, size_t bytes) { + uint64_t flag = 0; + const char *a = (const char *)a_; + const char *b = (const char *)b_; + const char *e = a + bytes; + /* any uneven bytes? */ + if (bytes & 63) { + /* consume uneven byte head */ + uint64_t ua[8] FIO_ALIGN(16) = {0}; + uint64_t ub[8] FIO_ALIGN(16) = {0}; + /* all these if statements can run in parallel */ + if (bytes & 32) { + fio_memcpy32(ua, a); + fio_memcpy32(ub, b); + } + if (bytes & 16) { + fio_memcpy16(ua + 4, a + (bytes & 32)); + fio_memcpy16(ub + 4, b + (bytes & 32)); + } + if (bytes & 8) { + fio_memcpy8(ua + 6, a + (bytes & 48)); + fio_memcpy8(ub + 6, b + (bytes & 48)); + } + if (bytes & 4) { + fio_memcpy4((uint32_t *)ua + 14, a + (bytes & 56)); + fio_memcpy4((uint32_t *)ub + 14, b + (bytes & 56)); + } + if (bytes & 2) { + fio_memcpy2((uint16_t *)ua + 30, a + (bytes & 60)); + fio_memcpy2((uint16_t *)ub + 30, b + (bytes & 60)); + } + if (bytes & 1) { + ((char *)ua)[62] = *(a + (bytes & 62)); + ((char *)ub)[62] = *(b + (bytes & 62)); + } + for (size_t i = 0; i < 8; ++i) + flag |= ua[i] ^ ub[i]; + a += bytes & 63; + b += bytes & 63; + } + while (a < e) { + uint64_t ua[8] FIO_ALIGN(16); + uint64_t ub[8] FIO_ALIGN(16); + fio_memcpy64(ua, a); + fio_memcpy64(ub, b); + for (size_t i = 0; i < 8; ++i) + flag |= ua[i] ^ ub[i]; + a += 64; + b += 64; + } + return !flag; +} + +/* ***************************************************************************** +Bit rotation +***************************************************************************** */ + +/** Left rotation for an unknown size element, inlined. */ +#define FIO_LROT(i, bits) \ + (((i) << ((bits) & ((sizeof((i)) << 3) - 1))) | \ + ((i) >> ((-(bits)) & ((sizeof((i)) << 3) - 1)))) + +/** Right rotation for an unknown size element, inlined. */ +#define FIO_RROT(i, bits) \ + (((i) >> ((bits) & ((sizeof((i)) << 3) - 1))) | \ + ((i) << ((-(bits)) & ((sizeof((i)) << 3) - 1)))) + +#if __has_builtin(__builtin_rotateleft8) +/** 8Bit left rotation, inlined. */ +#define fio_lrot8(i, bits) __builtin_rotateleft8(i, bits) +#else +/** 8Bit left rotation, inlined. */ +FIO_IFUNC uint8_t fio_lrot8(uint8_t i, uint8_t bits) { + return ((i << (bits & 7UL)) | (i >> ((-(bits)) & 7UL))); +} +#endif + +#if __has_builtin(__builtin_rotateleft16) +/** 16Bit left rotation, inlined. */ +#define fio_lrot16(i, bits) __builtin_rotateleft16(i, bits) +#else +/** 16Bit left rotation, inlined. */ +FIO_IFUNC uint16_t fio_lrot16(uint16_t i, uint8_t bits) { + return ((i << (bits & 15UL)) | (i >> ((-(bits)) & 15UL))); +} +#endif + +#if __has_builtin(__builtin_rotateleft32) +/** 32Bit left rotation, inlined. */ +#define fio_lrot32(i, bits) __builtin_rotateleft32(i, bits) +#else +/** 32Bit left rotation, inlined. */ +FIO_IFUNC uint32_t fio_lrot32(uint32_t i, uint8_t bits) { + return ((i << (bits & 31UL)) | (i >> ((-(bits)) & 31UL))); +} +#endif + +#if __has_builtin(__builtin_rotateleft64) +/** 64Bit left rotation, inlined. */ +#define fio_lrot64(i, bits) __builtin_rotateleft64(i, bits) +#else +/** 64Bit left rotation, inlined. */ +FIO_IFUNC uint64_t fio_lrot64(uint64_t i, uint8_t bits) { + return ((i << ((bits)&63UL)) | (i >> ((-(bits)) & 63UL))); +} +#endif + +#if __has_builtin(__builtin_rotatrightt8) +/** 8Bit right rotation, inlined. */ +#define fio_rrot8(i, bits) __builtin_rotateright8(i, bits) +#else +/** 8Bit right rotation, inlined. */ +FIO_IFUNC uint8_t fio_rrot8(uint8_t i, uint8_t bits) { + return ((i >> (bits & 7UL)) | (i << ((-(bits)) & 7UL))); +} +#endif + +#if __has_builtin(__builtin_rotateright16) +/** 16Bit right rotation, inlined. */ +#define fio_rrot16(i, bits) __builtin_rotateright16(i, bits) +#else +/** 16Bit right rotation, inlined. */ +FIO_IFUNC uint16_t fio_rrot16(uint16_t i, uint8_t bits) { + return ((i >> (bits & 15UL)) | (i << ((-(bits)) & 15UL))); +} +#endif + +#if __has_builtin(__builtin_rotateright32) +/** 32Bit right rotation, inlined. */ +#define fio_rrot32(i, bits) __builtin_rotateright32(i, bits) +#else +/** 32Bit right rotation, inlined. */ +FIO_IFUNC uint32_t fio_rrot32(uint32_t i, uint8_t bits) { + return ((i >> (bits & 31UL)) | (i << ((-(bits)) & 31UL))); +} +#endif + +#if __has_builtin(__builtin_rotateright64) +/** 64Bit right rotation, inlined. */ +#define fio_rrot64(i, bits) __builtin_rotateright64(i, bits) +#else +/** 64Bit right rotation, inlined. */ +FIO_IFUNC uint64_t fio_rrot64(uint64_t i, uint8_t bits) { + return ((i >> ((bits)&63UL)) | (i << ((-(bits)) & 63UL))); +} +#endif + +#ifdef __SIZEOF_INT128__ +#if __has_builtin(__builtin_rotateright128) && \ + __has_builtin(__builtin_rotateleft128) +/** 128Bit left rotation, inlined. */ +#define fio_lrot128(i, bits) __builtin_rotateleft128(i, bits) +/** 128Bit right rotation, inlined. */ +#define fio_rrot128(i, bits) __builtin_rotateright128(i, bits) +#else +/** 128Bit left rotation, inlined. */ +FIO_IFUNC __uint128_t fio_lrot128(__uint128_t i, uint8_t bits) { + return ((i << ((bits)&127UL)) | (i >> ((-(bits)) & 127UL))); +} +/** 128Bit right rotation, inlined. */ +FIO_IFUNC __uint128_t fio_rrot128(__uint128_t i, uint8_t bits) { + return ((i >> ((bits)&127UL)) | (i << ((-(bits)) & 127UL))); +} +#endif +#endif /* __SIZEOF_INT128__ */ + +#if __LITTLE_ENDIAN__ +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot16 fio_rrot16 +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot32 fio_rrot32 +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot64 fio_rrot64 +#else +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot16 fio_lrot16 +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot32 fio_lrot32 +/** Rotates the bits Forwards (endian specific). */ +#define fio_frot64 fio_lrot64 +#endif + +/* ***************************************************************************** +Byte masking (XOR) +***************************************************************************** */ + +/** + * Masks data using a persistent 64 bit mask. + * + * When the buffer's memory is aligned, the function may perform significantly + * better. + */ +FIO_IFUNC void fio_xmask(char *buf_, size_t len, uint64_t mask) { + register char *buf = (char *)buf_; + for (size_t i = 31; i < len; i += 32) { + for (size_t g = 0; g < 4; ++g) { + fio_u2buf64u(buf, (fio_buf2u64u(buf) ^ mask)); + buf += 8; + } + } + if (len & 16) + for (size_t g = 0; g < 2; ++g) { + fio_u2buf64u(buf, (fio_buf2u64u(buf) ^ mask)); + buf += 8; + } + if (len & 8) { + fio_u2buf64u(buf, (fio_buf2u64u(buf) ^ mask)); + buf += 8; + } + { + uint64_t tmp = 0; + fio_memcpy7x(&tmp, buf, len); + tmp ^= mask; + fio_memcpy7x(buf, &tmp, len); + } +} + +/** + * Masks data using a persistent 64 bit mask. + * + * When the buffer's memory is aligned, the function may perform significantly + * better. + */ +FIO_IFUNC void fio_xmask_cpy(char *restrict dest, + const char *src, + size_t len, + uint64_t mask) { + if (dest == src) { + fio_xmask(dest, len, mask); + return; + } + for (size_t i = 31; i < len; i += 32) { + for (size_t g = 0; g < 4; ++g) { + fio_u2buf64u(dest, (fio_buf2u64u(src) ^ mask)); + dest += 8; + src += 8; + } + } + if (len & 16) + for (size_t g = 0; g < 2; ++g) { + fio_u2buf64u(dest, (fio_buf2u64u(src) ^ mask)); + dest += 8; + src += 8; + } + if (len & 8) { + fio_u2buf64u(dest, (fio_buf2u64u(src) ^ mask)); + dest += 8; + src += 8; + } + if (len & 7) { + uint64_t tmp; + fio_memcpy7x(&tmp, src, len); + tmp ^= mask; + fio_memcpy7x(dest, &tmp, len); + } +} + +/* ***************************************************************************** +Popcount (set bit counting) and Hemming Distance +***************************************************************************** */ + +#if __has_builtin(__builtin_popcountll) +/** performs a `popcount` operation to count the set bits. */ +#define fio_popcount(n) __builtin_popcountll(n) +#else +FIO_IFUNC int fio_popcount(uint64_t n) { + /* for logic, see Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight */ + n = n - ((n >> 1) & 0x5555555555555555); + n = (n & 0x3333333333333333) + ((n >> 2) & 0x3333333333333333); + n = (n + (n >> 4)) & 0x0f0f0f0f0f0f0f0f; + n = n + (n >> 8); + n = n + (n >> 16); + n = n + (n >> 32); + return n & 0x7f; +} +#endif + +#define fio_hemming_dist(n1, n2) fio_popcount(((uint64_t)(n1) ^ (uint64_t)(n2))) + +/* ***************************************************************************** +Bit Mapping (placed here to avoid dependency between FIO_MEMALT and FIO_MATH) +***************************************************************************** */ +#if !defined(__has_builtin) || !__has_builtin(__builtin_ctzll) || \ + !__has_builtin(__builtin_clzll) +FIO_SFUNC size_t fio___single_bit_index_unsafe(uint64_t i) { + switch (i) { + case UINT64_C(0x1): return 0; + case UINT64_C(0x2): return 1; + case UINT64_C(0x4): return 2; + case UINT64_C(0x8): return 3; + case UINT64_C(0x10): return 4; + case UINT64_C(0x20): return 5; + case UINT64_C(0x40): return 6; + case UINT64_C(0x80): return 7; + case UINT64_C(0x100): return 8; + case UINT64_C(0x200): return 9; + case UINT64_C(0x400): return 10; + case UINT64_C(0x800): return 11; + case UINT64_C(0x1000): return 12; + case UINT64_C(0x2000): return 13; + case UINT64_C(0x4000): return 14; + case UINT64_C(0x8000): return 15; + case UINT64_C(0x10000): return 16; + case UINT64_C(0x20000): return 17; + case UINT64_C(0x40000): return 18; + case UINT64_C(0x80000): return 19; + case UINT64_C(0x100000): return 20; + case UINT64_C(0x200000): return 21; + case UINT64_C(0x400000): return 22; + case UINT64_C(0x800000): return 23; + case UINT64_C(0x1000000): return 24; + case UINT64_C(0x2000000): return 25; + case UINT64_C(0x4000000): return 26; + case UINT64_C(0x8000000): return 27; + case UINT64_C(0x10000000): return 28; + case UINT64_C(0x20000000): return 29; + case UINT64_C(0x40000000): return 30; + case UINT64_C(0x80000000): return 31; + case UINT64_C(0x100000000): return 32; + case UINT64_C(0x200000000): return 33; + case UINT64_C(0x400000000): return 34; + case UINT64_C(0x800000000): return 35; + case UINT64_C(0x1000000000): return 36; + case UINT64_C(0x2000000000): return 37; + case UINT64_C(0x4000000000): return 38; + case UINT64_C(0x8000000000): return 39; + case UINT64_C(0x10000000000): return 40; + case UINT64_C(0x20000000000): return 41; + case UINT64_C(0x40000000000): return 42; + case UINT64_C(0x80000000000): return 43; + case UINT64_C(0x100000000000): return 44; + case UINT64_C(0x200000000000): return 45; + case UINT64_C(0x400000000000): return 46; + case UINT64_C(0x800000000000): return 47; + case UINT64_C(0x1000000000000): return 48; + case UINT64_C(0x2000000000000): return 49; + case UINT64_C(0x4000000000000): return 50; + case UINT64_C(0x8000000000000): return 51; + case UINT64_C(0x10000000000000): return 52; + case UINT64_C(0x20000000000000): return 53; + case UINT64_C(0x40000000000000): return 54; + case UINT64_C(0x80000000000000): return 55; + case UINT64_C(0x100000000000000): return 56; + case UINT64_C(0x200000000000000): return 57; + case UINT64_C(0x400000000000000): return 58; + case UINT64_C(0x800000000000000): return 59; + case UINT64_C(0x1000000000000000): return 60; + case UINT64_C(0x2000000000000000): return 61; + case UINT64_C(0x4000000000000000): return 62; + case UINT64_C(0x8000000000000000): return 63; + } + return (0ULL - 1ULL); +} +#endif /* __builtin_ctzll || __builtin_clzll */ + +/** Returns the index of the least significant (lowest) bit. */ +FIO_SFUNC size_t fio_lsb_index_unsafe(uint64_t i) { +#if defined(__has_builtin) && __has_builtin(__builtin_ctzll) + return __builtin_ctzll(i); +#else + return fio___single_bit_index_unsafe(i & ((~i) + 1)); +#endif /* __builtin vs. map */ +} + +/** Returns the index of the most significant (highest) bit. */ +FIO_SFUNC size_t fio_msb_index_unsafe(uint64_t i) { +#if defined(__has_builtin) && __has_builtin(__builtin_clzll) + return 63 - __builtin_clzll(i); +#else + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + i = ((i + 1) >> 1) | (i & ((uint64_t)1ULL << 63)); + return fio___single_bit_index_unsafe(i); +#endif /* __builtin vs. map */ +} + +/* ***************************************************************************** +Byte Value helpers +***************************************************************************** */ + +/** + * Detects a byte where no bits are set (0) within a 4 byte vector. + * + * The zero byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint32_t fio_has_zero_byte32(uint32_t row) { + return (row - UINT32_C(0x01010101)) & (~row & UINT32_C(0x80808080)); +} + +/** + * Detects if `byte` exists within a 4 byte vector. + * + * The requested byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint32_t fio_has_byte32(uint32_t row, uint8_t byte) { + return fio_has_zero_byte32((row ^ (UINT32_C(0x01010101) * byte))); +} + +/** + * Detects a byte where all the bits are set (255) within a 4 byte vector. + * + * The full byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint32_t fio_has_full_byte32(uint32_t row) { + return fio_has_zero_byte32(row); + // return ((row & UINT32_C(0x7F7F7F7F)) + UINT32_C(0x01010101)) & + // (row & UINT32_C(0x80808080)); +} + +/** + * Detects a byte where no bits are set (0) within an 8 byte vector. + * + * The zero byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint64_t fio_has_zero_byte64(uint64_t row) { +#define FIO_HAS_ZERO_BYTE64(row) \ + (((row)-UINT64_C(0x0101010101010101)) & \ + ((~(row)) & UINT64_C(0x8080808080808080))) + return FIO_HAS_ZERO_BYTE64(row); +} + +/** + * Detects a byte where no bits are set (0) within an 8 byte vector. + * + * This variation should NOT be used to build a bitmap, but May be used to + * detect the first occurrence. + */ +FIO_IFUNC uint64_t fio_has_zero_byte_alt64(uint64_t row) { +#define FIO_HAS_ZERO_BYTE64(row) \ + (((row)-UINT64_C(0x0101010101010101)) & \ + ((~(row)) & UINT64_C(0x8080808080808080))) + return FIO_HAS_ZERO_BYTE64(row); +} + +/** + * Detects if `byte` exists within an 8 byte vector. + * + * The requested byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint64_t fio_has_byte64(uint64_t row, uint8_t byte) { + return fio_has_zero_byte64((row ^ (UINT64_C(0x0101010101010101) * byte))); +} + +/** + * Detects a byte where all the bits are set (255) within an 8 byte vector. + * + * The full byte will be be set to 0x80, all other bytes will be 0x0. + */ +FIO_IFUNC uint64_t fio_has_full_byte64(uint64_t row) { +#define FIO_HAS_FULL_BYTE64(row) \ + ((((row)&UINT64_C(0x7F7F7F7F7F7F7F7F)) + UINT64_C(0x0101010101010101)) & \ + (row)&UINT64_C(0x8080808080808080)) + return FIO_HAS_FULL_BYTE64(row); +} + +/** Converts a `fio_has_byteX` result to a bitmap. */ +FIO_IFUNC uint64_t fio_has_byte2bitmap(uint64_t result) { +/** Converts a FIO_HAS_FULL_BYTE64 result to relative position bitmap. */ +#define FIO_HAS_BYTE2BITMAP(result, bit_index) \ + do { \ + (result) = fio_ltole64((result)); /* map little endian to bitmap */ \ + (result) >>= bit_index; /* move bit index to 0x01 */ \ + (result) |= (result) >> 7; /* pack all 0x80 bits into one byte */ \ + (result) |= (result) >> 14; \ + (result) |= (result) >> 28; \ + (result) &= 0xFFU; \ + } while (0) + FIO_HAS_BYTE2BITMAP(result, 7); + return result; +} + +/** Isolates the least significant (lowest) bit. */ +FIO_IFUNC uint64_t fio_bits_lsb(uint64_t i) { return (i & ((~i) + 1)); } + +/** Isolates the most significant (highest) bit. */ +FIO_IFUNC uint64_t fio_bits_msb(uint64_t i) { + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + i = ((i + 1) >> 1) | (i & ((uint64_t)1ULL << 63)); + return i; +} + +/** Returns the index of the most significant (highest) bit. */ +FIO_IFUNC size_t fio_bits_msb_index(uint64_t i) { + if (!i) + goto zero; + return fio_msb_index_unsafe(i); +zero: + return (size_t)-1; +} + +/** Returns the index of the least significant (lowest) bit. */ +FIO_IFUNC size_t fio_bits_lsb_index(uint64_t i) { + if (!i) + goto zero; + return fio_lsb_index_unsafe(i); +zero: + return (size_t)-1; +} + +/* ***************************************************************************** +Bitmap access / manipulation +***************************************************************************** */ + +/** Gets the state of a bit in a bitmap. */ +FIO_IFUNC uint8_t fio_bit_get(void *map, size_t bit) { + return ((((uint8_t *)(map))[(bit) >> 3] >> ((bit)&7)) & 1); +} + +/** Sets the a bit in a bitmap (sets to 1). */ +FIO_IFUNC void fio_bit_set(void *map, size_t bit) { + ((uint8_t *)map)[bit >> 3] |= (uint8_t)(1UL << (bit & 7)); +} + +/** Unsets the a bit in a bitmap (sets to 0). */ +FIO_IFUNC void fio_bit_unset(void *map, size_t bit) { + ((uint8_t *)map)[bit >> 3] &= (uint8_t)(~(1UL << (bit & 7))); +} + +/** Flips the a bit in a bitmap (sets to 0 if 1, sets to 1 if 0). */ +FIO_IFUNC void fio_bit_flip(void *map, size_t bit) { + ((uint8_t *)map)[bit >> 3] ^= (uint8_t)((1UL << (bit & 7))); +} + +/* ***************************************************************************** +Fun Primes +***************************************************************************** */ + +/* Primes with with 16 bits, half of them set. */ +#define FIO_U16_HASH_PRIME0 0xDA23U +#define FIO_U16_HASH_PRIME1 0xB48BU +#define FIO_U16_HASH_PRIME2 0xC917U +#define FIO_U16_HASH_PRIME3 0xD855U +#define FIO_U16_HASH_PRIME4 0xE0B9U +#define FIO_U16_HASH_PRIME5 0xE471U +#define FIO_U16_HASH_PRIME6 0x85CDU +#define FIO_U16_HASH_PRIME7 0xD433U +#define FIO_U16_HASH_PRIME8 0xE951U +#define FIO_U16_HASH_PRIME9 0xA8E5U + +/* Primes with with 32 bits, half of them set. */ +#define FIO_U32_HASH_PRIME0 0xC19F5985UL +#define FIO_U32_HASH_PRIME1 0x8D567931UL +#define FIO_U32_HASH_PRIME2 0x9C178B17UL +#define FIO_U32_HASH_PRIME3 0xA4B842DFUL +#define FIO_U32_HASH_PRIME4 0xB0B94EC9UL +#define FIO_U32_HASH_PRIME5 0xFA9E7084UL +#define FIO_U32_HASH_PRIME6 0xCA63037BUL +#define FIO_U32_HASH_PRIME7 0xD728C15DUL +#define FIO_U32_HASH_PRIME8 0xA872A277UL +#define FIO_U32_HASH_PRIME9 0xF5781551UL + +/* Primes with with 64 bits, half of them set. */ +#define FIO_U64_HASH_PRIME0 0x39664DEECA23D825 +#define FIO_U64_HASH_PRIME1 0x48644F7B3959621F +#define FIO_U64_HASH_PRIME2 0x613A19F5CB0D98D5 +#define FIO_U64_HASH_PRIME3 0x84B56B93C869EA0F +#define FIO_U64_HASH_PRIME4 0x8EE38D13E0D95A8D +#define FIO_U64_HASH_PRIME5 0x92E99EC981F0E279 +#define FIO_U64_HASH_PRIME6 0xDDC3100BEF158BB1 +#define FIO_U64_HASH_PRIME7 0x918F4D38049F78BD +#define FIO_U64_HASH_PRIME8 0xB6C9F8032A35E2D9 +#define FIO_U64_HASH_PRIME9 0xFA2A5F16D2A128D5 + +/* ***************************************************************************** +64bit addition (ADD) / subtraction (SUB) / multiplication (MUL) with carry. +***************************************************************************** */ + +/** Add with carry. */ +FIO_MIFN uint64_t fio_math_addc64(uint64_t a, + uint64_t b, + uint64_t carry_in, + uint64_t *carry_out) { + FIO_ASSERT_DEBUG(carry_out, "fio_math_addc64 requires a carry pointer"); +#if __has_builtin(__builtin_addcll) && UINT64_MAX == LLONG_MAX + return __builtin_addcll(a, b, carry_in, (unsigned long long *)carry_out); +#elif defined(__SIZEOF_INT128__) && 0 + /* This is actually slower as it occupies more CPU registers */ + __uint128_t u = (__uint128_t)a + b + carry_in; + *carry_out = (uint64_t)(u >> 64U); + return (uint64_t)u; +#else + uint64_t u = a + (b += carry_in); + *carry_out = (b < carry_in) + (u < a); + return u; +#endif +} + +/** Multi-precision ADD for `len` 64 bit words a + b. Returns the carry. */ +FIO_MIFN bool fio_math_add(uint64_t *dest, + const uint64_t *a, + const uint64_t *b, + const size_t len) { + uint64_t c = 0; + for (size_t i = 0; i < len; ++i) + dest[i] = fio_math_addc64(a[i], b[i], c, &c); + return (bool)c; +} + +#ifdef __SIZEOF_INT128__ +/** Multi-precision ADD for `bits` long a + b. Returns the carry. */ +FIO_MIFN __uint128_t fio_math_addc128(const __uint128_t a, + const __uint128_t b, + bool carry_in, + bool *carry_out) { + __uint128_t r = a + b + carry_in; + *carry_out = (r < a) | ((r == a) & carry_in); + return r; +} +FIO_MIFN bool fio_math_add2(__uint128_t *dest, + const __uint128_t *a, + const __uint128_t *b, + const size_t len) { + bool c = 0; + for (size_t i = 0; i < len; ++i) + dest[i] = fio_math_addc128(a[i], b[i], c, &c); + return c; +} +#endif + +/** Subtract with borrow. */ +FIO_MIFN uint64_t fio_math_subc64(uint64_t a, + uint64_t b, + uint64_t borrow_in, + uint64_t *borrow_out) { + FIO_ASSERT_DEBUG(borrow_out, "fio_math_subc64 requires a carry pointer"); +#if __has_builtin(__builtin_subcll) && UINT64_MAX == LLONG_MAX + uint64_t u = + __builtin_subcll(a, b, borrow_in, (unsigned long long *)borrow_out); +#elif defined(__SIZEOF_INT128__) + __uint128_t u = (__uint128_t)a - b - borrow_in; + if (borrow_out) + *borrow_out = (uint64_t)(u >> 127U); +#else + uint64_t u = a - b; + a = u > a; + b = u < borrow_in; + u -= borrow_in; + *borrow_out = a + b; +#endif + return (uint64_t)u; +} + +/** Multi-precision SUB for `len` 64 bit words a + b. Returns the borrow. */ +FIO_MIFN uint64_t fio_math_sub(uint64_t *dest, + const uint64_t *a, + const uint64_t *b, + const size_t len) { + uint64_t c = 0; + for (size_t i = 0; i < len; ++i) + dest[i] = fio_math_subc64(a[i], b[i], c, &c); + return c; +} + +/** Multiply with carry out. */ +FIO_MIFN uint64_t fio_math_mulc64(uint64_t a, uint64_t b, uint64_t *carry_out) { + FIO_ASSERT_DEBUG(carry_out, "fio_math_mulc64 requires a carry pointer"); +#if defined(__SIZEOF_INT128__) + __uint128_t r = (__uint128_t)a * b; + *carry_out = (uint64_t)(r >> 64U); +#else /* long multiplication using 32 bits results in up to 64 bit result */ + uint64_t r, midc = 0, lowc = 0; + const uint64_t al = a & 0xFFFFFFFF; + const uint64_t ah = a >> 32; + const uint64_t bl = b & 0xFFFFFFFF; + const uint64_t bh = b >> 32; + const uint64_t lo = al * bl; + const uint64_t hi = ah * bh; + const uint64_t mid = fio_math_addc64(al * bh, ah * bl, 0, &midc); + r = fio_math_addc64(lo, (mid << 32), 0, &lowc); + *carry_out = hi + (mid >> 32) + (midc << 32) + lowc; +#endif + return (uint64_t)r; +} + +/** + * Multi-precision long multiplication for `len` 64 bit words. + * + * `dest` must be `len * 2` long to hold the result. + * + * `a` and `b` must be of equal `len`. + * + * This uses long multiplication, which may be slower for larger numbers. + */ +FIO_IFUNC void fio___math_mul_long(uint64_t *restrict target, + const uint64_t *a, + const uint64_t *b, + const size_t len) { + for (size_t i = 0; i < len; ++i) + target[i] = 0; /* zero out result */ + for (size_t i = 0; i < len; ++i) { +#ifdef __SIZEOF_INT128__ + __uint128_t carry = 0; + for (size_t j = 0; j < len; j++) { + size_t k = i + j; + __uint128_t product = (__uint128_t)a[i] * b[j]; + __uint128_t sum = + (__uint128_t)target[k] + (product & 0xFFFFFFFFFFFFFFFF) + carry; + target[k] = (uint64_t)sum; + carry = (product >> 64) + (sum >> 64); + } + target[i + len] += (uint64_t)carry; +#else + uint64_t ch = 0, cl = 0; + for (size_t j = 0; j < len; ++j) { + /* Multiply hi and lo parts, getting a 128-bit result (hi:lo) */ + uint64_t hi, lo; + lo = fio_math_mulc64(a[i], b[j], &hi); + /* add to result, propagate carry */ + target[i + j] = fio_math_addc64(target[i + j], lo, cl, &cl); + target[i + j + 1] = fio_math_addc64(target[i + j + 1], hi, ch, &ch); + } + target[len - 1] += cl; +#endif + } +} + +/** + * Multi-precision MUL for `len` 64 bit words. + * + * `dest` must be `len * 2` long to hold the result. + * + * `a` and `b` must be of equal `len`. + */ +FIO_IFUNC void fio_math_mul(uint64_t *restrict dest, + const uint64_t *a, + const uint64_t *b, + const size_t len) { + if (!len) { + dest[0] = 0; + } + if (len == 1) { /* route to the correct function */ + dest[0] = fio_math_mulc64(a[0], b[0], dest + 1); + } else { // len < 16 + fio___math_mul_long(dest, a, b, len); + } + /* FIXME!!! (len >= 16) ? Karatsuba-ish / FFT math? : long mul */ +} + +/* ***************************************************************************** +Vector Types (SIMD / Math) +***************************************************************************** */ +#if FIO___HAS_ARM_INTRIN || __has_attribute(vector_size) +#define FIO_HAS_UX 1 +#endif + +/** An unsigned 128bit union type. */ +typedef union { + /** unsigned native word size array, length is system dependent */ + size_t uz[16 / sizeof(size_t)]; + /** known bit word arrays */ + uint64_t u64[2]; + uint32_t u32[4]; + uint16_t u16[8]; + uint8_t u8[16]; + /** vector types, if supported */ +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[1]; + uint32x4_t x32[1]; + uint16x8_t x16[1]; + uint8x16_t x8[1]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(16))) x64[1]; + uint32_t __attribute__((vector_size(16))) x32[1]; + uint16_t __attribute__((vector_size(16))) x16[1]; + uint8_t __attribute__((vector_size(16))) x8[1]; +#endif +#if defined(__SIZEOF_INT128__) + __uint128_t alignment_for_u128_[1]; +#endif +} fio_u128 FIO_ALIGN(16); + +/** An unsigned 256bit union type. */ +typedef union { + size_t uz[32 / sizeof(size_t)]; + uint64_t u64[4]; + uint32_t u32[8]; + uint16_t u16[16]; + uint8_t u8[32]; + fio_u128 u128[2]; +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[2]; + uint32x4_t x32[2]; + uint16x8_t x16[2]; + uint8x16_t x8[2]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(32))) x64[1]; + uint32_t __attribute__((vector_size(32))) x32[1]; + uint16_t __attribute__((vector_size(32))) x16[1]; + uint8_t __attribute__((vector_size(32))) x8[1]; +#endif +#if defined(__SIZEOF_INT128__) + __uint128_t alignment_for_u128_[2]; +#endif +#if defined(__SIZEOF_INT256__) + __uint256_t alignment_for_u256_[1]; +#endif +} fio_u256 FIO_ALIGN(16); + +/** An unsigned 512bit union type. */ +typedef union { + size_t uz[64 / sizeof(size_t)]; + uint64_t u64[8]; + uint32_t u32[16]; + uint16_t u16[32]; + uint8_t u8[64]; + fio_u128 u128[4]; + fio_u256 u256[2]; +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[4]; + uint32x4_t x32[4]; + uint16x8_t x16[4]; + uint8x16_t x8[4]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(64))) x64[1]; + uint32_t __attribute__((vector_size(64))) x32[1]; + uint16_t __attribute__((vector_size(64))) x16[1]; + uint8_t __attribute__((vector_size(64))) x8[1]; +#endif +} fio_u512 FIO_ALIGN(16); + +/** An unsigned 1024bit union type. */ +typedef union { + size_t uz[128 / sizeof(size_t)]; + uint64_t u64[16]; + uint32_t u32[32]; + uint16_t u16[64]; + uint8_t u8[128]; + fio_u128 u128[8]; + fio_u256 u256[4]; + fio_u512 u512[2]; +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[8]; + uint32x4_t x32[8]; + uint16x8_t x16[8]; + uint8x16_t x8[8]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(128))) x64[1]; + uint32_t __attribute__((vector_size(128))) x32[1]; + uint16_t __attribute__((vector_size(128))) x16[1]; + uint8_t __attribute__((vector_size(128))) x8[1]; +#endif +} fio_u1024 FIO_ALIGN(16); + +/** An unsigned 2048bit union type. */ +typedef union { + size_t uz[256 / sizeof(size_t)]; + uint64_t u64[32]; + uint32_t u32[64]; + uint16_t u16[128]; + uint8_t u8[256]; + fio_u128 u128[16]; + fio_u256 u256[8]; + fio_u512 u512[4]; + fio_u1024 u1024[2]; +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[16]; + uint32x4_t x32[16]; + uint16x8_t x16[16]; + uint8x16_t x8[16]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(256))) x64[1]; + uint32_t __attribute__((vector_size(256))) x32[1]; + uint16_t __attribute__((vector_size(256))) x16[1]; + uint8_t __attribute__((vector_size(256))) x8[1]; +#endif +} fio_u2048 FIO_ALIGN(16); + +/** An unsigned 4096bit union type. */ +typedef union { + size_t uz[512 / sizeof(size_t)]; + uint64_t u64[64]; + uint32_t u32[128]; + uint16_t u16[256]; + uint8_t u8[512]; + fio_u128 u128[32]; + fio_u256 u256[16]; + fio_u512 u512[8]; + fio_u1024 u1024[4]; + fio_u2048 u2048[2]; +#if FIO___HAS_ARM_INTRIN + uint64x2_t x64[32]; + uint32x4_t x32[32]; + uint16x8_t x16[32]; + uint8x16_t x8[32]; +#elif __has_attribute(vector_size) + uint64_t __attribute__((vector_size(512))) x64[1]; + uint32_t __attribute__((vector_size(512))) x32[1]; + uint16_t __attribute__((vector_size(512))) x16[1]; + uint8_t __attribute__((vector_size(512))) x8[1]; +#endif +} fio_u4096 FIO_ALIGN(16); + +FIO_ASSERT_STATIC(sizeof(fio_u4096) == 512, "Math type size error!"); + +#define fio_u128_init8(...) ((fio_u128){.u8 = {__VA_ARGS__}}) +#define fio_u128_init16(...) ((fio_u128){.u16 = {__VA_ARGS__}}) +#define fio_u128_init32(...) ((fio_u128){.u32 = {__VA_ARGS__}}) +#define fio_u128_init64(...) ((fio_u128){.u64 = {__VA_ARGS__}}) +#define fio_u256_init8(...) ((fio_u256){.u8 = {__VA_ARGS__}}) +#define fio_u256_init16(...) ((fio_u256){.u16 = {__VA_ARGS__}}) +#define fio_u256_init32(...) ((fio_u256){.u32 = {__VA_ARGS__}}) +#define fio_u256_init64(...) ((fio_u256){.u64 = {__VA_ARGS__}}) +#define fio_u512_init8(...) ((fio_u512){.u8 = {__VA_ARGS__}}) +#define fio_u512_init16(...) ((fio_u512){.u16 = {__VA_ARGS__}}) +#define fio_u512_init32(...) ((fio_u512){.u32 = {__VA_ARGS__}}) +#define fio_u512_init64(...) ((fio_u512){.u64 = {__VA_ARGS__}}) + +#define fio_u1024_init8(...) ((fio_u1024){.u8 = {__VA_ARGS__}}) +#define fio_u1024_init16(...) ((fio_u1024){.u16 = {__VA_ARGS__}}) +#define fio_u1024_init32(...) ((fio_u1024){.u32 = {__VA_ARGS__}}) +#define fio_u1024_init64(...) ((fio_u1024){.u64 = {__VA_ARGS__}}) +#define fio_u2048_init8(...) ((fio_u2048){.u8 = {__VA_ARGS__}}) +#define fio_u2048_init16(...) ((fio_u2048){.u16 = {__VA_ARGS__}}) +#define fio_u2048_init32(...) ((fio_u2048){.u32 = {__VA_ARGS__}}) +#define fio_u2048_init64(...) ((fio_u2048){.u64 = {__VA_ARGS__}}) +#define fio_u4096_init8(...) ((fio_u4096){.u8 = {__VA_ARGS__}}) +#define fio_u4096_init16(...) ((fio_u4096){.u16 = {__VA_ARGS__}}) +#define fio_u4096_init32(...) ((fio_u4096){.u32 = {__VA_ARGS__}}) +#define fio_u4096_init64(...) ((fio_u4096){.u64 = {__VA_ARGS__}}) + +/* ***************************************************************************** +Vector Helpers - memory load operations (implementation starts here) +***************************************************************************** */ + +#define FIO_MATH_TYPE_LOADER(bits, bytes) \ + /** Loads from memory using local-endian. */ \ + FIO_MIFN fio_u##bits fio_u##bits##_load(const void *buf) { \ + fio_u##bits r; \ + fio_memcpy##bytes(&r, buf); \ + return r; \ + } \ + /** Stores to memory using local-endian. */ \ + FIO_IFUNC void fio_u##bits##_store(void *buf, const fio_u##bits a) { \ + fio_memcpy##bytes(buf, &a); \ + } \ + FIO_VECTOR_LOADER_ENDIAN_FUNC(bits, 16) \ + FIO_VECTOR_LOADER_ENDIAN_FUNC(bits, 32) \ + FIO_VECTOR_LOADER_ENDIAN_FUNC(bits, 64) + +#define FIO_VECTOR_LOADER_ENDIAN_FUNC(total_bits, bits) \ + /** Loads vector from memory, reading from little-endian. */ \ + FIO_MIFN fio_u##total_bits fio_u##total_bits##_load_le##bits( \ + const void *buf) { \ + fio_u##total_bits r = fio_u##total_bits##_load(buf); \ + for (size_t i = 0; i < (total_bits / bits); ++i) { \ + r.u##bits[i] = fio_ltole##bits(r.u##bits[i]); \ + } \ + return r; \ + } \ + /** Loads vector from memory, reading from big-endian. */ \ + FIO_MIFN fio_u##total_bits fio_u##total_bits##_load_be##bits( \ + const void *buf) { \ + fio_u##total_bits r = fio_u##total_bits##_load(buf); \ + for (size_t i = 0; i < (total_bits / bits); ++i) { \ + r.u##bits[i] = fio_lton##bits(r.u##bits[i]); \ + } \ + return r; \ + } \ + FIO_MIFN fio_u##total_bits fio_u##total_bits##_bswap##bits( \ + fio_u##total_bits a) { \ + fio_u##total_bits r; \ + for (size_t i = 0; i < (total_bits / bits); ++i) \ + r.u##bits[i] = fio_bswap##bits(a.u##bits[i]); \ + return r; \ + } + +FIO_MATH_TYPE_LOADER(128, 16) +FIO_MATH_TYPE_LOADER(256, 32) +FIO_MATH_TYPE_LOADER(512, 64) +FIO_MATH_TYPE_LOADER(1024, 128) +FIO_MATH_TYPE_LOADER(2048, 256) +FIO_MATH_TYPE_LOADER(4096, 512) + +#undef FIO_MATH_TYPE_LOADER +#undef FIO_VECTOR_LOADER_ENDIAN_FUNC +#undef FIO_VECTOR_LOADER_ENDIAN + +/* ***************************************************************************** +Vector Helpers - Vector Math Operations +***************************************************************************** */ + +#if FIO_HAS_UX && !defined(DEBUG) + +/** Performs `a op b` (+,-, *, etc') as a vector of `bit` long words. */ +#define FIO_MATH_UXXX_OP(t, a, b, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).x##bits) / sizeof((t).x##bits[0])); \ + ++i__) \ + (t).x##bits[i__] = (a).x##bits[i__] op(b).x##bits[i__]; \ + } while (0) +/** Performs `a op b` (+,-, *, etc'), where `b` is a constant. */ +#define FIO_MATH_UXXX_COP(t, a, b, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).x##bits) / sizeof((t).x##bits[0])); \ + ++i__) \ + (t).x##bits[i__] = (a).x##bits[i__] op(b); \ + } while (0) +/** Performs `t = op (a)`. */ +#define FIO_MATH_UXXX_SOP(t, a, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).x##bits) / sizeof((t).x##bits[0])); \ + ++i__) \ + (t).x##bits[i__] = op(a).x##bits[i__]; \ + } while (0) + +#else /* FIO_HAS_UX */ + +#define FIO_MATH_UXXX_OP(t, a, b, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).u##bits) / sizeof((t).u##bits[0])); \ + ++i__) \ + (t).u##bits[i__] = (a).u##bits[i__] op(b).u##bits[i__]; \ + } while (0) +#define FIO_MATH_UXXX_COP(t, a, b, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).u##bits) / sizeof((t).u##bits[0])); \ + ++i__) \ + (t).u##bits[i__] = (a).u##bits[i__] op(b); \ + } while (0) +#define FIO_MATH_UXXX_SOP(t, a, bits, op) \ + do { \ + for (size_t i__ = 0; i__ < (sizeof((t).u##bits) / sizeof((t).u##bits[0])); \ + ++i__) \ + (t).u##bits[i__] = op((a).u##bits[i__]); \ + } while (0) +#endif /* FIO_HAS_UX */ + +/** Performs vector reduction for using `op` (+,-, *, etc'), storing to `t`. */ +#define FIO_MATH_UXXX_REDUCE(t, a, bits, op) \ + do { \ + t = 0; \ + for (size_t i__ = 0; i__ < (sizeof((a).u##bits) / sizeof((a).u##bits[0])); \ + ++i__) \ + (t) = (t)op(a).u##bits[i__]; \ + } while (0) + +#define FIO___UXXX_DEF_OP(total_bits, bits, opnm, op) \ + FIO_IFUNC void fio_u##total_bits##_##opnm##bits( \ + fio_u##total_bits *target, \ + const fio_u##total_bits *a, \ + const fio_u##total_bits *b) { \ + FIO_MATH_UXXX_OP(((target)[0]), ((a)[0]), ((b)[0]), bits, op); \ + } \ + FIO_IFUNC void fio_u##total_bits##_c##opnm##bits(fio_u##total_bits *target, \ + const fio_u##total_bits *a, \ + uint##bits##_t b) { \ + FIO_MATH_UXXX_COP(((target)[0]), ((a)[0]), (b), bits, op); \ + } \ + FIO_MIFN uint##bits##_t fio_u##total_bits##_reduce_##opnm##bits( \ + fio_u##total_bits *a) { \ + uint##bits##_t t; \ + FIO_MATH_UXXX_REDUCE(t, ((a)[0]), bits, op); \ + return t; \ + } +#define FIO___UXXX_DEF_OP2(total_bits, bits, opnm, op) \ + FIO_IFUNC void fio_u##total_bits##_##opnm(fio_u##total_bits *target, \ + const fio_u##total_bits *a, \ + const fio_u##total_bits *b) { \ + FIO_MATH_UXXX_OP(((target)[0]), ((a)[0]), ((b)[0]), bits, op); \ + } + +#define FIO___UXXX_DEF_OP4T_INNER(total_bits, opnm, op) \ + FIO___UXXX_DEF_OP(total_bits, 8, opnm, op) \ + FIO___UXXX_DEF_OP(total_bits, 16, opnm, op) \ + FIO___UXXX_DEF_OP(total_bits, 32, opnm, op) \ + FIO___UXXX_DEF_OP(total_bits, 64, opnm, op) + +#define FIO___UXXX_DEF_OP4T(total_bits) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, add, +) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, sub, -) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, mul, *) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, and, &) \ + FIO___UXXX_DEF_OP2(total_bits, 64, and, &) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, or, |) \ + FIO___UXXX_DEF_OP2(total_bits, 64, or, |) \ + FIO___UXXX_DEF_OP4T_INNER(total_bits, xor, ^) \ + FIO___UXXX_DEF_OP2(total_bits, 64, xor, ^) \ + FIO_IFUNC void fio_u##total_bits##_inv(fio_u##total_bits *target, \ + const fio_u##total_bits *a) { \ + FIO_MATH_UXXX_SOP(((target)[0]), ((a)[0]), 64, ~); \ + } + +FIO___UXXX_DEF_OP4T(128) +FIO___UXXX_DEF_OP4T(256) +FIO___UXXX_DEF_OP4T(512) +FIO___UXXX_DEF_OP4T(1024) +FIO___UXXX_DEF_OP4T(2048) +FIO___UXXX_DEF_OP4T(4096) + +#undef FIO___UXXX_DEF_OP4T +#undef FIO___UXXX_DEF_OP4T_INNER +#undef FIO___UXXX_DEF_OP +#undef FIO___UXXX_DEF_OP2 +/* ***************************************************************************** +Vector Helpers - Multi-Precision Math +***************************************************************************** */ + +#undef FIO___VMATH_DEF_LARGE_ADD_SUB +#define FIO___VMATH_DEF_LARGE_ADD_SUB(bits) \ + /** Performs A+B, storing in `result`. Return the carry bit (1 or 0). */ \ + FIO_MIFN uint64_t fio_u##bits##_add(fio_u##bits *result, \ + const fio_u##bits *a, \ + const fio_u##bits *b) { \ + uint64_t carry = 0; \ + for (size_t i = 0; i < (bits / 64); ++i) { \ + uint64_t sum = a->u64[i] + b->u64[i] + carry; \ + carry = (sum < a->u64[i]) | (carry & (sum == a->u64[i])); \ + result->u64[i] = sum; \ + } \ + return carry; \ + } \ + /** Performs A-B, storing in `result`. Returns the borrow bit (1 or 0). */ \ + FIO_MIFN uint64_t fio_u##bits##_sub(fio_u##bits *result, \ + const fio_u##bits *a, \ + const fio_u##bits *b) { \ + uint64_t borrow = 0; \ + for (size_t i = 0; i < (bits / 64); ++i) { \ + uint64_t diff = a->u64[i] - b->u64[i] - borrow; \ + borrow = \ + ((a->u64[i] < b->u64[i]) | ((a->u64[i] == b->u64[i]) & borrow)); \ + result->u64[i] = diff; \ + } \ + return borrow; \ + } \ + /** Returns -1, 0, or 1 if a < b, a == b or a > a (respectively). */ \ + FIO_MIFN int fio_u##bits##_cmp(const fio_u##bits *a, const fio_u##bits *b) { \ + unsigned is_eq = 1; \ + unsigned is_bigger = 0; \ + for (size_t i = (bits / 64); i--;) { \ + is_bigger |= (is_eq & (a->u64[i] > b->u64[i])); \ + is_eq &= (unsigned)(a->u64[i] == b->u64[i]); \ + } \ + return (is_eq - 1) + (is_bigger << 1); \ + } + +#undef FIO___VMATH_DEF_LARGE_MUL +#define FIO___VMATH_DEF_LARGE_MUL(dbl_bits, bits) \ + /** Multiplies A and B, storing the result in `result`. */ \ + FIO_SFUNC void fio_u##bits##_mul(fio_u##dbl_bits *result, \ + const fio_u##bits *a, \ + const fio_u##bits *b) { \ + fio_math_mul(result->u64, a->u64, b->u64, (bits / 64)); \ + } \ + FIO_SFUNC void fio_u##bits##_montgomery_mul(fio_u##bits *result, \ + const fio_u##bits *a, \ + const fio_u##bits *b, \ + const fio_u##bits *N, \ + const fio_u##bits *N_dash) { \ + fio_u##dbl_bits u; \ + fio_u##dbl_bits t, mN; \ + /* Step 1: t = a * b */ \ + fio_u##bits##_mul(&t, a, b); \ + /* Step 2: m = ((t Mod R) * N_dash) mod R */ \ + fio_u##bits##_mul(&mN, t.u##bits, N_dash); \ + /* Step 3: u = (t + m * N) */ \ + fio_u##bits##_mul(&mN, mN.u##bits, N); \ + (void)fio_u##dbl_bits##_add(&u, &t, &mN); \ + /* Step 4: Constant Time select, if u >= N, then u = u - N */ \ + bool selector = (bool)fio_u##bits##_sub(u.u##bits, u.u##bits + 1, N); \ + /* Step 5: Set result */ \ + /* result is u.u##bits[0] if equal or bigger */ \ + /* result is u.u##bits[1] if smaller */ \ + /* TODO: use mask instead of implied `if` selector? memory not hot? */ \ + *result = u.u##bits[selector]; \ + } + +FIO___VMATH_DEF_LARGE_ADD_SUB(128) +FIO___VMATH_DEF_LARGE_ADD_SUB(256) +FIO___VMATH_DEF_LARGE_ADD_SUB(512) +FIO___VMATH_DEF_LARGE_ADD_SUB(1024) +FIO___VMATH_DEF_LARGE_ADD_SUB(2048) +FIO___VMATH_DEF_LARGE_ADD_SUB(4096) + +FIO___VMATH_DEF_LARGE_MUL(256, 128) +FIO___VMATH_DEF_LARGE_MUL(512, 256) +FIO___VMATH_DEF_LARGE_MUL(1024, 512) +FIO___VMATH_DEF_LARGE_MUL(2048, 1024) +FIO___VMATH_DEF_LARGE_MUL(4096, 2048) + +#undef FIO___VMATH_DEF_LARGE_ADD_SUB +#undef FIO___VMATH_DEF_LARGE_MUL + +/* ***************************************************************************** +String and Buffer Information Containers + Helper Macros +***************************************************************************** */ + +/** An information type for reporting the string's state. */ +typedef struct fio_str_info_s { + /** The string's length, if any. */ + size_t len; + /** The string's buffer (pointer to first byte) or NULL on error. */ + char *buf; + /** The buffer's capacity. Zero (0) indicates the buffer is read-only. */ + size_t capa; +} fio_str_info_s; + +/** An information type for reporting/storing buffer data (no `capa`). */ +typedef struct fio_buf_info_s { + /** The buffer's length, if any. */ + size_t len; + /** The buffer's address (may be NULL if no buffer). */ + char *buf; +} fio_buf_info_s; + +/** Compares two `fio_str_info_s` objects for content equality. */ +#define FIO_STR_INFO_IS_EQ(s1, s2) \ + ((s1).len == (s2).len && \ + (!(s1).len || (s1).buf == (s2).buf || \ + ((s1).buf && (s2).buf && (s1).buf[0] == (s2).buf[0] && \ + !FIO_MEMCMP((s1).buf, (s2).buf, (s1).len)))) + +/** Compares two `fio_buf_info_s` objects for content equality. */ +#define FIO_BUF_INFO_IS_EQ(s1, s2) FIO_STR_INFO_IS_EQ((s1), (s2)) + +/** A NULL fio_str_info_s. */ +#define FIO_STR_INFO0 ((fio_str_info_s){0}) + +/** Converts a C String into a fio_str_info_s. */ +#define FIO_STR_INFO1(str) \ + ((fio_str_info_s){.len = ((str) ? FIO_STRLEN((str)) : 0), .buf = (str)}) + +/** Converts a String with a known length into a fio_str_info_s. */ +#define FIO_STR_INFO2(str, length) \ + ((fio_str_info_s){.len = (length), .buf = (str)}) + +/** Converts a String with a known length and capacity into a fio_str_info_s. */ +#define FIO_STR_INFO3(str, length, capacity) \ + ((fio_str_info_s){.len = (length), .buf = (str), .capa = (capacity)}) + +/** A NULL fio_buf_info_s. */ +#define FIO_BUF_INFO0 ((fio_buf_info_s){0}) + +/** Converts a C String into a fio_buf_info_s. */ +#define FIO_BUF_INFO1(str) \ + ((fio_buf_info_s){.len = ((str) ? FIO_STRLEN((str)) : 0), .buf = (str)}) + +/** Converts a String with a known length into a fio_buf_info_s. */ +#define FIO_BUF_INFO2(str, length) \ + ((fio_buf_info_s){.len = (length), .buf = (str)}) + +/** Converts a fio_buf_info_s into a fio_str_info_s. */ +#define FIO_BUF2STR_INFO(buf_info) \ + ((fio_str_info_s){.len = (buf_info).len, .buf = (buf_info).buf}) + +/** Converts a fio_buf_info_s into a fio_str_info_s. */ +#define FIO_STR2BUF_INFO(str_info) \ + ((fio_buf_info_s){.len = (str_info).len, .buf = (str_info).buf}) + +/** Creates a stack fio_str_info_s variable `name` with `capacity` bytes. */ +#define FIO_STR_INFO_TMP_VAR(name, capacity) \ + char fio___stack_mem___##name[(capacity) + 1]; \ + fio___stack_mem___##name[(capacity)] = 0; /* guard */ \ + fio_str_info_s name = (fio_str_info_s) { \ + .buf = fio___stack_mem___##name, .capa = (capacity) \ + } + +/** Tests to see if memory reallocation happened. */ +#define FIO_STR_INFO_TMP_IS_REALLOCATED(name) \ + (fio___stack_mem___##name != name.buf) + +/* ***************************************************************************** +UTF-8 Support (basic) +***************************************************************************** */ + +#ifndef FIO_UTF8_ALLOW_IF +/* UTF-8 Constant Time? (0 = avoid mis-predictions; 1 = mostly ascii) */ +#define FIO_UTF8_ALLOW_IF 1 + +#endif + +/* Returns the number of bytes required to UTF-8 encoded a code point `u` */ +FIO_IFUNC unsigned fio_utf8_code_len(uint32_t u) { + uint32_t len = (1U + ((uint32_t)(u) > 127) + ((uint32_t)(u) > 2047) + + ((uint32_t)(u) > 65535)); + len &= (uint32_t)((uint32_t)(u) > ((1U << 21) - 1)) - 1; + return len; +} + +/** Returns 1-4 (UTF-8 char length), 8 (middle of a char) or 0 (invalid). */ +FIO_IFUNC unsigned fio_utf8_char_len_unsafe(uint8_t c) { + /* Ruby script for map: + map = []; + 32.times { | i | + map << (((i & 0b10000) == 0b00000) ? 1 + : ((i & 0b11000) == 0b10000) ? 8 + : ((i & 0b11100) == 0b11000) ? 2 + : ((i & 0b11110) == 0b11100) ? 3 + : ((i & 0b11111) == 0b11110) ? 4 + : 0) + }; puts "static const uint8_t map[32] = {#{ map.join(', ')} };" + */ + static const uint8_t map[32] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, + 8, 8, 2, 2, 2, 2, 3, 3, 4, 0}; + return map[c >> 3]; +} + +/** Returns the number of valid UTF-8 bytes used by first char at `str`. */ +FIO_IFUNC unsigned fio_utf8_char_len(const void *str_) { + unsigned r, tst; + const uint8_t *s = (uint8_t *)str_; + r = fio_utf8_char_len_unsafe(*s) & 7; +#if FIO_UTF8_ALLOW_IF + if (r < 2) + return r; + tst = 1; + tst += (fio_utf8_char_len_unsafe(s[tst]) >> 3) & (r > 3); + tst += (fio_utf8_char_len_unsafe(s[tst]) >> 3) & (r > 2); + tst += (fio_utf8_char_len_unsafe(s[tst]) >> 3); + if (r != tst) + r = 0; +#else + tst = (r > 0); + tst += ((fio_utf8_char_len_unsafe(s[tst]) >> 3) & (r > 3)); + tst += ((fio_utf8_char_len_unsafe(s[tst]) >> 3) & (r > 2)); + tst += (fio_utf8_char_len_unsafe(s[tst]) >> 3); + r &= 0U - (r == tst); +#endif + + return r; +} + +/** Writes code point to `dest` using UFT-8. Returns number of bytes written. */ +FIO_IFUNC unsigned fio_utf8_write(void *dest_, uint32_t u) { + const uint8_t len = fio_utf8_code_len(u); + uint8_t *dest = (uint8_t *)dest_; +#if FIO_UTF8_ALLOW_IF + if (len < 2) { /* writes, but doesn't report on len == 0 */ + *dest = u; + return len; + } + const uint8_t offset = 0xF0U << (4U - len); + const uint8_t head = 0x80U << (len < 2); + const uint8_t mask = 63U; + *(dest) = offset | ((u) >> (((len - 1) << 3) - ((len - 1) << 1))); + (dest) += 1; + *(dest) = head | (((u) >> 12) & mask); + (dest) += (len > 3); + *(dest) = head | (((u) >> 6) & mask); + (dest) += (len > 2); + *(dest) = head | ((u)&mask); + return len; +#else + const uint8_t offset = 0xF0U << (4U - len); + const uint8_t head = 0x80U << (len < 2); + const uint8_t mask = 63U; + *dest = (uint8_t)u; + dest += (len == 1); + *dest = offset | ((u) >> (((len - 1) << 3) - ((len - 1) << 1))); + dest += (len > 1); + *dest = head | (((u) >> 12) & mask); + dest += (len > 3); + *dest = head | (((u) >> 6) & mask); + dest += (len > 2); + *dest = head | ((u)&mask); + return len; +#endif +} + +/** + * Decodes the first UTF-8 char at `str` and returns its code point value. + * + * Advances the pointer at `str` by the number of bytes consumed (read). + */ +FIO_IFUNC uint32_t fio_utf8_read(char **str) { + const uint8_t *s = *(const uint8_t **)str; + unsigned len = fio_utf8_char_len(s); + *str += len; +#if FIO_UTF8_ALLOW_IF + if (!len) + return 0; + if (len == 1) + return *s; + const uint32_t t2 = (len > 2); + const uint32_t t3 = 1 + (len > 3); + const uint32_t t3a = (len > 2) + (len > 3); + const uint32_t t4 = len - 1; + return ((uint32_t)(s[0] & (63 >> t4)) << ((t4 << 3) - (t4 << 1))) | + ((uint32_t)(s[1] & 63) << ((t3a << 3) - (t3a << 1))) | + ((uint32_t)(s[t3] & 63) << ((t2 << 3) - (t2 << 1))) | + ((uint32_t)(s[t4] & 63)); +#else + const uint32_t t1 = (len > 1); + const uint32_t t2 = (len > 2); + const uint32_t t3 = t2 + (len > 3); + const uint32_t t3a = (len > 2) + (len > 3); + const uint32_t t4 = len - t1; + uint32_t r1 = *s & ((uint32_t)0UL - (len == 1)); + uint32_t r2 = ((uint32_t)(s[0] & (63 >> t4)) << ((t4 << 3) - (t4 << 1))) | + ((uint32_t)(s[t1] & 63) << ((t3a << 3) - (t3a << 1))) | + ((uint32_t)(s[t3] & 63) << ((t2 << 3) - (t2 << 1))) | + ((uint32_t)(s[t4] & 63)); + r2 &= (uint32_t)0UL - t1; + return (r1 | r2); +#endif +} + +/** Decodes the first UTF-8 char at `str` and returns its code point value. */ +FIO_IFUNC uint32_t fio_utf8_peek(const char *str) { + return fio_utf8_read((char **)&str); +} + +/* ***************************************************************************** +C++ extern end +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +} +#endif + +/* ***************************************************************************** +End persistent segment (end include-once guard) +***************************************************************************** */ +#endif /* H___FIO_CORE___H */ +/* ***************************************************************************** + + + + Multi-Inclusion Macros + + + +***************************************************************************** */ + +/* ***************************************************************************** +Tests Inclusion (everything + MEMALT) +***************************************************************************** */ +#if !defined(FIO___RECURSIVE_INCLUDE) && \ + (defined(FIO_TEST_ALL) || defined(FIO___TEST_MACRO_SUSPENDED)) && \ + !defined(H___FIO_TESTS_INC_FINISHED___H) + +/* Inclusion cycle three - facil.io memory allocator for all else. */ +#if !defined(H___FIO_EVERYTHING___H) /* include everything first, then test */ +#undef FIO_TEST_ALL +#define FIO___TEST_MACRO_SUSPENDED +#undef FIO_LEAK_COUNTER +#define FIO_LEAK_COUNTER 1 +#define FIO_EVERYTHING +#else /* define test inclusion */ +#define H___FIO_TESTS_INC_FINISHED___H +#undef FIO___TEST_MACRO_SUSPENDED +#define FIO_TEST_ALL +#endif + +#endif /* FIO_TEST_ALL */ + +/* ***************************************************************************** +Special `extern` support FIO_BASIC, FIO_EVERYTHING, etc' +***************************************************************************** */ +#if !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO_EXTERN) && \ + (defined(FIO_TEST_ALL) || defined(FIO_EVERYTHING) || defined(FIO_BASIC)) +#if defined(FIO_EXTERN) && ((FIO_EXTERN + 1) < 3) +#undef FIO_EXTERN +#define FIO_EXTERN 2 +#define FIO_EVERYTHING___REMOVE_EXTERN 1 +#endif +#if defined(FIO_EXTERN_COMPLETE) && ((FIO_EXTERN_COMPLETE + 1) < 3) +#undef FIO_EXTERN_COMPLETE +#define FIO_EXTERN_COMPLETE 2 +#define FIO_EVERYTHING___REMOVE_EXTERN_COMPLETE 1 +#endif +#endif + +/* ***************************************************************************** +Everything Inclusion +***************************************************************************** */ +#if !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO_EVERYTHING) && \ + !defined(H___FIO_EVERYTHING___H) + +#if !defined(H___FIO_EVERYTHING1___H) +#define H___FIO_EVERYTHING1___H +#undef FIO_FIOBJ +#undef FIO_HTTP +#undef FIO_MALLOC +#undef FIO_MUSTACHE +#undef FIO_PUBSUB +#undef FIO_IO +#undef FIOBJ_MALLOC +#define FIO_CLI +#define FIO_CORE +#define FIO_CRYPT +#define FIO_SIGNAL +#define FIO_SOCK +#define FIO_THREADS + +#else +#undef H___FIO_EVERYTHING1___H +#undef FIO_EVERYTHING +#define H___FIO_EVERYTHING___H +#undef FIO_MEMALT +#define FIO_FIOBJ +#define FIO_HTTP +#define FIO_IO +#define FIO_MALLOC +#define FIO_MUSTACHE +#define FIO_PUBSUB +#define FIO_MEMALT + +#endif + +#define FIO___INCLUDE_AGAIN +#endif /* FIO_EVERYTHING */ + +/* ***************************************************************************** +FIO_BASIC Basic Kitchen Sink Inclusion +***************************************************************************** */ +#if !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO_BASIC) && \ + !defined(H___FIO_BASIC___H) + +#if !defined(H___FIO_BASIC_ROUND1___H) +#define H___FIO_BASIC_ROUND1___H +#undef FIO_CLI +#undef FIO_CORE +#undef FIO_CRYPT +#undef FIO_FIOBJ +#undef FIO_MALLOC +#undef FIO_MUSTACHE +#undef FIO_THREADS +#undef FIOBJ_MALLOC +#define FIO_CLI +#define FIO_CORE +#define FIO_CRYPT +#define FIO_THREADS + +#elif !defined(H___FIO_BASIC_ROUND2___H) +#define H___FIO_BASIC_ROUND2___H +#define FIO_FIOBJ +#define FIO_MUSTACHE +#define FIOBJ_MALLOC + +#else +#define H___FIO_BASIC___H +#undef H___FIO_BASIC_ROUND1___H +#undef H___FIO_BASIC_ROUND2___H +#undef FIO_BASIC +#define FIO_MALLOC +#endif + +#define FIO___INCLUDE_AGAIN +#endif /* FIO_BASIC */ +/* ***************************************************************************** +FIO_CRYPT Poor-man's Cryptographic Elements +***************************************************************************** */ +#if defined(FIO_CRYPT) +#undef FIO_CHACHA +#undef FIO_ED25519 +#undef FIO_SHA1 +#undef FIO_SHA2 +#define FIO_CHACHA +#define FIO_ED25519 +#define FIO_SHA1 +#define FIO_SHA2 +#undef FIO_CRYPT +#endif /* FIO_CRYPT */ + +/* ***************************************************************************** +FIO_CORE Core Inclusion +***************************************************************************** */ +#if defined(FIO_CORE) +#undef FIO_ATOL +#undef FIO_FILES +#undef FIO_GLOB_MATCH +#undef FIO_LOG +#undef FIO_MATH +#undef FIO_RAND +#undef FIO_STATE +#undef FIO_TIME +#undef FIO_URL +#undef FIO_CORE +#define FIO_ATOL +#define FIO_FILES +#define FIO_GLOB_MATCH +#define FIO_LOG +#define FIO_MATH +#define FIO_RAND +#define FIO_STATE +#define FIO_TIME +#define FIO_URL +#endif + +/* ***************************************************************************** + + + + Shortcut Macros + + + +***************************************************************************** */ + +/* ***************************************************************************** +Memory Allocation - FIO_MALLOC as a "global" default memory allocator +***************************************************************************** */ +/* FIO_MALLOC defines a "global" default memory allocator */ +#if defined(FIO_MALLOC) && !defined(H___FIO_MALLOC___H) +#define H___FIO_MALLOC___H +#ifndef FIO_MEMORY_NAME +#define FIO_MEMORY_NAME fio +#endif +#ifndef FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG +/* for a general allocator, increase system allocation size to 8Mb */ +#define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 23 +#endif + +#ifndef FIO_MEMORY_CACHE_SLOTS +/* for a general allocator, increase cache size */ +#define FIO_MEMORY_CACHE_SLOTS 8 +#endif + +#ifndef FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG +/* set fragmentation cost at 0.25Mb blocks */ +#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 5 +#endif + +#ifndef FIO_MEMORY_ENABLE_BIG_ALLOC +/* support big allocations using undivided memory chunks */ +#define FIO_MEMORY_ENABLE_BIG_ALLOC 1 +#endif + +#ifndef FIO_MEMORY_INITIALIZE_ALLOCATIONS +/* should memory be initialized to zero? */ +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 1 +#endif +/* ***************************************************************************** +Memory Allocation - FIO_MALLOC defines a FIOBJ dedicated memory allocator +***************************************************************************** */ +/* FIOBJ_MALLOC defines a FIOBJ dedicated memory allocator */ +#elif defined(FIOBJ_MALLOC) && !defined(H___FIOBJ_MALLOC___H) +#define H___FIOBJ_MALLOC___H +#ifndef FIO_MEMORY_NAME +#define FIO_MEMORY_NAME fiobj_mem +#endif +#ifndef FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG +/* 4Mb per system call */ +#define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 22 +#endif +#ifndef FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG +/* fight fragmentation */ +#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 4 +#endif +#ifndef FIO_MEMORY_CACHE_SLOTS +/* cache up to 64Mb */ +#define FIO_MEMORY_CACHE_SLOTS 16 +#endif +#endif /* FIOBJ_MALLOC / FIO_MALLOC*/ + +#undef FIOBJ_MALLOC +#undef FIO_MALLOC +/* ***************************************************************************** +FIO_SORT_NAME naming +***************************************************************************** */ + +#if defined(FIO_SORT_TYPE) && !defined(FIO_SORT_NAME) +#define FIO_SORT_NAME FIO_NAME(FIO_SORT_TYPE, vec) +#endif + +/* ***************************************************************************** +FIO_MAP Ordering & Naming Shortcut +***************************************************************************** */ +#if defined(FIO_UMAP_NAME) +#define FIO_MAP_NAME FIO_UMAP_NAME +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 0 +#elif defined(FIO_OMAP_NAME) +#define FIO_MAP_NAME FIO_OMAP_NAME +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 1 +#endif + +/* ***************************************************************************** + + + + Higher Level Dependencies (i.e. Server module) + + + +***************************************************************************** */ + +#if defined(FIO_HTTP) +#undef FIO_HTTP_HANDLE +#define FIO_HTTP_HANDLE +#endif + +#if defined(FIO_HTTP) +#undef FIO_PUBSUB +#define FIO_PUBSUB +#endif + +#if defined(FIO_HTTP) || defined(FIO_PUBSUB) +#undef FIO_IO +#define FIO_IO +#endif + +#if defined(FIO_HTTP) || defined(FIO_IO) +#undef FIO_POLL +#define FIO_POLL +#endif + +/* ***************************************************************************** + + + + Mid Level Dependencies (i.e., types / helpers) + + + +***************************************************************************** */ + +#if defined(FIO_FIOBJ) +#define FIO_MUSTACHE +#define FIO_JSON +#endif + +#if defined(FIO_HTTP) +#undef FIO_HTTP1_PARSER +#define FIO_HTTP1_PARSER +#endif + +#if defined(FIO_HTTP) +#undef FIO_WEBSOCKET_PARSER +#define FIO_WEBSOCKET_PARSER +#endif + +#if defined(FIO_POLL) || defined(FIO_IO) || defined(FIO_PUBSUB) +#undef FIO_SOCK +#define FIO_SOCK +#endif + +#if defined(FIO_HTTP_HANDLE) || defined(FIO_QUEUE) || defined(FIO_FIOBJ) || \ + defined(FIO_LEAK_COUNTER) || defined(FIO_MEMORY_NAME) || defined(FIO_POLL) +#undef FIO_STATE +#define FIO_STATE +#endif + +#if defined(FIO_HTTP_HANDLE) || defined(FIO_STR_NAME) || \ + defined(FIO_STR_SMALL) || defined(FIO_ARRAY_TYPE_STR) || \ + defined(FIO_MAP_KEY_KSTR) || defined(FIO_MAP_KEY_BSTR) || \ + (defined(FIO_MAP_NAME) && !defined(FIO_MAP_KEY)) || \ + defined(FIO_MUSTACHE) || defined(FIO_MAP2_NAME) +#undef FIO_STR +#define FIO_STR +#endif + +#if defined(FIO_IO) +#undef FIO_STREAM +#define FIO_STREAM +#endif + +#if defined(FIO_IO) +#undef FIO_QUEUE +#define FIO_QUEUE +#endif + +#if defined(FIO_HTTP_HANDLE) || defined(FIO_QUEUE) +#undef FIO_TIME +#define FIO_TIME +#endif + +/* ***************************************************************************** + + + + Crypto Elements Dependencies (i.e., SHA-1 etc') + + + +***************************************************************************** */ +#if defined(FIO_PUBSUB) +#define FIO_CHACHA +#endif + +#if defined(FIO_HTTP_HANDLE) +#define FIO_SHA1 +#endif + +#if defined(FIO_PUBSUB) +#define FIO_SHA2 +#endif + +#if defined(FIO_CHACHA) || defined(FIO_SHA1) || defined(FIO_SHA2) +#undef FIO_CRYPTO_CORE +#define FIO_CRYPTO_CORE +#endif +/* ***************************************************************************** + + + + Core Level Dependencies (i.e., atomics, etc') + + + +***************************************************************************** */ + +#if defined(FIO_STR) || defined(FIO_HTTP) || defined(FIO_STREAM) +#undef FIO_FILES +#define FIO_FILES +#endif + +#if defined(FIO_CLI) || defined(FIO_HTTP_HANDLE) || \ + defined(FIO_HTTP1_PARSER) || defined(FIO_JSON) || defined(FIO_STR) || \ + defined(FIO_TIME) || defined(FIO_FILES) +#undef FIO_ATOL +#define FIO_ATOL +#endif + +#if defined(FIO_PUBSUB) +#undef FIO_GLOB_MATCH +#define FIO_GLOB_MATCH +#endif + +#if defined(FIO_CLI) || defined(FIO_MEMORY_NAME) || defined(FIO_POLL) || \ + defined(FIO_STATE) || defined(FIO_HTTP_HANDLE) +#undef FIO_IMAP_CORE +#define FIO_IMAP_CORE +#endif + +#if defined(FIO_CHACHA) || defined(FIO_SHA2) +#undef FIO_MATH +#define FIO_MATH +#endif + +#if defined(FIO_CLI) || defined(FIO_FILES) || defined(FIO_HTTP_HANDLE) || \ + defined(FIO_MEMORY_NAME) || defined(FIO_POLL) || defined(FIO_STATE) || \ + defined(FIO_STR) +#undef FIO_RAND +#define FIO_RAND +#endif + +#if defined(FIO_IO) +#undef FIO_SIGNAL +#define FIO_SIGNAL +#endif + +#if defined(FIO_MEMORY_NAME) || defined(FIO_QUEUE) || \ + (defined(DEBUG) && defined(FIO_STATE)) +#undef FIO_THREADS +#define FIO_THREADS +#endif + +#if defined(FIO_SOCK) +#undef FIO_URL +#define FIO_URL +#endif +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** +C++ extern start +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* ***************************************************************************** + + + + + Common internal Macros + + + +These are re-defined for ever `include` cycle +***************************************************************************** */ + +/* ***************************************************************************** +Memory allocation macros +***************************************************************************** */ + +#if defined(FIO_MEM_RESET) || !defined(FIO_MEM_REALLOC) || \ + !defined(FIO_MEM_FREE) + +#undef FIO_MEM_REALLOC +#undef FIO_MEM_FREE +#undef FIO_MEM_REALLOC_IS_SAFE +#undef FIO_MEM_RESET + +/* if a global allocator was previously defined route macros to fio_malloc */ +#if defined(H___FIO_MALLOC___H) +/** Reallocates memory, copying (at least) `copy_len` if necessary. */ +#define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) \ + fio_realloc2((ptr), (new_size), (copy_len)) +/** Frees allocated memory. */ +#define FIO_MEM_FREE(ptr, size) fio_free((ptr)) +/** Set to true of internall allocator is used (memory returned set to zero). */ +#define FIO_MEM_REALLOC_IS_SAFE fio_realloc_is_safe() + +#else /* H___FIO_MALLOC___H */ +/** Reallocates memory, copying (at least) `copy_len` if necessary. */ +#define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) \ + realloc((ptr), (new_size)) +/** Frees allocated memory. */ +#define FIO_MEM_FREE(ptr, size) free((ptr)) +/** Set to true of internall allocator is used (memory returned set to zero). */ +#define FIO_MEM_REALLOC_IS_SAFE 0 +#endif /* H___FIO_MALLOC___H */ + +#endif /* defined(FIO_MEM_REALLOC) */ + +/** FIO_MEMORY_DISABLE disables all custom memory allocators. */ +#if defined(FIO_MEMORY_DISABLE) +#ifndef FIO_MALLOC_TMP_USE_SYSTEM +#define FIO_MALLOC_TMP_USE_SYSTEM 1 +#endif +#endif + +/* recursive? */ +#if !defined(FIO_MEM_REALLOC_) || !defined(FIO_MEM_FREE_) +#undef FIO_MEM_REALLOC_ +#undef FIO_MEM_FREE_ +#undef FIO_MEM_REALLOC_IS_SAFE_ + +#ifdef FIO_MALLOC_TMP_USE_SYSTEM /* force malloc */ +#define FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) \ + realloc((ptr), (new_size)) +#define FIO_MEM_FREE_(ptr, size) free((ptr)) +#define FIO_MEM_REALLOC_IS_SAFE_ 0 + +#else /* FIO_MALLOC_TMP_USE_SYSTEM */ +#define FIO_MEM_REALLOC_ FIO_MEM_REALLOC +#define FIO_MEM_FREE_ FIO_MEM_FREE +#define FIO_MEM_REALLOC_IS_SAFE_ FIO_MEM_REALLOC_IS_SAFE +#endif /* FIO_MALLOC_TMP_USE_SYSTEM */ + +#endif /* !defined(FIO_MEM_REALLOC_)... */ + +/* ***************************************************************************** +Locking selector +***************************************************************************** */ + +#ifndef FIO_USE_THREAD_MUTEX_TMP +#define FIO_USE_THREAD_MUTEX_TMP FIO_USE_THREAD_MUTEX +#endif + +#if FIO_USE_THREAD_MUTEX_TMP +#define FIO_THREADS +#define FIO___LOCK_NAME "OS mutex" +#define FIO___LOCK_TYPE fio_thread_mutex_t +#define FIO___LOCK_INIT ((FIO___LOCK_TYPE)FIO_THREAD_MUTEX_INIT) +#define FIO___LOCK_DESTROY(lock) fio_thread_mutex_destroy(&(lock)) +#define FIO___LOCK_LOCK(lock) \ + do { \ + if (fio_thread_mutex_lock(&(lock))) \ + FIO_LOG_ERROR("Couldn't lock mutex @ %s:%d - error (%d): %s", \ + __FILE__, \ + __LINE__, \ + errno, \ + strerror(errno)); \ + } while (0) +#define FIO___LOCK_TRYLOCK(lock) fio_thread_mutex_trylock(&(lock)) +#define FIO___LOCK_UNLOCK(lock) \ + do { \ + if (fio_thread_mutex_unlock(&(lock))) { \ + FIO_LOG_ERROR("Couldn't release mutex @ %s:%d - error (%d): %s", \ + __FILE__, \ + __LINE__, \ + errno, \ + strerror(errno)); \ + } \ + } while (0) + +#else +#define FIO___LOCK_NAME "facil.io spinlocks" +#define FIO___LOCK_TYPE fio_lock_i +#define FIO___LOCK_INIT ((FIO___LOCK_TYPE)FIO_LOCK_INIT) +#define FIO___LOCK_DESTROY(lock) ((lock) = FIO___LOCK_INIT) +#define FIO___LOCK_LOCK(lock) fio_lock(&(lock)) +#define FIO___LOCK_TRYLOCK(lock) fio_trylock(&(lock)) +#define FIO___LOCK_UNLOCK(lock) fio_unlock(&(lock)) +#endif + +/* ***************************************************************************** +Recursive inclusion management +***************************************************************************** */ +#ifndef SFUNC_ /* if we aren't in a recursive #include statement */ + +#ifdef FIO_EXTERN +#define SFUNC_ +#define IFUNC_ + +#else /* !FIO_EXTERN */ +#undef SFUNC +#undef IFUNC +#define SFUNC_ FIO_SFUNC +#define IFUNC_ FIO_IFUNC +#endif /* FIO_EXTERN */ + +#undef SFUNC +#undef IFUNC +#define SFUNC SFUNC_ +#define IFUNC IFUNC_ + +#elif !defined(FIO___RECURSIVE_INCLUDE) || (FIO___RECURSIVE_INCLUDE + 1 != 100) +/* SFUNC_ - internal helper types are always `static` */ +#undef SFUNC +#undef IFUNC +#define SFUNC static __attribute__((unused)) +#define IFUNC static inline __attribute__((unused)) +#endif /* SFUNC_ vs FIO___RECURSIVE_INCLUDE*/ + +/* ***************************************************************************** +Leak Counter Helpers +***************************************************************************** */ +#undef FIO_LEAK_COUNTER_DEF +#undef FIO_LEAK_COUNTER_ON_ALLOC +#undef FIO_LEAK_COUNTER_ON_FREE + +#if (FIO_LEAK_COUNTER + 1) == 1 +/* No leak counting defined */ +#define FIO_LEAK_COUNTER_DEF(name) +#define FIO_LEAK_COUNTER_ON_ALLOC(name) +#define FIO_LEAK_COUNTER_ON_FREE(name) +#define FIO_LEAK_COUNTER_COUNT(name) ((size_t)0) +#else +#define FIO_LEAK_COUNTER_DEF(name) \ + FIO_IFUNC size_t FIO_NAME(fio___leak_counter, name)(size_t i) { \ + static volatile size_t counter; \ + size_t tmp = fio_atomic_add_fetch(&counter, i); \ + if (FIO_UNLIKELY(tmp == ((size_t)-1))) \ + goto error_double_free; \ + return tmp; \ + error_double_free: \ + FIO_ASSERT(0, \ + "(%d) " FIO_MACRO2STR(name) " `free` after `free` detected!", \ + fio_getpid()); \ + } \ + static void FIO_NAME(fio___leak_counter_cleanup, name)(void *i) { \ + size_t counter = FIO_NAME(fio___leak_counter, name)((size_t)(uintptr_t)i); \ + FIO_LOG_DEBUG2("(%d) testing leaks for " FIO_MACRO2STR(name), \ + fio_getpid()); \ + if (counter) \ + FIO_LOG_ERROR("(%d) %zu leaks detected for " FIO_MACRO2STR(name), \ + fio_getpid(), \ + counter); \ + } \ + FIO_CONSTRUCTOR(FIO_NAME(fio___leak_counter_const, name)) { \ + fio_state_callback_add(FIO_CALL_AT_EXIT, \ + FIO_NAME(fio___leak_counter_cleanup, name), \ + NULL); \ + } +#define FIO_LEAK_COUNTER_COUNT(name) FIO_NAME(fio___leak_counter, name)(0) +#define FIO_LEAK_COUNTER_ON_ALLOC(name) FIO_NAME(fio___leak_counter, name)(1) +#define FIO_LEAK_COUNTER_ON_FREE(name) \ + FIO_NAME(fio___leak_counter, name)(((size_t)-1)) +#endif + +/* ***************************************************************************** +Pointer Tagging +***************************************************************************** */ +#ifndef FIO_PTR_TAG +/** + * Supports embedded pointer tagging / untagging for the included types. + * + * Should resolve to a tagged pointer value. i.e.: ((uintptr_t)(p) | 1) + */ +#define FIO_PTR_TAG(p) (p) +#endif + +#ifndef FIO_PTR_UNTAG +/** + * Supports embedded pointer tagging / untagging for the included types. + * + * Should resolve to an untagged pointer value. i.e.: ((uintptr_t)(p) | ~1UL) + */ +#define FIO_PTR_UNTAG(p) (p) +#endif + +/** + * If FIO_PTR_TAG_TYPE is defined, then functions returning a type's pointer + * will return a pointer of the specified type instead. + */ +#ifndef FIO_PTR_TAG_TYPE +#endif + +#ifndef FIO_PTR_TAG_VALIDATE +/** + * If FIO_PTR_TAG_VALIDATE is defined, tagging will be verified before executing + * any code. + * + * FIO_PTR_TAG_VALIDATE must fail on NULL pointers. + */ +#define FIO_PTR_TAG_VALIDATE(ptr) ((ptr) != NULL) +#endif + +#undef FIO_PTR_TAG_VALID_OR_RETURN +#define FIO_PTR_TAG_VALID_OR_RETURN(tagged_ptr, value) \ + do { \ + if (!(FIO_PTR_TAG_VALIDATE((tagged_ptr)))) { \ + FIO_LOG_DEBUG("pointer tag (type) mismatch in function call."); \ + return (value); \ + } \ + } while (0) +#undef FIO_PTR_TAG_VALID_OR_RETURN_VOID +#define FIO_PTR_TAG_VALID_OR_RETURN_VOID(tagged_ptr) \ + do { \ + if (!(FIO_PTR_TAG_VALIDATE((tagged_ptr)))) { \ + FIO_LOG_DEBUG("pointer tag (type) mismatch in function call."); \ + return; \ + } \ + } while (0) +#undef FIO_PTR_TAG_VALID_OR_GOTO +#define FIO_PTR_TAG_VALID_OR_GOTO(tagged_ptr, lable) \ + do { \ + if (!(FIO_PTR_TAG_VALIDATE((tagged_ptr)))) { \ + /* Log error since GOTO indicates cleanup or other side-effects. */ \ + FIO_LOG_ERROR("(" FIO__FILE__ ":" FIO_MACRO2STR( \ + __LINE__) ") pointer tag (type) mismatch in function call."); \ + goto lable; \ + } \ + } while (0) + +#define FIO_PTR_TAG_GET_UNTAGGED(untagged_type, tagged_ptr) \ + ((untagged_type *)(FIO_PTR_UNTAG((tagged_ptr)))) +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_LOG /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Logging + + + + + +Use: + +```c +FIO_LOG2STDERR("message.") // => message. +FIO_LOG_LEVEL = FIO_LOG_LEVEL_WARNING; // set dynamic logging level +FIO_LOG_INFO("message"); // => [no output, exceeds logging level] +int i = 3; +FIO_LOG_WARNING("number invalid: %d", i); // => WARNING: number invalid: 3 +``` + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ + +/** + * Enables logging macros that avoid heap memory allocations + */ +#if !defined(FIO_NO_LOG) && !defined(H___FIO_LOG___H) && \ + (defined(FIO_LOG) || defined(FIO_LEAK_COUNTER)) +#define H___FIO_LOG___H + +#undef FIO_LOG2STDERR + +FIO_SFUNC FIO___PRINTF_STYLE(1, 0) void FIO_LOG2STDERR(const char *format, + ...) { +#if FIO_LOG_LENGTH_LIMIT > 128 +#define FIO_LOG____LENGTH_ON_STACK FIO_LOG_LENGTH_LIMIT +#define FIO_LOG____LENGTH_BORDER (FIO_LOG_LENGTH_LIMIT - 34) +#else +#define FIO_LOG____LENGTH_ON_STACK (FIO_LOG_LENGTH_LIMIT + 34) +#define FIO_LOG____LENGTH_BORDER FIO_LOG_LENGTH_LIMIT +#endif + va_list argv; + char tmp___log[FIO_LOG____LENGTH_ON_STACK + 32]; + va_start(argv, format); + int len___log = vsnprintf(tmp___log, FIO_LOG_LENGTH_LIMIT - 2, format, argv); + va_end(argv); + if (len___log > 0) { + if (len___log >= FIO_LOG_LENGTH_LIMIT - 2) { + fio_memcpy32(tmp___log + FIO_LOG____LENGTH_BORDER, + "...\n\t\x1B[2mWARNING:\x1B[0m TRUNCATED!"); + len___log = FIO_LOG____LENGTH_BORDER + 32; + } + tmp___log[len___log++] = '\n'; + tmp___log[len___log] = '0'; + fwrite(tmp___log, 1, len___log, stderr); + return; + } + fwrite("\x1B[1mERROR:\x1B[0m log output error (can't write).\n", + 1, + 47, + stderr); +#undef FIO_LOG____LENGTH_ON_STACK +#undef FIO_LOG____LENGTH_BORDER +} + +/** The logging level */ +#ifndef FIO_LOG_LEVEL_DEFAULT +#if DEBUG +#define FIO_LOG_LEVEL_DEFAULT FIO_LOG_LEVEL_DEBUG +#else +#define FIO_LOG_LEVEL_DEFAULT FIO_LOG_LEVEL_INFO +#endif +#endif +int FIO_WEAK FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEFAULT; +FIO_IFUNC int fio___log_level_set(int i) { return (FIO_LOG_LEVEL = i); } +FIO_IFUNC int fio___log_level(void) { return FIO_LOG_LEVEL; } + +#undef FIO_LOG_LEVEL_GET +#undef FIO_LOG_LEVEL_SET + +/** Sets the Logging Level. */ +#define FIO_LOG_LEVEL_SET(new_level) fio___log_level_set(new_level) +/** Returns the Logging Level. */ +#define FIO_LOG_LEVEL_GET() ((fio___log_level())) + +#endif /* FIO_LOG */ +#undef FIO_LOG +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Alternatives to memcpy, memchr etc' + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MEMALT) && !defined(H___FIO_MEMALT___H) +#define H___FIO_MEMALT___H 1 + +/* ***************************************************************************** +Memory Helpers - API +***************************************************************************** */ + +/** + * A somewhat naive implementation of `memset`. + * + * Probably slower than the one included with your compiler's C library. + */ +SFUNC void *fio_memset(void *restrict dest, uint64_t data, size_t bytes); + +/** + * A somewhat naive implementation of `memcpy`. + * + * Probably slower than the one included with your compiler's C library. + */ +SFUNC void *fio_memcpy(void *dest_, const void *src_, size_t bytes); + +/** + * A token seeking function. This is a fallback for `memchr`, but `memchr` + * should be faster. + */ +SFUNC void *fio_memchr(const void *buffer, const char token, size_t len); + +/** + * A comparison function. This is a fallback for `memcmp`, but `memcmp` + * should be faster. + */ +SFUNC int fio_memcmp(const void *a_, const void *b_, size_t len); + +/** An alternative to `strlen` - may raise Address Sanitation errors. */ +SFUNC size_t fio_strlen(const char *str); + +/* ***************************************************************************** +Alternatives - Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +FIO_MEMCPY / fio_memcpy - memcpy fallback +***************************************************************************** */ + +/** an unsafe memcpy (no checks + assumes no overlapping memory regions)*/ +FIO_SFUNC void *fio___memcpy_buffered_x(void *restrict d_, + const void *restrict s_, + size_t l) { + char *restrict d = (char *restrict)d_; + const char *restrict s = (const char *restrict)s_; + uint64_t t[8] FIO_ALIGN(16); + while (l > 63) { + fio_memcpy64(t, s); + FIO_COMPILER_GUARD_INSTRUCTION; + fio_memcpy64(d, t); + l -= 64; + d += 64; + s += 64; + } +#define FIO___MEMCPY_UNSAFE_STEP(bytes) \ + do { \ + fio_memcpy##bytes(t, s); \ + FIO_COMPILER_GUARD_INSTRUCTION; \ + fio_memcpy##bytes(d, t); \ + (d += bytes), (s += bytes); \ + } while (0) + if (l & 32) + FIO___MEMCPY_UNSAFE_STEP(32); + if ((l & 16)) + FIO___MEMCPY_UNSAFE_STEP(16); + if ((l & 8)) + FIO___MEMCPY_UNSAFE_STEP(8); + if ((l & 4)) + FIO___MEMCPY_UNSAFE_STEP(4); + if ((l & 2)) + FIO___MEMCPY_UNSAFE_STEP(2); +#undef FIO___MEMCPY_UNSAFE_STEP + if ((l & 1)) + *d++ = *s; + + return (void *)d; +} + +/** an unsafe memcpy (no checks + assumes no overlapping memory regions)*/ +FIO_SFUNC void *fio___memcpy_buffered_reversed_x(void *d_, + const void *s_, + size_t l) { + char *d = (char *)d_ + l; + const char *s = (const char *)s_ + l; + uint64_t t[8] FIO_ALIGN(16); + while (l > 63) { + (s -= 64), (d -= 64), (l -= 64); + fio_memcpy64(t, s); + FIO_COMPILER_GUARD_INSTRUCTION; + fio_memcpy64(d, t); + } + if ((l & 32)) { + (d -= 32), (s -= 32); + fio_memcpy32(t, s); + FIO_COMPILER_GUARD_INSTRUCTION; + fio_memcpy32(d, t); + } + if ((l & 16)) { + (d -= 16), (s -= 16); + fio_memcpy16(t, s); + FIO_COMPILER_GUARD_INSTRUCTION; + fio_memcpy16(d, t); + } + if ((l & 8)) { + (d -= 8), (s -= 8); + fio_memcpy8(t, s); + FIO_COMPILER_GUARD_INSTRUCTION; + fio_memcpy8(d, t); + } + if ((l & 4)) { + (d -= 4), (s -= 4); + fio_memcpy4(t, s); + fio_memcpy4(d, t); + } + if ((l & 2)) { + (d -= 2), (s -= 2); + fio_memcpy2(t, s); + fio_memcpy2(d, t); + } + if ((l & 1)) + *--d = *--s; + return (void *)d; +} + +#define FIO___MEMCPY_BLOCKx_NUM 255ULL + +/** memcpy / memmove alternative that should work with unaligned memory */ +SFUNC void *fio_memcpy(void *dest_, const void *src_, size_t bytes) { + char *d = (char *)dest_; + const char *s = (const char *)src_; + + if ((d == s) | !bytes | !d | !s) { + if (bytes && (d != s)) + FIO_LOG_DEBUG2("fio_memcpy null error - ignored instruction"); + return d; + } + + if (s + bytes <= d || d + bytes <= s || + (uintptr_t)d + FIO___MEMCPY_BLOCKx_NUM < (uintptr_t)s) { + return fio___memcpy_unsafe_x(d, s, bytes); + } else if (d < s) { /* memory overlaps at end (copy forward, use buffer) */ + return fio___memcpy_buffered_x(d, s, bytes); + } else { /* memory overlaps at beginning, walk backwards (memmove) */ + return fio___memcpy_buffered_reversed_x(d, s, bytes); + } + return d; +} + +#undef FIO___MEMCPY_BLOCKx_NUM + +/* ***************************************************************************** +FIO_MEMSET / fio_memset - memset fallbacks +***************************************************************************** */ + +/** an 8 byte value memset implementation. */ +SFUNC void *fio_memset(void *restrict dest_, uint64_t data, size_t bytes) { + char *d = (char *)dest_; + if (data < 0x100) { /* if a single byte value, match memset */ + data |= (data << 8); + data |= (data << 16); + data |= (data << 32); + } + if (bytes < 32) + goto small_memset; + + /* 32 byte groups */ + for (;;) { + for (size_t i = 0; i < 32; i += 8) { + fio_memcpy8(d + i, &data); + } + bytes -= 32; + if (bytes < 32) + break; + d += 32; + } + /* remainder */ + d += bytes & 31; + data = fio_frot64(data, ((bytes & 7) << 3)); + for (size_t i = 0; i < 32; i += 8) { + fio_memcpy8(d + i, &data); + } + return dest_; + +small_memset: + if (bytes & 16) { + fio_memcpy8(d, &data); + fio_memcpy8(d + 8, &data); + d += 16; + } + if (bytes & 8) { + fio_memcpy8(d, &data); + d += 8; + } + fio_memcpy7x(d, &data, bytes); + return dest_; +} + +/* ***************************************************************************** +FIO_MEMCHR / fio_memchr - memchr fallbacks +***************************************************************************** */ + +/** + * A token seeking function. This is a fallback for `memchr`, but `memchr` + * should be faster. + */ +SFUNC void *fio_memchr(const void *buffer, const char token, size_t len) { + // return (void *)memchr(buffer, token, len); /* FIXME */ + const char *r = (const char *)buffer; + const char *e = r + (len - 127); + uint64_t u[16] FIO_ALIGN(16) = {0}; + uint64_t flag = 0; + size_t i; + uint64_t umsk = ((uint64_t)((uint8_t)token)); + umsk |= (umsk << 32); /* make each byte in umsk == token */ + umsk |= (umsk << 16); + umsk |= (umsk << 8); + if (len < 8) + goto small_memchr; + while (r < e) { + fio_memcpy128(u, r); + for (i = 0; i < 16; ++i) { + u[i] ^= umsk; + flag |= (u[i] = fio_has_zero_byte64(u[i])); + } + if (flag) + goto found_in_map; + r += 128; + } + e += 120; + i = 0; + while (r < e) { + fio_memcpy8(u, r); + u[0] ^= umsk; + flag = fio_has_zero_byte64(u[0]); + if (flag) + goto found_in_8; + r += 8; + } +small_memchr: + switch ((len & 7)) { /* clang-format off */ + case 7: if (*r == token) return (void *)r; ++r; /* fall through */ + case 6: if (*r == token) return (void *)r; ++r; /* fall through */ + case 5: if (*r == token) return (void *)r; ++r; /* fall through */ + case 4: if (*r == token) return (void *)r; ++r; /* fall through */ + case 3: if (*r == token) return (void *)r; ++r; /* fall through */ + case 2: if (*r == token) return (void *)r; ++r; /* fall through */ + case 1: if (*r == token) return (void *)r; ++r; + } /* clang-format on */ + return NULL; +found_in_map: + flag = 0; + for (i = 0; !u[i]; ++i) + ; + flag = u[i]; + r += i << 3; +found_in_8: + flag = fio_has_byte2bitmap(flag); + return (void *)(r + fio_lsb_index_unsafe(flag)); +} + +/* ***************************************************************************** +fio_strlen +***************************************************************************** */ + +SFUNC FIO___ASAN_AVOID size_t fio_strlen(const char *str) { + if (!str) + return 0; + uintptr_t start = (uintptr_t)str; + /* we must align memory, to avoid crushing when nearing last page boundary */ + uint64_t flag = 0; + uint64_t map[8] FIO_ALIGN(16); + /* align to 8 bytes - most likely skipped */ + switch (start & 7) { // clang-format off + case 1: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 2: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 3: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 4: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 5: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 6: if(*str == 0) return (uintptr_t)str - start; ++str; /* fall through */ + case 7: if(*str == 0) return (uintptr_t)str - start; ++str; + } // clang-format on + /* align to 64 bytes */ + for (size_t i = 0; i < 9; ++i) { + if ((flag = fio_has_zero_byte64(*(uint64_t *)str))) + goto found_nul_byte0; + str += 8; + } + str = FIO_PTR_MATH_RMASK(const char, str, 6); /* compiler hint */ + /* loop endlessly */ + for (;;) { + for (size_t i = 0; i < 8; ++i) { + flag |= (map[i] = fio_has_zero_byte64(((uint64_t *)str)[i])); + } + if (flag) + goto found_nul_byte8; + str += 64; + } + +found_nul_byte8: + flag = 0; + for (size_t i = 0; i < 8; ++i) { + map[i] = fio_has_byte2bitmap(map[i]); + flag |= (map[i] << (i << 3)); /* pack bitmap */ + } + str += fio_lsb_index_unsafe(flag); + return (uintptr_t)str - start; + +found_nul_byte0: + str += fio_lsb_index_unsafe(fio_has_byte2bitmap(flag)); + return (uintptr_t)str - start; +} + +/* ***************************************************************************** +fio_memcmp +***************************************************************************** */ + +/** Same as `memcmp`. Returns 1 if `a > b`, -1 if `a < b` and 0 if `a == b`. */ +SFUNC int fio_memcmp(const void *a_, const void *b_, size_t len) { + if (a_ == b_ || !len) + return 0; + uint64_t ua[8] FIO_ALIGN(16); + uint64_t ub[8] FIO_ALIGN(16); + size_t flag = 0; + char *a = (char *)a_; + char *b = (char *)b_; + char *e; + if (*a != *b) + return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); + if (len < 8) + goto fio_memcmp_mini; + if (len < 64) + goto fio_memcmp_small; + + e = a + len - 63; + do { + fio_memcpy64(ua, a); + fio_memcpy64(ub, b); + for (size_t i = 0; i < 8; ++i) + flag |= (ua[i] ^ ub[i]); + if (flag) + goto fio_memcmp_found; + a += 64; + b += 64; + } while (a < e); + a += len & 63; + b += len & 63; + a -= 64; + b -= 64; + fio_memcpy64(ua, a); + fio_memcpy64(ub, b); + for (size_t i = 0; i < 8; ++i) + flag |= (ua[i] ^ ub[i]); + if (flag) + goto fio_memcmp_found; + return 0; + +fio_memcmp_found: + if (ua[0] == ub[0]) + for (size_t i = 8; --i;) + if (ua[i] != ub[i]) { + ua[0] = ua[i]; + ub[0] = ub[i]; + } + goto fio_memcmp_small_found; + +fio_memcmp_small: + e = a + len - 7; + do { + fio_memcpy8(ua, a); + fio_memcpy8(ub, b); + if (ua[0] != ub[0]) + goto fio_memcmp_small_found; + a += 8; + b += 8; + } while (a < e); + a += len & 7; + b += len & 7; + a -= 8; + b -= 8; + fio_memcpy8(ua, a); + fio_memcpy8(ub, b); + if (ua[0] != ub[0]) + goto fio_memcmp_small_found; + return 0; + +fio_memcmp_small_found: + ua[0] = fio_lton64(ua[0]); + ub[0] = fio_lton64(ub[0]); + return (int)1 - (int)((ub[0] > ua[0]) << 1); + +fio_memcmp_mini: + switch ((len & 7)) { /* clang-format off */ + case 7: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 6: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 5: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 4: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 3: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 2: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; /* fall through */ + case 1: if (*a != *b) return (int)1 - (int)(((unsigned)b[0] > (unsigned)a[0]) << 1); ++a; ++b; + } /* clang-format on */ + return 0; +} + +/* ***************************************************************************** +Alternatives - cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN */ +#endif /* FIO_MEMALT */ +#ifndef H___FIO_OS_PATCHES___H +#define H___FIO_OS_PATCHES___H +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ************************************************************************* */ + +/* ***************************************************************************** + + +Patch for OSX version < 10.12 from https://stackoverflow.com/a/9781275/4025095 + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if (defined(__MACH__) && !defined(CLOCK_REALTIME)) +#warning fio_time functions defined using gettimeofday patch. +#include +#define CLOCK_REALTIME 0 +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 0 +#endif +#define clock_gettime fio_clock_gettime +// clock_gettime is not implemented on older versions of OS X (< 10.12). +// If implemented, CLOCK_MONOTONIC will have already been defined. +FIO_IFUNC int fio_clock_gettime(int clk_id, struct timespec *t) { + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) + return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; + (void)clk_id; +} + +#endif +/* ***************************************************************************** + + + + +Patches for Windows + + + + +***************************************************************************** */ +#if FIO_OS_WIN + +/* ***************************************************************************** +Windows initialization +***************************************************************************** */ + +/* Enable console colors */ +FIO_CONSTRUCTOR(fio___windows_startup_housekeeping) { + HANDLE c = GetStdHandle(STD_OUTPUT_HANDLE); + if (c) { + DWORD mode = 0; + if (GetConsoleMode(c, &mode)) { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(c, mode); + } + } + c = GetStdHandle(STD_ERROR_HANDLE); + if (c) { + DWORD mode = 0; + if (GetConsoleMode(c, &mode)) { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(c, mode); + } + } + /* by default, windows read files using _O_TEXT, which affects behavior */ + _set_fmode(_O_BINARY); +} + +/* ***************************************************************************** +Inlined patched and MACRO statements +***************************************************************************** */ + +#ifndef __MINGW32__ +/** patch for strcasecmp */ +FIO_IFUNC int strcasecmp(const char *s1, const char *s2) { + return _stricmp(s1, s2); +} +/** patch for write */ +FIO_IFUNC int write(int fd, const void *b, unsigned int l) { + return _write(fd, b, l); +} +/** patch for read */ +FIO_IFUNC int read(int const fd, void *const b, unsigned const l) { + return _read(fd, b, l); +} +/** patch for clock_gettime */ +FIO_SFUNC int clock_gettime(const uint32_t clk_type, struct timespec *tv); +#endif /* __MINGW32__ */ + +/** patch for pwrite */ +FIO_SFUNC ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); + +#ifndef O_APPEND +#define O_APPEND _O_APPEND +#define O_BINARY _O_BINARY +#define O_CREAT _O_CREAT +#define O_CREAT _O_CREAT +#define O_SHORT_LIVED _O_SHORT_LIVED +#define O_CREAT _O_CREAT +#define O_TEMPORARY _O_TEMPORARY +#define O_CREAT _O_CREAT +#define O_EXCL _O_EXCL +#define O_NOINHERIT _O_NOINHERIT +#define O_RANDOM _O_RANDOM +#define O_RDONLY _O_RDONLY +#define O_RDWR _O_RDWR +#define O_SEQUENTIAL _O_SEQUENTIAL +#define O_TEXT _O_TEXT +#define O_TRUNC _O_TRUNC +#define O_WRONLY _O_WRONLY +#define O_U16TEXT _O_U16TEXT +#define O_U8TEXT _O_U8TEXT +#define O_WTEXT _O_WTEXT +#endif /* O_APPEND */ + +#ifndef S_IWUSR +#define S_IREAD _S_IREAD +#define S_IWRITE _S_IWRITE +#define S_IRUSR _S_IREAD +#define S_IWUSR _S_IWRITE +#define S_IRWXO (_S_IREAD | _S_IWRITE) +#define S_IRWXG (_S_IREAD | _S_IWRITE) +#define S_IRWXU (_S_IREAD | _S_IWRITE) +#endif /* S_IWUSR */ + +#ifndef O_TMPFILE +#define O_TMPFILE O_TEMPORARY +#endif + +#if defined(CLOCK_REALTIME) && defined(CLOCK_MONOTONIC) && \ + CLOCK_REALTIME == CLOCK_MONOTONIC +#undef CLOCK_MONOTONIC +#undef CLOCK_REALTIME +#endif + +#ifndef CLOCK_REALTIME +#ifdef CLOCK_MONOTONIC +#define CLOCK_REALTIME (CLOCK_MONOTONIC + 1) +#else +#define CLOCK_REALTIME 0 +#endif +#endif + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif + +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else /* although this value is usually (in practice) 256 + 4, we go big. */ +#define PATH_MAX 1024 +#endif +#endif + +#if !defined(fstat) +#define fstat _fstat +#endif /* fstat */ +#if !defined(stat) +#define stat _stat +#endif /* stat */ +#if !defined(lseek) +#define lseek _lseeki64 +#endif /* lseek */ +#if !defined(unlink) +#define unlink _unlink +#endif /* unlink */ + +#if !FIO_HAVE_UNIX_TOOLS || defined(__MINGW32__) +#define pipe(fds) _pipe(fds, 65536, _O_BINARY) +#endif + +/* ***************************************************************************** +Patched function Implementation +***************************************************************************** */ + +#ifndef __MINGW32__ +/* based on: + * https://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows + */ +/** patch for clock_gettime */ +FIO_SFUNC int clock_gettime(const uint32_t clk_type, struct timespec *tv) { + if (!tv) + return -1; + static union { + uint64_t u; + LARGE_INTEGER li; + } freq = {.u = 0}; + static double tick2n = 0; + union { + uint64_t u; + FILETIME ft; + LARGE_INTEGER li; + } tu; + + switch (clk_type) { + case CLOCK_REALTIME: + realtime_clock: + GetSystemTimePreciseAsFileTime(&tu.ft); + tv->tv_sec = tu.u / 10000000; + tv->tv_nsec = tu.u - (tv->tv_sec * 10000000); + return 0; + +#ifdef CLOCK_PROCESS_CPUTIME_ID + case CLOCK_PROCESS_CPUTIME_ID: +#endif +#ifdef CLOCK_THREAD_CPUTIME_ID + case CLOCK_THREAD_CPUTIME_ID: +#endif + case CLOCK_MONOTONIC: + if (!QueryPerformanceCounter(&tu.li)) + goto realtime_clock; + if (!freq.u) + QueryPerformanceFrequency(&freq.li); + if (!freq.u) { + tick2n = 0; + freq.u = 1; + } else { + tick2n = (double)1000000000 / freq.u; + } + tv->tv_sec = tu.u / freq.u; + tv->tv_nsec = + (uint64_t)(0ULL + ((double)(tu.u - (tv->tv_sec * freq.u)) * tick2n)); + return 0; + } + return -1; +} +#endif /* __MINGW32__ */ + +/** patch for pwrite */ +FIO_SFUNC ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { + /* Credit to Jan Biedermann (GitHub: @janbiedermann) */ + ssize_t bytes_written = 0; + HANDLE handle = (HANDLE)_get_osfhandle(fd); + if (handle == INVALID_HANDLE_VALUE) + goto bad_file; + OVERLAPPED overlapped = {0}; + if (offset > 0) + overlapped.Offset = offset; + if (WriteFile(handle, buf, count, (u_long *)&bytes_written, &overlapped)) + return bytes_written; + errno = EIO; + return -1; +bad_file: + errno = EBADF; + return -1; +} + +/* ***************************************************************************** + + + +Patches for POSIX + + + +***************************************************************************** */ +#elif FIO_OS_POSIX /* POSIX patches */ +#endif + +/* ***************************************************************************** +Done with Patches +***************************************************************************** */ +#endif /* H___FIO_OS_PATCHES___H */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ATOL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + String <=> Number helpers + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_ATOL) && !defined(H___FIO_ATOL___H) +#define H___FIO_ATOL___H +#include +#include + +#ifndef FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER +#define FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER 1 +#endif + +/* ***************************************************************************** +Strings to Signed Numbers - The fio_aton function +***************************************************************************** */ + +/** Result type for fio_aton */ +typedef struct { + union { + int64_t i; + double f; + uint64_t u; + }; + int is_float; + int err; +} fio_aton_s; + +/** + * Converts a String to a number - either an integer or a float (double). + * + * Skips white space at the beginning of the string. + * + * Auto detects binary and hex formats when prefix is provided (0x / 0b). + * + * Auto detects octal when number starts with zero. + * + * Auto detects the Strings "inf", "infinity" and "nan" as float values. + * + * The number's format and type are returned in the return type. + * + * If a numerical overflow or format error occurred, the `.err` flag is set. + * + * Note: rounding errors may occur, as this is not an `strtod` exact match. + */ +FIO_SFUNC fio_aton_s fio_aton(char **pstr); + +/* ***************************************************************************** +Strings to Signed Numbers - API +***************************************************************************** */ +/** + * A helper function that converts between String data to a signed int64_t. + * + * Numbers are assumed to be in base 10. Octal (`0###`), Hex (`0x##`/`x##`) and + * binary (`0b##`/ `b##`) are recognized as well. For binary Most Significant + * Bit must come first. + * + * The most significant difference between this function and `strtol` (aside of + * API design), is the added support for binary representations. + */ +SFUNC int64_t fio_atol(char **pstr); + +/** A helper function that converts between String data to a signed double. */ +SFUNC double fio_atof(char **pstr); + +/* ***************************************************************************** +Signed Numbers to Strings - API +***************************************************************************** */ + +/** + * A helper function that writes a signed int64_t to a string. + * + * No overflow guard is provided, make sure there's at least 68 bytes available + * (for base 2). + * + * Offers special support for base 2 (binary), base 8 (octal), base 10 and base + * 16 (hex) where prefixes are automatically added if required (i.e.,`"0x"` for + * hex and `"0b"` for base 2, and `"0"` for octal). + * + * Supports any base up to base 36 (using 0-9,A-Z). + * + * An unsupported base will log an error and print zero. + * + * Returns the number of bytes actually written (excluding the NUL terminator). + */ +SFUNC size_t fio_ltoa(char *dest, int64_t num, uint8_t base); + +/** + * A helper function that converts between a double to a string. + * + * No overflow guard is provided, make sure there's at least 130 bytes + * available (for base 2). + * + * Supports base 2, base 10 and base 16. An unsupported base will silently + * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the + * beginning of the string). + * + * Returns the number of bytes actually written (excluding the NUL + * terminator). + */ +SFUNC size_t fio_ftoa(char *dest, double num, uint8_t base); + +/* ***************************************************************************** +Unsigned Numbers, Building Blocks and Helpers +***************************************************************************** */ +/** + * Maps characters to alphanumerical value, where numbers have their natural + * values (0-9) and `A-Z` (or `a-z`) are the values 10-35. + * + * Out of bound values return 255. + * + * This allows parsing of numeral strings for up to base 36. + */ +IFUNC uint8_t fio_c2i(unsigned char c); + +/** + * Maps numeral values to alphanumerical characters, where numbers have their + * natural values (0-9) and `A-Z` are the values 10-35. + * + * Accepts values up to 63. Returns zero for values over 35. Out of bound values + * produce undefined behavior. + * + * This allows printing of numerals for up to base 36. + */ +IFUNC uint8_t fio_i2c(unsigned char i); + +/** Returns the number of digits in base 10. */ +FIO_IFUNC size_t fio_digits10(int64_t i); +/** Returns the number of digits in base 10 for an unsigned number. */ +FIO_SFUNC size_t fio_digits10u(uint64_t i); + +/** Returns the number of digits in base 8 for an unsigned number. */ +FIO_SFUNC size_t fio_digits8u(uint64_t i); +/** Returns the number of digits in base 16 for an unsigned number. */ +FIO_SFUNC size_t fio_digits16u(uint64_t i); +/** Returns the number of digits in base 2 for an unsigned number. */ +FIO_SFUNC size_t fio_digits_bin(uint64_t i); +/** Returns the number of digits in any base X<65 for an unsigned number. */ +FIO_SFUNC size_t fio_digits_xbase(uint64_t i, size_t base); + +/** Writes a signed number to `dest` using `digits` bytes (+ `NUL`) */ +FIO_IFUNC void fio_ltoa10(char *dest, int64_t i, size_t digits); +/** Reads a signed base 10 formatted number. */ +SFUNC int64_t fio_atol10(char **pstr); + +/** Writes unsigned number to `dest` using `digits` bytes (+ `NUL`) */ +FIO_IFUNC void fio_ltoa10u(char *dest, uint64_t i, size_t digits); +/** Writes unsigned number to `dest` using `digits` bytes (+ `NUL`) */ +FIO_IFUNC void fio_ltoa16u(char *dest, uint64_t i, size_t digits); +/** Writes unsigned number to `dest` using `digits` bytes (+ `NUL`) */ +FIO_IFUNC void fio_ltoa_bin(char *dest, uint64_t i, size_t digits); +/** Writes unsigned number to `dest` using `digits` bytes (+ `NUL`) */ +FIO_IFUNC void fio_ltoa_xbase(char *dest, + uint64_t i, + size_t digits, + size_t base); + +/** Reads a signed base 8 formatted number. */ +SFUNC uint64_t fio_atol8u(char **pstr); +/** Reads a signed base 10 formatted number. */ +SFUNC uint64_t fio_atol10u(char **pstr); +/** Reads an unsigned hex formatted number (possibly prefixed with "0x"). */ +SFUNC uint64_t fio_atol16u(char **pstr); +/** Reads an unsigned binary formatted number (possibly prefixed with "0b"). */ +SFUNC uint64_t fio_atol_bin(char **pstr); +/** Read an unsigned number in any base up to base 36. */ +SFUNC uint64_t fio_atol_xbase(char **pstr, size_t base); + +/** Converts an unsigned `val` to a signed `val`, with overflow protection. */ +FIO_IFUNC int64_t fio_u2i_limit(uint64_t val, size_t invert); + +/* ***************************************************************************** +IEEE 754 Floating Points, Building Blocks and Helpers +***************************************************************************** */ + +/** Converts a 64 bit integer to an IEEE 754 formatted double. */ +FIO_IFUNC double fio_i2d(int64_t mant, int64_t exponent_in_base_2); + +/** Converts a 64 bit unsigned integer to an IEEE 754 formatted double. */ +FIO_IFUNC double fio_u2d(uint64_t mant, int64_t exponent_in_base_2); + +/* ***************************************************************************** +Big Numbers +***************************************************************************** */ + +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u128 fio_u128_hex_read(char **pstr); +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u256 fio_u256_hex_read(char **pstr); +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u512 fio_u512_hex_read(char **pstr); +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u1024 fio_u1024_hex_read(char **pstr); +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u2048 fio_u2048_hex_read(char **pstr); +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u4096 fio_u4096_hex_read(char **pstr); + +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u128_hex_write(char *dest, const fio_u128 *); +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u256_hex_write(char *dest, const fio_u256 *); +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u512_hex_write(char *dest, const fio_u512 *); +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u1024_hex_write(char *dest, const fio_u1024 *); +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u2048_hex_write(char *dest, const fio_u2048 *); +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u4096_hex_write(char *dest, const fio_u4096 *); + +/* ***************************************************************************** + + +Implementation - inlined + + +***************************************************************************** */ + +/** Returns the number of digits in base 10. */ +FIO_IFUNC size_t fio_digits10(int64_t i) { + if (i >= 0) + return fio_digits10u(i); + return fio_digits10u((0ULL - (uint64_t)i)) + 1; +} + +/** Returns the number of digits in base 2 for an unsigned number. */ +FIO_SFUNC size_t fio_digits_bin(uint64_t i) { + size_t r = 1; + if (!i) + return r; + r = fio_msb_index_unsafe(i) + 1; + r += (r & 1); /* binary is written 2 zeros at a time */ + return r; +} + +/** Returns the number of digits in base 8 for an unsigned number. */ +FIO_SFUNC size_t fio_digits8u(uint64_t i) { + size_t r = 1; + for (;;) { + if (i < 8) + return r; + if (i < 64) + return r + 1; + if (i < 512) + return r + 2; + if (i < 4096) + return r + 3; + if (i < 32768) + return r + 4; + if (i < 262144) + return r + 5; + if (i < 2097152) + return r + 6; + if (i < 16777216) + return r + 7; + r += 8; + i >>= 24; + } +} + +/** Returns the number of digits in base 10 for an unsigned number. */ +FIO_SFUNC size_t fio_digits10u(uint64_t i) { + size_t r = 1; + for (;;) { + if (i < 10ULL) + return r; + if (i < 100ULL) + return r + 1; + if (i < 1000ULL) + return r + 2; + if (i < 10000ULL) + return r + 3; + r += 4; + i /= 10000ULL; + } +} + +/** Returns the number of digits in base 16 for an unsigned number. */ +FIO_SFUNC size_t fio_digits16u(uint64_t i) { + if (i < 0x100ULL) + return 2; + if (i < 0x10000ULL) + return 4; + if (i < 0x1000000ULL) + return 6; + if (i < 0x100000000ULL) + return 8; + if (i < 0x10000000000ULL) + return 10; + if (i < 0x1000000000000ULL) + return 12; + if (i < 0x100000000000000ULL) + return 14; + return 16; +} + +/** Returns the number of digits in base X<65 for an unsigned number. */ +FIO_SFUNC size_t fio_digits_xbase(uint64_t i, size_t base) { + size_t base2 = base * base; + size_t base3 = base2 * base; + size_t base4 = base3 * base; + size_t r = 1; + for (;;) { + if (i < base) + return r; + if (i < base2) + return r + 1; + if (i < base3) + return r + 2; + if (i < base4) + return r + 3; + r += 4; + i /= base4; + } +} + +FIO_IFUNC void fio_ltoa10(char *dest, int64_t i, size_t digits) { + size_t inv = i < 0; + dest[0] = '-'; + dest += inv; + if (inv) + i = (int64_t)((uint64_t)0 - (uint64_t)i); + fio_ltoa10u(dest, (uint64_t)i, digits - inv); +} + +FIO_IFUNC void fio_ltoa8u(char *dest, uint64_t i, size_t digits) { + dest += digits; + *dest-- = 0; + while (i > 7) { + *dest-- = '0' + (i & 7); + i >>= 3; + } + *dest = '0' + i; +} + +FIO_IFUNC void fio_ltoa10u(char *dest, uint64_t i, size_t digits) { + dest += digits; + *dest-- = 0; + while (i > 9) { + uint64_t nxt = i / 10; + *dest-- = '0' + (i - (nxt * 10ULL)); + i = nxt; + } + *dest = '0' + (unsigned char)i; +} + +FIO_IFUNC void fio_ltoa16u(char *dest, uint64_t i, size_t digits) { + digits += (digits & 1U); /* force even number of digits */ + dest += digits; + *dest-- = 0; + while (digits) { + digits -= 2; + *dest-- = fio_i2c(i & 15); + i >>= 4; + *dest-- = fio_i2c(i & 15); + i >>= 4; + } +} + +FIO_IFUNC void fio_ltoa_bin(char *dest, uint64_t i, size_t digits) { + dest += digits; + *dest = 0; + switch (digits & 7) { + case 7: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 6: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 5: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 4: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 3: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 2: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 1: *--dest = '0' + (i & 1); i >>= 1; /* fall through */ + case 0:; + } + digits &= ~(uint64_t)7ULL; + while (digits > 7) { + uint64_t tmp = (i & 0xFFULL); + dest -= 8; + digits -= 8; + i >>= 8; + tmp = ((tmp & 0x7F) * 0x02040810204081ULL) | ((tmp & 0x80) << 49); + tmp &= 0x0101010101010101ULL; + tmp += (0x0101010101010101ULL * '0'); + fio_u2buf64_be(dest, tmp); + } +} + +FIO_IFUNC void fio_ltoa_xbase(char *dest, + uint64_t i, + size_t digits, + size_t base) { + dest += digits; + *dest-- = 0; + while (i >= base) { + uint64_t nxt = i / base; + *dest-- = fio_i2c(i - (nxt * base)); + i = nxt; + } + *dest = fio_i2c(i); +} + +/** Converts an unsigned `val` to a signed `val`, with overflow protection. */ +FIO_IFUNC int64_t fio_u2i_limit(uint64_t val, size_t to_negative) { + if (!to_negative) { + /* overflow? */ + if (!(val & 0x8000000000000000ULL)) + return val; + errno = E2BIG; + val = 0x7FFFFFFFFFFFFFFFULL; + return val; + } + if (!(val & 0x8000000000000000ULL)) { + val = (int64_t)0LL - (int64_t)val; + return val; + } + /* read overflow */ + errno = E2BIG; + return (int64_t)(val = 0x8000000000000000ULL); +} + +/* ***************************************************************************** +IEEE 754 Floating Points, Building Blocks and Helpers +***************************************************************************** */ + +#ifndef FIO_MATH_DBL_MANT_MASK +#define FIO_MATH_DBL_MANT_MASK (((uint64_t)1ULL << 52) - 1) +#define FIO_MATH_DBL_EXPO_MASK ((uint64_t)2047ULL << 52) +#define FIO_MATH_DBL_SIGN_MASK ((uint64_t)1ULL << 63) +#endif + +FIO_IFUNC int fio_d2expo(double d) { + int r; + union { + uint64_t u64; + double d; + } u = {.d = d}; + u.u64 &= FIO_MATH_DBL_EXPO_MASK; + r = (int)(u.u64 >> 52); + r -= 1023; + r *= -1; + return r; +} + +/** Converts a 64 bit integer to an IEEE 754 formatted double. */ +FIO_IFUNC double fio_u2d(uint64_t mant, int64_t exponent) { +#ifndef FIO___ATON_TIE2EVEN + /* If set, performs a rounding attempt with tie to even */ +#define FIO___ATON_TIE2EVEN 0 +#endif + union { + uint64_t u64; + double d; + } u = {0}; + size_t msbi; + if (!mant) + return u.d; + msbi = fio_msb_index_unsafe(mant); + if (FIO___ATON_TIE2EVEN && FIO_UNLIKELY(msbi > 52)) { /* losing precision */ + bool not53 = (msbi != 53); + bool far_set = ((mant >> (53 + not53)) != 0); + mant = mant >> (msbi - (53 + not53)); + mant |= far_set; + mant |= (mant >> (1U + not53)) & 1; /* set the non-even bit as rounder */ + mant += 1; /* 1 will propagate if rounding is necessary. */ + bool add_to_expo = (mant >> (53U + not53)) & 1; + mant >>= (1U + not53 + add_to_expo); + exponent += add_to_expo; + } + /* normalize exponent */ + exponent += msbi + 1023; + if (FIO_UNLIKELY(exponent > 2047)) + goto is_inifinity_or_nan; + if (FIO_UNLIKELY(exponent <= 0)) + goto is_subnormal; + exponent = (uint64_t)exponent << 52; + u.u64 |= exponent; + /* reposition mant bits so we "hide" the fist set bit in bit[52] */ + if (msbi < 52) + mant = mant << (52 - msbi); + else if (!FIO___ATON_TIE2EVEN && + FIO_UNLIKELY(msbi > 52)) /* losing precision */ + mant = mant >> (msbi - 52); + u.u64 |= mant & FIO_MATH_DBL_MANT_MASK; /* remove the 1 set bit */ + return u.d; + +is_inifinity_or_nan: + u.u64 = FIO_MATH_DBL_EXPO_MASK; + return u.d; + +is_subnormal: + exponent += 51 - msbi; + if (exponent < 0) + return u.d; + u.u64 = mant >> exponent; + return u.d; +} + +/** Converts a 64 bit integer to an IEEE 754 formatted double. */ +FIO_IFUNC double fio_i2d(int64_t mant, int64_t exponent) { + union { + uint64_t u64; + int64_t i64; + double d; + } u = {.i64 = mant}; + bool sign = (u.i64 < 0); + if (sign) + u.i64 = -u.i64; + u.d = fio_u2d(u.u64, exponent); + u.u64 |= ((uint64_t)sign) << 63; + return u.d; +} + +/* ***************************************************************************** +Implementation - possibly externed +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Unsigned core and helpers +***************************************************************************** */ + +/** + * Maps characters to alphanumerical value, where numbers have their natural + * values (0-9) and `A-Z` (or `a-z`) are the values 10-35. + * + * Out of bound values return 255. + * + * This allows parsing of numeral strings for up to base 36. + */ +IFUNC uint8_t fio_c2i(unsigned char c) { + static const uint8_t fio___alphanumeric_map[256] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + return fio___alphanumeric_map[c]; +} + +/** + * Maps numeral values to alphanumerical characters, where numbers have their + * natural values (0-9) and `A-Z` are the values 10-35. + * + * Accepts values up to 63. Returns zero for values over 35. Out of bound values + * produce undefined behavior. + * + * This allows printing of numerals for up to base 36. + */ +IFUNC uint8_t fio_i2c(unsigned char i) { + static const uint8_t fio___alphanumeric_map[64] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + return fio___alphanumeric_map[i & 63]; +} + +/** Reads a signed base 8 formatted number. */ +SFUNC uint64_t fio_atol8u(char **pstr) { + uint64_t r = 0; + size_t d; + while ((d = (size_t)fio_c2i((unsigned char)(**pstr))) < 8) { + r <<= 3; + r |= d; + ++*pstr; + if ((r & UINT64_C(0xE000000000000000))) + break; +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + *pstr += (**pstr == '_'); /* allow '_' as a divider. */ +#endif + } + if ((fio_c2i(**pstr)) < 8) + errno = E2BIG; + return r; +} + +/** Reads an unsigned base 10 formatted number. */ +SFUNC uint64_t fio_atol10u(char **pstr) { + uint64_t r = 0, u0 = 0, u1 = 0; + char *pos = *pstr; + /* can't use SIMD, as we don't want to overflow. */ + for (size_t i = 0; i < 8; ++i) + u0 += ((pos[u0] >= '0') & (pos[u0] <= '9')); + switch ((u0 & 12)) { /* now we are safe to copy all bytes validated */ + case 8: + r = fio_buf2u64_le(pos); + *pstr = (pos += 8); /* credit Johnny Lee, not mine... */ + r = ((r & 0x0F0F0F0F0F0F0F0FULL) * 2561ULL) >> 8; + r = ((r & 0x00FF00FF00FF00FFULL) * 6553601ULL) >> 16; + r = ((r & 0x0000FFFF0000FFFFULL) * 42949672960001ULL) >> 32; + u1 = r; /* https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ */ + break; + case 4: + r = ((unsigned)(pos[0] - '0') * 1000) + ((unsigned)(pos[1] - '0') * 100) + + ((unsigned)(pos[2] - '0') * 10) + (unsigned)(pos[3] - '0'); + *pstr = (pos += 4); + u1 = r; + break; + } + + u0 = (uint64_t)(pos[0] - '0'); + if (u0 > 9ULL) + return r; + r *= 10; + for (;;) { + r += u0; + if (r < u1) + goto value_overflow; + ++pos; +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + pos += (*pos == '_'); /* allow '_' as a divider. */ +#endif + u0 = (uint64_t)(pos[0] - '0'); + if (u0 > 9ULL) + break; + if (r > ((~(uint64_t)0ULL) / 10)) + goto value_overflow_stepback; + u1 = r; + r *= 10; + } + *pstr = pos; + return r; + +value_overflow_stepback: + --pos; +value_overflow: + r = u1; + errno = E2BIG; + *pstr = pos; + return r; +} + +/** Reads a signed base 10 formatted number. */ +SFUNC int64_t fio_atol10(char **pstr) { + // const uint64_t add_limit = (~(uint64_t)0ULL) - 9; + char *pos = *pstr; + const size_t inv = (pos[0] == '-'); + pos += inv; + // uint64_t val = 0; + // uint64_t r0; + // while (((r0 = pos[0] - '0') < 10ULL) & (val < add_limit)) { + // val *= 10; + // val += r0; + // ++pos; + // } + // if (((size_t)(pos[0] - '0') < 10ULL)) { + // errno = E2BIG; + // } + *pstr = pos; + uint64_t val = fio_atol10u(pstr); + if (((size_t)(**pstr - '0') < 10ULL)) + errno = E2BIG; + return fio_u2i_limit(val, inv); +} + +/** Reads an unsigned hex formatted number (possibly prefixed with "0x"). */ +FIO_IFUNC uint64_t fio___atol16u_with_prefix(uint64_t r, char **pstr) { + size_t d; + unsigned char *p = (unsigned char *)*pstr; + p += ((p[0] == '0') & ((p[1] | 32) == 'x')) << 1; + if ((d = fio_c2i(*p)) > 15) + goto possible_misread; + for (;;) { + r |= d; + ++p; + d = (size_t)fio_c2i(*p); + if (d > 15) + break; + if ((r & UINT64_C(0xF000000000000000))) { + errno = E2BIG; + break; + } + r <<= 4; +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + p += (*p == '_'); /* allow '_' as a divider. */ +#endif + } + *pstr = (char *)p; + return r; +possible_misread: + /* if 0x was read, move to X. */ + *pstr += ((pstr[0][0] == '0') & ((pstr[0][1] | 32) == 'x')); + return r; +} + +/** Reads an unsigned hex formatted number (possibly prefixed with "0x"). */ +SFUNC uint64_t fio_atol16u(char **pstr) { + uint64_t r = 0; + size_t d; + unsigned char *p = (unsigned char *)*pstr; + p += ((p[0] == '0') & ((p[1] | 32) == 'x')) << 1; + if ((d = fio_c2i(*p)) > 15) + goto possible_misread; + for (;;) { + r |= d; + ++p; + d = (size_t)fio_c2i(*p); + if (d > 15) + break; + if ((r & UINT64_C(0xF000000000000000))) { + errno = E2BIG; + break; + } + r <<= 4; +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + p += (*p == '_'); /* allow '_' as a divider. */ +#endif + } + *pstr = (char *)p; + return r; +possible_misread: + /* if 0x was read, move to X. */ + *pstr += ((pstr[0][0] == '0') & ((pstr[0][1] | 32) == 'x')); + return r; +} + +/** Reads an unsigned binary formatted number (possibly prefixed with "0b"). */ +SFUNC FIO___ASAN_AVOID uint64_t fio_atol_bin(char **pstr) { + uint64_t r = 0; + *pstr += (**pstr == '0'); + *pstr += (**pstr | 32) == 'b' && (((size_t)(pstr[0][1]) - (size_t)'0') < 2); +#if FIO___ASAN_DETECTED || 1 + for (;;) { /* Prevent safe overflow of allocated memory region */ + if ((r & UINT64_C(0x8000000000000000))) + break; + size_t len = 0; + union { + uint64_t u64; + uint32_t u32; + } u; + for (size_t i = 0; i < 8; ++i) + len += (((size_t)pstr[0][len] - (size_t)'0') < 2); + if (!len) + goto done; + switch (len & 12) { + case 8: + if ((r & UINT64_C(0xFF00000000000000))) + break; /* from switch */ + u.u64 = fio_buf2u64_be(*pstr); + u.u64 -= 0x0101010101010101ULL * '0'; + u.u64 |= u.u64 >> 7; + u.u64 |= u.u64 >> 14; + u.u64 |= u.u64 >> 28; + u.u64 &= 0xFF; + r <<= 8; + r |= u.u64; + *pstr += 8; + continue; + case 4: + if ((r & UINT64_C(0xF000000000000000))) + break; /* from switch */ + u.u32 = fio_buf2u32_be(*pstr); + u.u32 -= (0x01010101UL * '0'); + u.u32 |= u.u32 >> 7; + u.u32 |= u.u32 >> 14; + u.u32 &= 0x0F; + r <<= 4; + r |= u.u32; + *pstr += 4; + continue; + } + while ((len = (size_t)((unsigned char)(**pstr)) - (size_t)'0') < 2) { + r <<= 1; + r |= len; + ++*pstr; + if ((r & UINT64_C(0x8000000000000000))) + break; + } +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + if ((**pstr == '_') | (**pstr == '.')) { /* allow as a dividers */ + ++*pstr; + continue; + } +#endif + break; + } +done: +#else + size_t d; + for (; (((uintptr_t)*pstr & 4095) < 4089);) { /* respect page boundary */ + uint64_t tmp = fio_buf2u64_be(*pstr); /* may overflow */ + tmp -= 0x0101010101010101ULL * '0'; /* was it all `0`s and `1`s? */ + if (tmp & (~0x0101010101010101ULL)) /* if note, break. */ + break; + tmp |= tmp >> 7; + tmp |= tmp >> 14; + tmp |= tmp >> 28; + tmp &= 0xFF; + r <<= 8; + r |= tmp; + *pstr += 8; + if ((r & UINT64_C(0xFF00000000000000))) + break; + } + while ((d = (size_t)((unsigned char)(**pstr)) - (size_t)'0') < 2) { + r <<= 1; + r |= d; + ++*pstr; + if ((r & UINT64_C(0x8000000000000000))) + break; +#if FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER + *pstr += (**pstr == '_') | (**pstr == '.'); /* allow as a dividers */ +#endif + } +#endif + if (((size_t)(**pstr) - (size_t)'0') < 2) + errno = E2BIG; + return r; +} + +/** Attempts to read an unsigned number in any base up to base 36. */ +SFUNC uint64_t fio_atol_xbase(char **pstr, size_t base) { + uint64_t r = 0; + if (base > 36) + return r; + if (base == 10) + return (r = fio_atol10u(pstr)); + if (base == 16) + return (r = fio_atol16u(pstr)); + if (base == 2) + return (r = fio_atol_bin(pstr)); + const uint64_t limit = (~UINT64_C(0)) / base; + size_t d; + while ((d = (size_t)fio_c2i((unsigned char)(**pstr))) < base) { + r *= base; + r += d; + ++*pstr; + if (r > limit) + break; + } + if ((fio_c2i(**pstr)) < base) + errno = E2BIG; + return r; +} + +/* ***************************************************************************** +fio_atol +***************************************************************************** */ + +SFUNC int64_t FIO___ASAN_AVOID fio_atol(char **pstr) { + /* note: sanitizer avoided due to possible 8 byte overflow within mem-page */ + static uint64_t (*const fn[])(char **) = { + fio_atol10u, + fio_atol8u, + fio_atol_bin, + fio_atol16u, + }; + if (!pstr || !(*pstr)) + return 0; + union { + uint64_t u64; + int64_t i64; + } u = {0}; + char *p = *pstr; + + uint32_t neg = 0, base = 0; + neg = (p[0] == '-'); + p += (neg | (p[0] == '+')); + + base += (p[0] == '0'); /* starts with zero? - oct */ + p += base; /* consume the possible '0' */ + base += ((p[0] | 32) == 'b'); /* binary */ + base += ((p[0] | 32) == 'x') << 1; /* hex */ + p += (base > 1); /* consume 'b' or 'x' */ + char *const s = p; /* mark starting point */ + u.u64 = fn[base](&p); /* convert string to unsigned long long */ + if (p != s || base == 1) /* false oct base, a single '0'? */ + *pstr = p; + if ((neg | !base)) /* if base 10 or negative, treat signed bit as overflow */ + return fio_u2i_limit(u.u64, neg); + return u.i64; +} + +/* ***************************************************************************** +fio_ltoa +***************************************************************************** */ + +SFUNC size_t fio_ltoa(char *dest, int64_t num, uint8_t base) { + size_t len = 0; + uint64_t n = (uint64_t)num; + size_t digits; + char dump[96]; + if (!dest) + dest = dump; + + switch (base) { + case 1: /* fall through */ + case 2: /* Base 2 */ + len += (digits = fio_digits_bin(n)); + fio_ltoa_bin(dest, n, digits); /* embedded sign bit */ + return len; + case 8: /* Base 8 */ + if (num < 0) { + *(dest++) = '-'; + n = 0 - n; + ++len; + } + len += (digits = fio_digits8u(n)); + fio_ltoa8u(dest, n, digits); + return len; + case 16: /* Base 16 */ + len += (digits = fio_digits16u(n)); + fio_ltoa16u(dest, n, digits); /* embedded sign bit */ + return len; + case 0: /* fall through */ + case 10: /* Base 10 */ + if (num < 0) { + *(dest++) = '-'; + n = 0 - n; + ++len; + } + len += (digits = fio_digits10u(n)); + fio_ltoa10u(dest, n, digits); + return len; + default: /* any base up to base 36 */ + if (base > 36) + goto base_error; + if (num < 0) { + *(dest++) = '-'; + n = 0 - n; + ++len; + } + len += (digits = fio_digits_xbase(n, base)); + fio_ltoa_xbase(dest, n, digits, base); + return len; + } + +base_error: + FIO_LOG_ERROR("fio_ltoa base out of range"); + return len; +} + +/* ***************************************************************************** +fio_atof +***************************************************************************** */ + +SFUNC double fio_atof(char **pstr) { + if (!pstr || !(*pstr)) + return 0; + if ((*pstr)[0] == 'b' || ((*pstr)[1] == '0' && (*pstr)[1] == 'b')) + goto binary_raw; + return strtod(*pstr, pstr); +binary_raw: + /* binary representation is assumed to spell an exact double */ + (void)0; + union { + uint64_t i; + double d; + } punned = {.i = (uint64_t)fio_atol_bin(pstr)}; + *pstr += ((**pstr | 32) == 'f'); /* support 0b1111111F */ + return punned.d; +} + +/* ***************************************************************************** +fio_ftoa +***************************************************************************** */ + +SFUNC size_t fio_ftoa(char *dest, double num, uint8_t base) { + if (base == 2 || base == 16) { + /* handle binary / Hex representation the same as an int64_t */ + /* FIXME: Hex representation should use floating-point hex instead */ + union { + int64_t i; + double d; + } p; + p.d = num; + return fio_ltoa(dest, p.i, base); + } + size_t written = 0; + + if (isinf(num)) + goto is_inifinity; + if (isnan(num)) + goto is_nan; + + written = snprintf(dest, 30, "%g", num); + /* test if we need to add a ".0" to the end of the string */ + for (char *start = dest;;) { + switch (*start) { + case ',': + *start = '.'; // locale issues? + /* fall through */ + case 'e': /* fall through */ + case '.': /* fall through */ goto finish; + case 0: goto add_dot_zero; + } + ++start; + } +add_dot_zero: + dest[written++] = '.'; + dest[written++] = '0'; + +finish: + dest[written] = 0; + return written; + +is_inifinity: + if (num < 0) + dest[written++] = '-'; + fio_memcpy8(dest + written, "Infinity"); + written += 8; + dest[written] = 0; + return written; +is_nan: + fio_memcpy4(dest, "NaN"); + return 3; +} + +/* ***************************************************************************** +fio_aton +***************************************************************************** */ +/** Returns a power of 10. Supports values up to 1.0e308. */ +FIO_IFUNC long double fio___aton_pow10(uint64_t e10) { + // clang-format off +#define fio___aton_pow10_map_row(i) 1.0e##i##0L, 1.0e##i##1L, 1.0e##i##2L, 1.0e##i##3L, 1.0e##i##4L, 1.0e##i##5L, 1.0e##i##6L, 1.0e##i##7L, 1.0e##i##8L, 1.0e##i##9L + static const long double pow_map[] = { + fio___aton_pow10_map_row(0), fio___aton_pow10_map_row(1), fio___aton_pow10_map_row(2), fio___aton_pow10_map_row(3), fio___aton_pow10_map_row(4), + fio___aton_pow10_map_row(5), fio___aton_pow10_map_row(6), fio___aton_pow10_map_row(7), fio___aton_pow10_map_row(8), fio___aton_pow10_map_row(9), + fio___aton_pow10_map_row(10), fio___aton_pow10_map_row(11), fio___aton_pow10_map_row(12), fio___aton_pow10_map_row(13), fio___aton_pow10_map_row(14), + fio___aton_pow10_map_row(15), fio___aton_pow10_map_row(16), fio___aton_pow10_map_row(17), fio___aton_pow10_map_row(18), fio___aton_pow10_map_row(19), + fio___aton_pow10_map_row(20), fio___aton_pow10_map_row(21), fio___aton_pow10_map_row(22), fio___aton_pow10_map_row(23), fio___aton_pow10_map_row(24), + fio___aton_pow10_map_row(25), fio___aton_pow10_map_row(26), fio___aton_pow10_map_row(27), fio___aton_pow10_map_row(28), fio___aton_pow10_map_row(29), + 1.0e300L, 1.0e301L, 1.0e302L, 1.0e303L, 1.0e304L, 1.0e305L, 1.0e306L, 1.0e307L, 1.0e308L, // clang-format on + }; +#undef fio___aton_pow10_map_row + if (e10 < sizeof(pow_map) / sizeof(pow_map[0])) + return pow_map[e10]; + return powl(10, e10); /* return infinity? */ +} + +/** Returns a power of 10. Supports values up to 1.0e-308. */ +FIO_IFUNC long double fio___aton_pow10n(uint64_t e10) { + // clang-format off +#define fio___aton_pow10_map_row(i) 1.0e-##i##0L, 1.0e-##i##1L, 1.0e-##i##2L, 1.0e-##i##3L, 1.0e-##i##4L, 1.0e-##i##5L, 1.0e-##i##6L, 1.0e-##i##7L, 1.0e-##i##8L, 1.0e-##i##9L + static const long double pow_map[] = { + fio___aton_pow10_map_row(0), fio___aton_pow10_map_row(1), fio___aton_pow10_map_row(2), fio___aton_pow10_map_row(3), fio___aton_pow10_map_row(4), + fio___aton_pow10_map_row(5), fio___aton_pow10_map_row(6), fio___aton_pow10_map_row(7), fio___aton_pow10_map_row(8), fio___aton_pow10_map_row(9), + fio___aton_pow10_map_row(10), fio___aton_pow10_map_row(11), fio___aton_pow10_map_row(12), fio___aton_pow10_map_row(13), fio___aton_pow10_map_row(14), + fio___aton_pow10_map_row(15), fio___aton_pow10_map_row(16), fio___aton_pow10_map_row(17), fio___aton_pow10_map_row(18), fio___aton_pow10_map_row(19), + fio___aton_pow10_map_row(20), fio___aton_pow10_map_row(21), fio___aton_pow10_map_row(22), fio___aton_pow10_map_row(23), fio___aton_pow10_map_row(24), + fio___aton_pow10_map_row(25), fio___aton_pow10_map_row(26), fio___aton_pow10_map_row(27), fio___aton_pow10_map_row(28), fio___aton_pow10_map_row(29), + 1.0e-300L, 1.0e-301L, 1.0e-302L, 1.0e-303L, 1.0e-304L, 1.0e-305L, 1.0e-306L, 1.0e-307L, 1.0e-308L, // clang-format on + }; +#undef fio___aton_pow10_map_row + if (e10 < sizeof(pow_map) / sizeof(pow_map[0])) + return pow_map[e10]; + return powl(10, (int64_t)(0 - e10)); /* return zero? */ +} + +FIO_SFUNC FIO___ASAN_AVOID fio_aton_s fio_aton(char **pstr) { + /* note: sanitizer avoided due to possible 8 byte overflow within mem-page */ + static uint64_t (*const fn[])(char **) = { + fio_atol10u, + fio_atol8u, + fio_atol_bin, + fio_atol16u, + }; + static uint32_t base_limit[] = {10, 8, 1, 16}; + static char exponent_char[] = "eepp"; + fio_aton_s r = {0}; + long double dbl = 0, dbl_dot = 0; + if (!pstr || !(*pstr)) + return r; + char *start, *head, *p = *pstr; + uint64_t before_dot = 0, after_dot = 0, expo = 0; + size_t head_expo = 0, dot_expo = 0; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + ++p; + + uint16_t neg = 0, expo_neg = 0, base = 0, force_float = 0; + neg = (p[0] == '-'); + p += (neg | (p[0] == '+')); + + if ((p[0] | 32) == 'i') + goto is_infinity; + if ((p[0] | 32) == 'n') + goto is_nan; + + base += (p[0] == '0'); /* oct */ + p += base; /* consume '0' */ + base += ((p[0] | 32) == 'b'); /* binary */ + base += ((p[0] | 32) == 'x') << 1; /* hex */ + base -= (base & (p[0] == '.')); /* 0. isn't oct... */ + p += (base > 1); /* consume 'b' or 'x' */ + start = p; /* mark starting point */ + + // FIO_LOG_INFO("Start Unsigned: %s", p); + before_dot = fn[base]((char **)&p); + if (base == 2) + goto is_binary; + head = p; + while (fio_c2i(p[0]) < base_limit[base]) + ++p; + head_expo = p - head; + force_float |= !!(head_expo); + if (p[0] == '.') { + ++p; + force_float = 1; + head = p; + after_dot = fn[base]((char **)&p); + dot_expo = p - head; + while (fio_c2i(p[0]) < base_limit[base]) + ++p; + } + if ((p[0] | 32) == exponent_char[base]) { + force_float = 1; + ++p; + expo_neg = (p[0] == '-'); + p += (expo_neg | (p[0] == '+')); + expo = fio_atol10u((char **)&p); + while ((uint8_t)(p[0] - '0') < 10) + ++p; + } + if (p != start || base == 1) /* false oct base, a single '0'? */ + *pstr = p; + // FIO_LOG_INFO("Start Tail: %s", p); + if (!force_float && (!(before_dot & ((uint64_t)1ULL << 63)) || + (!neg && base))) { /* is integer */ + r.u = before_dot; + if (neg) + r.i = 0 - r.u; + return r; + } + dbl = (long double)before_dot; + dbl_dot = (long double)after_dot; + if (!base) { + dbl *= fio___aton_pow10(head_expo); + if (after_dot) + dbl_dot *= fio___aton_pow10n(dot_expo); + } else if (base == 3) { + dbl *= fio_u2d(1, (head_expo << 2)); + dbl_dot *= fio_u2d(1, 0 - (dot_expo << 2)); + } else { /* if (base == 1) */ + dbl *= fio_u2d(1, (head_expo * 3)); + dbl_dot *= fio_u2d(1, 0 - (dot_expo * 3)); + } + dbl += dbl_dot; + if (expo) { + if (base < 2) { /* base 10 / Oct */ + dbl *= (expo_neg ? fio___aton_pow10n : fio___aton_pow10)(expo); + } else { + dbl *= fio_u2d(1, (int64_t)(expo_neg ? 0 - expo : expo)); + } + } + r.is_float = 1; + r.f = (double)dbl; + r.u |= (uint64_t)neg << 63; + return r; + +is_infinity: + if ((p[1] | 32) == 'n' && (p[2] | 32) == 'f') { /* inf */ + r.is_float = 1; + r.u = ((uint64_t)neg << 63) | ((uint64_t)2047ULL << 52); + p += 3 + (((p[3] | 32) == 'i' && + fio_buf2u64u("infinity") == + (fio_buf2u64u(p) | 0x2020202020202020ULL)) * + 5); + *pstr = (char *)p; + } else + r.err = 1; + return r; + +is_nan: + if ((p[1] | 32) == 'a' && (p[2] | 32) == 'n') { /* nan */ + r.is_float = 1; + r.i = ((~(uint64_t)0) >> (!neg)); + p += 3; + *pstr = (char *)p; + } else + r.err = 1; + return r; + +is_binary: + if (p == start) + return r; + r.u = before_dot; + r.is_float = ((p[0] | 32) == 'f'); + p += r.is_float; + *pstr = (char *)p; + return r; +} + +/* ***************************************************************************** +Big Numbers +***************************************************************************** */ + +FIO_IFUNC void fio___uXXX_hex_read(uint64_t *t, char **p, size_t l) { + char *start = *p; + start += (((unsigned)(start[0] == '0') & (start[1] == 'x')) << 1); + char *pos = start; + while (fio_i2c((uint8_t)*pos) < 16) + ++pos; + ++pos; + *p = pos; + for (size_t i = 0; i < l; ++i) { /* per uint64_t in t */ + uint64_t wrd = 0; + for (size_t j = 0; j < 16 && pos > start; ++j) { + --pos; + wrd |= ((uint64_t)fio_i2c((uint8_t)*pos) << (j << 2)); + } + *t = wrd; + ++t; + } +} + +FIO_IFUNC size_t fio___uXXX_hex_write(char *dest, const uint64_t *t, size_t l) { + while (--l && !t[l]) + ; + if (!l && !t[0]) { + dest[0] = '0'; + return 1; + } + char *pos = dest; + size_t digits = fio_digits16u(t[l]); + ++l; + while (l--) { + fio_ltoa16u(pos, t[l], digits); + pos += digits; + digits = 16; + } + return (size_t)(pos - dest); +} + +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u128 fio_u128_hex_read(char **pstr) { + fio_u128 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u256 fio_u256_hex_read(char **pstr) { + fio_u256 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u512 fio_u512_hex_read(char **pstr) { + fio_u512 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u1024 fio_u1024_hex_read(char **pstr) { + fio_u1024 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u2048 fio_u2048_hex_read(char **pstr) { + fio_u2048 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} +/** Reads a hex numeral string and initializes the numeral. */ +SFUNC fio_u4096 fio_u4096_hex_read(char **pstr) { + fio_u4096 r; + fio___uXXX_hex_read(r.u64, pstr, sizeof(r) / sizeof(r.u64[0])); + return r; +} + +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u128_hex_write(char *dest, const fio_u128 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u256_hex_write(char *dest, const fio_u256 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u512_hex_write(char *dest, const fio_u512 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u1024_hex_write(char *dest, const fio_u1024 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u2048_hex_write(char *dest, const fio_u2048 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} +/** Prints out the underlying 64 bit array (for debugging). */ +SFUNC size_t fio_u4096_hex_write(char *dest, const fio_u4096 *u) { + return fio___uXXX_hex_write(dest, u->u64, sizeof(u->u64) / sizeof(u->u64[0])); +} + +/* ***************************************************************************** +Numbers <=> Strings - Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_ATOL */ +#undef FIO_ATOL +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_GLOB_MATCH /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Globe Matching + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_GLOB_MATCH) && !defined(H___FIO_GLOB_MATCH___H) +#define H___FIO_GLOB_MATCH___H + +/* ***************************************************************************** +Globe Matching API +***************************************************************************** */ + +/** A binary glob matching helper. Returns 1 on match, otherwise returns 0. */ +SFUNC uint8_t fio_glob_match(fio_str_info_s pattern, fio_str_info_s string); + +/* ***************************************************************************** + + + + + Globe Matching Implementation + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Globe Matching Monitoring Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** + * Glob Matching + **************************************************************************** */ + +/** A binary glob matching helper. Returns 1 on match, otherwise returns 0. */ +SFUNC uint8_t fio_glob_match(fio_str_info_s pat, fio_str_info_s str) { + /* adapted and rewritten, with thankfulness, from the code at: + * https://github.com/opnfv/kvmfornfv/blob/master/kernel/lib/glob.c + * + * Original version's copyright: + * Copyright 2015 Open Platform for NFV Project, Inc. and its contributors + * Under the MIT license. + */ + + /* + * Backtrack to previous * on mismatch and retry starting one + * character later in the string. Because * matches all characters, + * there's never a need to backtrack multiple levels. + */ + uint8_t *back_pat = NULL, *back_str = (uint8_t *)str.buf; + size_t back_pat_len = 0, back_str_len = str.len; + + /* + * Loop over each token (character or class) in pat, matching + * it against the remaining unmatched tail of str. Return false + * on mismatch, or true after matching the trailing nul bytes. + */ + while (str.len && pat.len) { + uint8_t c = *(uint8_t *)str.buf++; + uint8_t d = *(uint8_t *)pat.buf++; + str.len--; + pat.len--; + + switch (d) { + case '?': /* Wildcard: anything goes */ break; + + case '*': /* Any-length wildcard */ + if (!pat.len) /* Optimize trailing * case */ + return 1; + back_pat = (uint8_t *)pat.buf; + back_pat_len = pat.len; + back_str = (uint8_t *)--str.buf; /* Allow zero-length match */ + back_str_len = ++str.len; + break; + + case '[': { /* Character class */ + uint8_t match = 0, inverted = (*(uint8_t *)pat.buf == '^' || + *(uint8_t *)pat.buf == '!'); + uint8_t *cls = (uint8_t *)pat.buf + inverted; + uint8_t a = *cls++; + + /* + * Iterate over each span in the character class. + * A span is either a single character a, or a + * range a-b. The first span may begin with ']'. + */ + do { + uint8_t b = a; + if (a == '\\') { /* when escaped, next character is regular */ + b = a = *(cls++); + } else if (cls[0] == '-' && cls[1] != ']') { + b = cls[1]; + + cls += 2; + if (a > b) { + uint8_t tmp = a; + a = b; + b = tmp; + } + } + match |= (a <= c && c <= b); + } while ((a = *cls++) != ']'); + + if (match == inverted) + goto backtrack; + pat.len -= cls - (uint8_t *)pat.buf; + pat.buf = (char *)cls; + + } break; + case '\\': d = *(uint8_t *)pat.buf++; pat.len--; + /* fall through */ + default: /* Literal character */ + if (c == d) + break; + backtrack: + if (!back_pat) + return 0; /* No point continuing */ + /* Try again from last *, one character later in str. */ + pat.buf = (char *)back_pat; + str.buf = (char *)++back_str; + str.len = --back_str_len; + pat.len = back_pat_len; + } + } + /* if the trailing pattern allows for empty data, skip it */ + while (pat.len && pat.buf[0] == '*') { + ++pat.buf; + --pat.len; + } + return !str.len && !pat.len; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_GLOB_MATCH_MONITOR_MAX +#endif /* FIO_GLOB_MATCH */ +#undef FIO_GLOB_MATCH +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IMAP_CORE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Index Maps (mapping a partial hash to an Array object) + Maps a Log 2 sized index map to a position in a Log 2 sized Array + + + +Copyright: Boaz Segev, 2019-2021; License: ISC / MIT (choose your license) +***************************************************************************** */ +#if defined(FIO_IMAP_CORE) && !defined(H___FIO_IMAP_CORE___H) +#define H___FIO_IMAP_CORE___H + +/* ***************************************************************************** +iMap Helper Macros +***************************************************************************** */ + +/** Helper macro for simple iMap array types. */ +#define FIO_IMAP_ALWAYS_VALID(o) (1) +/** Helper macro for simple iMap array types. */ +#define FIO_IMAP_ALWAYS_CMP_TRUE(a, b) (1) +/** Helper macro for simple iMap array types. */ +#define FIO_IMAP_ALWAYS_CMP_FALSE(a, b) (0) +/** Helper macro for simple iMap array types. */ +#define FIO_IMAP_SIMPLE_CMP(a, b) ((a)[0] == (b)[0]) +/** Helper macro for simple iMap array types. */ +#define FIO_IMAP_EACH(array_name, map_ptr, i) \ + for (size_t i = 0; i < (map_ptr)->w; ++i) \ + if (!FIO_NAME(array_name, is_valid)((map_ptr)->ary + i)) \ + continue; \ + else + +/* ***************************************************************************** +iMap Creation Macro +***************************************************************************** */ + +/** + * This MACRO defines the type and functions needed for an indexed array. + * + * This is used internally and documentation is poor. + * + * An indexed array is simple ordered array who's objects are indexed using an + * almost-hash map, allowing for easy seeking while also enjoying an array's + * advantages. + * + * The index map uses one `imap_type` (i.e., `uint64_t`) to store both the array + * index and any leftover hash data (the first half being tested during the + * random access and the leftover during comparison). The reserved value `0` + * indicates a free slot. The reserved value `~0` indicates a freed item (a free + * slot that was previously used). + * + * - `array_name_s` the main array container (.ary is the array itself) + * - `array_name_seeker_s` is a seeker type that finds objects. + * - `array_name_seek` finds an object or its future position. + * + * - `array_name_capa` the imap's theoretical storage capacity. + * - `array_name_set` writes or overwrites data to the array. + * - `array_name_get` returns a pointer to the object within the array. + * - `array_name_remove` removes an object and resets its memory to zero. + * - `array_name_reserve` reserves a minimum imap storage capacity. + * - `array_name_rehash` re-builds the imap (use after sorting). + */ +#define FIO_TYPEDEF_IMAP_ARRAY(array_name, \ + array_type, \ + imap_type, \ + hash_fn, \ + cmp_fn, \ + is_valid_fn) \ + FIO_LEAK_COUNTER_DEF(FIO_NAME(array_name, s)) \ + typedef struct { \ + array_type *ary; \ + imap_type count; \ + imap_type w; \ + uint32_t capa_bits; \ + } FIO_NAME(array_name, s); \ + typedef struct { \ + imap_type pos; \ + imap_type ipos; \ + imap_type set_val; \ + } FIO_NAME(array_name, seeker_s); \ + /** Returns the theoretical capacity for the indexed array. */ \ + FIO_IFUNC int FIO_NAME(array_name, is_valid)(array_type * pobj) { \ + return pobj && (!!is_valid_fn(pobj)); \ + } \ + /** Returns the theoretical capacity for the indexed array. */ \ + FIO_IFUNC size_t FIO_NAME(array_name, capa)(FIO_NAME(array_name, s) * a) { \ + if (!a || !a->capa_bits) \ + return 0; \ + return ((size_t)1ULL << a->capa_bits); \ + } \ + /** Returns a pointer to the index map. */ \ + FIO_IFUNC imap_type *FIO_NAME(array_name, \ + imap)(FIO_NAME(array_name, s) * a) { \ + return (imap_type *)(a->ary + ((imap_type)1ULL << a->capa_bits)); \ + } \ + /** Deallocates dynamic memory. */ \ + FIO_IFUNC void FIO_NAME(array_name, destroy)(FIO_NAME(array_name, s) * a) { \ + size_t capa = FIO_NAME(array_name, capa)(a); \ + if (a->ary) { \ + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(array_name, s)); \ + FIO_TYPEDEF_IMAP_FREE( \ + a->ary, \ + (capa * (sizeof(*a->ary)) + (capa * (sizeof(imap_type))))); \ + } \ + *a = (FIO_NAME(array_name, s)){0}; \ + (void)capa; /* if unused */ \ + } \ + /** Allocates dynamic memory. */ \ + FIO_IFUNC int FIO_NAME(array_name, __alloc)(FIO_NAME(array_name, s) * a, \ + size_t bits) { \ + if (!bits || bits > ((sizeof(imap_type) << 3) - 2)) \ + return -1; \ + size_t capa = 1ULL << bits; \ + size_t old_capa = FIO_NAME(array_name, capa)(a); \ + array_type *tmp = (array_type *)FIO_TYPEDEF_IMAP_REALLOC( \ + a->ary, \ + (a->bits ? (old_capa * (sizeof(array_type)) + \ + (old_capa * (sizeof(imap_type)))) \ + : 0), \ + (capa * (sizeof(array_type)) + (capa * (sizeof(imap_type)))), \ + (a->w * (sizeof(array_type)))); \ + (void)old_capa; /* if unused */ \ + if (!tmp) \ + return -1; \ + if (!a->ary) \ + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(array_name, s)); \ + a->capa_bits = (uint32_t)bits; \ + a->ary = tmp; \ + if (!FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE) { \ + FIO_MEMSET((tmp + a->w), 0, ((capa - a->w) * (sizeof(*tmp)))); \ + FIO_MEMSET((tmp + capa), 0, (capa * (sizeof(imap_type)))); \ + } \ + return 0; \ + } \ + /** Returns the index map position and array position of a value, if any. */ \ + FIO_SFUNC FIO_NAME(array_name, seeker_s) \ + FIO_NAME(array_name, seek)(FIO_NAME(array_name, s) * a, \ + array_type * pobj) { \ + FIO_NAME(array_name, seeker_s) \ + r = {0, ((imap_type) ~(imap_type)0), ((imap_type) ~(imap_type)0)}; \ + if (!a || ((!a->capa_bits) | (!a->ary))) \ + return r; \ + r.pos = a->w; \ + imap_type capa = (imap_type)1UL << a->capa_bits; \ + imap_type *imap = (imap_type *)(a->ary + capa); \ + const imap_type pos_mask = (imap_type)(capa - (imap_type)1); \ + const imap_type hash_mask = (imap_type)~pos_mask; \ + const imap_type hash = (imap_type)hash_fn(pobj); \ + imap_type tester = (hash & hash_mask); /* hides lower bits for `tester` */ \ + imap_type pos = hash + (hash >> a->capa_bits); /* use more bits for pos */ \ + tester += (!tester) << a->capa_bits; \ + tester -= (hash_mask == tester) << a->capa_bits; \ + size_t attempts = 11; \ + for (;;) { \ + /* tests up to 3 groups of 4 bytes (uint32_t) within a 64 byte group */ \ + for (size_t mini_steps = 0;;) { \ + pos &= pos_mask; \ + const imap_type pos_hash = imap[pos] & hash_mask; \ + const imap_type pos_index = imap[pos] & pos_mask; \ + if ((pos_hash == tester) && cmp_fn((a->ary + pos_index), pobj)) { \ + r.ipos = pos; \ + r.pos = pos_index; \ + r.set_val = tester | pos_index; \ + return r; \ + } \ + if (!imap[pos]) { \ + r.ipos = pos; \ + r.set_val = tester | r.pos; /* r.pos == a->w */ \ + return r; \ + } \ + if (imap[pos] == (imap_type)(~(imap_type)0)) { \ + r.ipos = pos; \ + r.set_val = tester | r.pos; /* r.pos == a->w */ \ + } \ + if (!((--attempts))) \ + return r; \ + if (mini_steps == 2) \ + break; \ + pos += 3 + mini_steps; /* 0, 3, 7 = max of 56 byte distance */ \ + ++mini_steps; \ + } \ + pos += (imap_type)0xC19F5985UL; /* big step */ \ + } \ + } \ + /** fills an empty imap with the info about existing elements. */ \ + FIO_SFUNC int FIO_NAME(array_name, \ + __fill_imap)(FIO_NAME(array_name, s) * a) { \ + if (!a->count) { \ + a->w = 0; \ + return 0; \ + } \ + imap_type *imap = FIO_NAME(array_name, imap)(a); \ + if (a->count != a->w) { \ + a->count = 0; \ + for (size_t i = 0; i < a->w; ++i) { \ + if (!is_valid_fn((a->ary + i))) \ + continue; \ + if (a->count != i) \ + a->ary[a->count] = a->ary[i]; \ + ++a->count; \ + } \ + } \ + for (a->w = 0; a->w < a->count; ++(a->w)) { \ + FIO_NAME(array_name, seeker_s) \ + s = FIO_NAME(array_name, seek)(a, a->ary + a->w); \ + if (s.pos != a->w || s.ipos == (imap_type)(~(imap_type)0)) { \ + a->w = a->count; \ + return -1; /* destination not big enough to contain collisions! */ \ + } \ + imap[s.ipos] = s.set_val; \ + } \ + a->w = a->count; \ + return 0; \ + } \ + /** expands the existing array & imap storage capacity. */ \ + FIO_IFUNC int FIO_NAME(array_name, __expand)(FIO_NAME(array_name, s) * a) { \ + for (;;) { \ + if (FIO_NAME(array_name, __alloc)(a, \ + a->capa_bits + 1 + (!a->capa_bits))) \ + return -1; \ + if (!FIO_NAME(array_name, __fill_imap)(a)) \ + return 0; \ + } \ + } \ + /** Reserves a minimum imap storage capacity. */ \ + FIO_IFUNC int FIO_NAME(array_name, reserve)(FIO_NAME(array_name, s) * a, \ + imap_type min) { \ + imap_type bits = 2; \ + if (min > ((imap_type)~0ULL) >> 1) \ + return -1; \ + while ((1ULL << bits) < min) \ + ++bits; \ + if (bits <= a->capa_bits) \ + return 0; \ + if (FIO_NAME(array_name, __alloc)(a, bits)) \ + return -1; \ + if (!FIO_NAME(array_name, __fill_imap)(a)) \ + return 0; \ + return FIO_NAME(array_name, __expand)(a); \ + } \ + /** Rehashes the array and fills the imap (use after sorting). */ \ + FIO_IFUNC int FIO_NAME(array_name, rehash)(FIO_NAME(array_name, s) * a) { \ + if (!a || !a->ary) \ + return -1; \ + size_t bytes = sizeof(imap_type) * ((size_t)1ULL << a->capa_bits); \ + imap_type *imap = FIO_NAME(array_name, imap)(a); \ + FIO_MEMSET(imap, 0, bytes); \ + if (!FIO_NAME(array_name, __fill_imap)(a)) \ + return -1; \ + return 0; \ + } \ + /** Sets an object in the Array. Optionally overwrites existing data. */ \ + FIO_IFUNC array_type *FIO_NAME(array_name, set)(FIO_NAME(array_name, s) * a, \ + array_type obj, \ + int overwrite) { \ + if (!a || !is_valid_fn((&obj))) \ + return NULL; \ + { \ + size_t capa = FIO_NAME(array_name, capa)(a); \ + if (a->w == capa) \ + FIO_NAME(array_name, __expand)(a); \ + else if (a->count != a->w && \ + (a->w + (a->w >> 1)) > FIO_NAME(array_name, capa)(a)) { \ + FIO_MEMSET((a->ary + capa), 0, (capa * (sizeof(imap_type)))); \ + FIO_NAME(array_name, __fill_imap)(a); \ + } \ + } \ + for (;;) { \ + FIO_NAME(array_name, seeker_s) s = FIO_NAME(array_name, seek)(a, &obj); \ + if (s.ipos == (imap_type)(~(imap_type)0)) { /* no room in the imap */ \ + FIO_NAME(array_name, __expand)(a); \ + continue; \ + } \ + if (s.pos == a->w) { /* new object */ \ + a->ary[a->w] = obj; \ + ++a->w; \ + ++a->count; \ + FIO_NAME(array_name, imap)(a)[s.ipos] = s.set_val; \ + return a->ary + s.pos; \ + } \ + FIO_ASSERT_DEBUG(s.pos < a->w && s.ipos < FIO_NAME(array_name, capa)(a), \ + "WTF?"); \ + if (!overwrite) \ + return a->ary + s.pos; \ + a->ary[s.pos] = obj; \ + return a->ary + s.pos; \ + } \ + } \ + /** Finds an object in the Array using the index map. */ \ + FIO_IFUNC array_type *FIO_NAME(array_name, get)(FIO_NAME(array_name, s) * a, \ + array_type obj) { \ + if (!a || !is_valid_fn((&obj))) \ + return NULL; \ + FIO_NAME(array_name, seeker_s) s = FIO_NAME(array_name, seek)(a, &obj); \ + if (s.pos >= a->w) \ + return NULL; \ + return a->ary + s.pos; \ + } \ + /** Removes an object in the Array's index map, zeroing out its memory. */ \ + FIO_IFUNC int FIO_NAME(array_name, remove)(FIO_NAME(array_name, s) * a, \ + array_type obj) { \ + if (!a || !is_valid_fn((&obj))) \ + return -1; \ + FIO_NAME(array_name, seeker_s) s = FIO_NAME(array_name, seek)(a, &obj); \ + if (s.pos >= a->w) \ + return -1; \ + a->ary[s.pos] = (array_type){0}; \ + FIO_NAME(array_name, imap)(a)[s.ipos] = (imap_type)(~(imap_type)0); \ + --a->count; \ + while (a->w && !is_valid_fn((a->ary + a->w - 1))) \ + --a->w; \ + return 0; \ + } + +#ifndef FIO_TYPEDEF_IMAP_REALLOC +#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC +#endif +#ifndef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE +#endif + +#ifndef FIO_TYPEDEF_IMAP_FREE +#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE +#endif + +#define FIO___IMAP_SEEKER_TYPE(bits) \ + typedef struct { \ + uint##bits##_t pos; \ + uint##bits##_t ipos; \ + uint##bits##_t set_val; \ + bool is_valid; \ + } fio___imap##bits##_seeker_s; \ + /** Returns the index map position and array position of a value, if any. */ \ + FIO_SFUNC fio___imap##bits##_seeker_s fio___imap##bits##_seek( \ + void *ary, \ + uint##bits##_t *imap, \ + const uint##bits##_t capa_bits, \ + void *pobj, \ + uint##bits##_t hash, \ + bool cmp_fn(void *arry, void *obj, uint##bits##_t indx), \ + const size_t max_attempts) { \ + fio___imap##bits##_seeker_s r = {(uint##bits##_t)(~(uint##bits##_t)0), \ + (uint##bits##_t)(~(uint##bits##_t)0), \ + (uint##bits##_t)(~(uint##bits##_t)0)}; \ + if (!ary) \ + return r; \ + const uint##bits##_t capa = ((uint##bits##_t)1 << capa_bits); \ + const uint##bits##_t pos_mask = (uint##bits##_t)(capa - 1); \ + const uint##bits##_t hash_mask = (uint##bits##_t) ~pos_mask; \ + uint##bits##_t tester = (hash & hash_mask); /* hide `tester` lower bits */ \ + uint##bits##_t pos = hash; /* use more bits */ \ + /* make sure tester isn't a reserved value (0 || ~0) */ \ + tester += (!tester) << capa_bits; \ + tester -= (hash_mask == tester) << capa_bits; \ + r.set_val = tester; /* store tester value */ \ + size_t attempts = max_attempts; \ + /* tests up to 3 groups of 4 bytes (uint32_t) within a 64 byte group */ \ + for (;;) { \ + for (size_t mini_steps = 0;;) { \ + pos &= pos_mask; \ + const uint##bits##_t pos_hash = imap[pos] & hash_mask; \ + const uint##bits##_t pos_index = imap[pos] & pos_mask; \ + if ((pos_hash == tester) && cmp_fn(ary, pobj, pos_index)) { \ + r.ipos = pos; \ + r.pos = pos_index; \ + r.set_val = tester | pos_index; \ + r.is_valid = 1; \ + return r; \ + } \ + if (!imap[pos]) { \ + r.ipos = pos; \ + r.set_val = tester; /* mark empty slot */ \ + return r; \ + } \ + if (imap[pos] == (uint##bits##_t)(~(uint##bits##_t)0)) { \ + r.ipos = pos; \ + } \ + if (!((--attempts))) \ + return r; \ + if (mini_steps == 2) \ + break; \ + pos += 3 + mini_steps; /* 0, 3, 7 => max of 56 byte distance */ \ + ++mini_steps; \ + } \ + pos += (uint##bits##_t)0xC19F5985UL; /* big step */ \ + } \ + } \ + /** utilizes the values returned by the seeker object. */ \ + FIO_IFUNC void fio___imap##bits##_set(uint##bits##_t *imap, \ + uint##bits##_t ipos, \ + uint##bits##_t set_val) { \ + imap[ipos] = set_val; \ + } + +FIO___IMAP_SEEKER_TYPE(8) +FIO___IMAP_SEEKER_TYPE(16) +FIO___IMAP_SEEKER_TYPE(32) +FIO___IMAP_SEEKER_TYPE(64) + +/* ***************************************************************************** +iMap Cleanup +***************************************************************************** */ +#endif /* FIO_IMAP_CORE */ +#undef FIO_IMAP_CORE +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MATH /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Basic Math Operations and Multi-Precision + Constant Time (when possible) + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MATH) && !defined(H___FIO_MATH___H) +#define H___FIO_MATH___H 1 + +/* ***************************************************************************** +Multi-precision, little endian helpers. + +Works with little endian uint64_t arrays or 64 bit numbers. +***************************************************************************** */ + +/** + * Multi-precision DIV for `len*64` bit long a, b. + * + * This is NOT constant time. + * + * The algorithm might be slow, as my math isn't that good and I couldn't + * understand faster division algorithms (such as Newton–Raphson division)... so + * this is sort of a factorized variation on long division. + */ +FIO_IFUNC void fio_math_div(uint64_t *dest, + uint64_t *reminder, + const uint64_t *a, + const uint64_t *b, + const size_t number_array_length); + +/** Multi-precision shift right for `len` word number `n`. */ +FIO_IFUNC void fio_math_shr(uint64_t *dest, + uint64_t *n, + const size_t right_shift_bits, + size_t number_array_length); + +/** Multi-precision shift left for `len*64` bit number `n`. */ +FIO_IFUNC void fio_math_shl(uint64_t *dest, + uint64_t *n, + const size_t left_shift_bits, + const size_t number_array_length); + +/** Multi-precision Inverse for `len*64` bit number `n` (turn `1` into `-1`). */ +FIO_IFUNC void fio_math_inv(uint64_t *dest, uint64_t *n, size_t len); + +/** Multi-precision - returns the index for the most significant bit or -1. */ +FIO_MIFN size_t fio_math_msb_index(uint64_t *n, const size_t len); + +/** Multi-precision - returns the index for the least significant bit or -1. */ +FIO_MIFN size_t fio_math_lsb_index(uint64_t *n, const size_t len); + +/* ***************************************************************************** +Multi-precision, little endian helpers. Works with full uint64_t arrays. +***************************************************************************** */ + +/** Multi-precision Inverse for `bits` number `n`. */ +FIO_IFUNC void fio_math_inv(uint64_t *dest, uint64_t *n, const size_t len) { + uint64_t c = 1; + for (size_t i = 0; i < len; ++i) { + uint64_t tmp = ~n[i] + c; + c = (tmp ^ n[i]) >> 63; + dest[i] = tmp; + } +} + +/** Multi-precision shift right for `bits` number `n`. */ +FIO_IFUNC void fio_math_shr(uint64_t *dest, + uint64_t *n, + size_t bits, + size_t len) { + const size_t offset = len - (bits >> 6); + bits &= 63; + // FIO_LOG_DEBUG("Shift Light of %zu bytes and %zu + // bits", len - offset, bits); + uint64_t c = 0, trash; + uint64_t *p_select[] = {dest + offset, &trash}; + if (bits) { + while (len--) { + --p_select[0]; + uint64_t ntmp = n[len]; + uint64_t ctmp = (ntmp << (64 - bits)); + dest[len] &= (uint64_t)0ULL - (len < offset); + p_select[p_select[0] < dest][0] = ((ntmp >> bits) | c); + c = ctmp; + } + return; + } + while (len--) { + --p_select[0]; + uint64_t ntmp = n[len]; + dest[len] &= (uint64_t)0ULL - (len < offset); + p_select[p_select[0] < dest][0] = ntmp; + } +} + +/** Multi-precision shift left for `bits` number `n`. */ +FIO_IFUNC void fio_math_shl(uint64_t *dest, + uint64_t *n, + size_t bits, + const size_t len) { + if (!len || !bits || !n || !dest) + return; + const size_t offset = bits >> 6; + bits &= 63; + uint64_t c = 0, trash; + uint64_t *p_select[] = {dest + offset, &trash}; + if (bits) { + for (size_t i = 0; i < len; (++i), ++p_select[0]) { + uint64_t ntmp = n[i]; + uint64_t ctmp = (ntmp >> (64 - bits)) & ((uint64_t)0ULL - (!!bits)); + ; + dest[i] &= (uint64_t)0ULL - (i >= offset); + p_select[p_select[0] >= (dest + len)][0] = ((ntmp << bits) | c); + c = ctmp; + } + return; + } + for (size_t i = 0; i < len; (++i), ++p_select[0]) { + uint64_t ntmp = n[i]; + dest[i] &= (uint64_t)0ULL - (i >= offset); + p_select[p_select[0] >= (dest + len)][0] = ntmp; + } +} + +/** Multi-precision - returns the index for the most + * significant bit. */ +FIO_IFUNC size_t fio_math_msb_index(uint64_t *n, size_t len) { + size_t r[2] = {0, (size_t)-1}; + uint64_t a = 0; + while (len--) { + const uint64_t mask = ((uint64_t)0ULL - (!a)); + a |= (mask & n[len]); + r[0] += (64 & (~mask)); + } + r[0] += fio_bits_msb_index(a); + return r[!a]; +} + +/** Multi-precision - returns the index for the least + * significant bit. */ +FIO_IFUNC size_t fio_math_lsb_index(uint64_t *n, const size_t len) { + size_t r[2] = {0, (size_t)-1}; + uint64_t a = 0; + uint64_t mask = (~(uint64_t)0ULL); + for (size_t i = 0; i < len; ++i) { + a |= mask & n[i]; + mask = ((uint64_t)0ULL - (!a)); + r[0] += (64 & mask); + } + r[0] += fio_bits_lsb_index(a); + return r[!a]; +} + +/** Multi-precision DIV for `len*64` bit long a, b. NOT + * constant time. */ +FIO_IFUNC void fio_math_div(uint64_t *dest, + uint64_t *reminder, + const uint64_t *a, + const uint64_t *b, + const size_t len) { + if (!len) + return; +#if !defined(_MSC_VER) && (!defined(__cplusplus) || __cplusplus > 201402L) + uint64_t t[len]; + uint64_t r[len]; + uint64_t q[len]; +#else + uint64_t t[256]; + uint64_t r[256]; + uint64_t q[256]; + FIO_ASSERT( + len <= 256, + "Multi Precision DIV (fio_math_div) overflows at 16384 bit numbers"); +#endif + FIO_MEMCPY(r, a, sizeof(uint64_t) * len); + FIO_MEMSET(q, 0, sizeof(uint64_t) * len); + size_t rlen; + uint64_t c, mask, imask; + const size_t blen = fio_math_msb_index((uint64_t *)b, len) + 1; + if (!blen) + goto divide_by_zero; /* divide by zero! */ + while ((rlen = fio_math_msb_index((uint64_t *)r, len)) >= blen) { + const size_t delta = rlen - blen; + fio_math_shl(t, (uint64_t *)b, delta, len); + (void)fio_math_sub(r, (uint64_t *)r, t, len); + q[delta >> 6] |= (1ULL << (delta & 63)); /* set the bit used */ + } + mask = (uint64_t)0ULL - fio_math_sub(t, (uint64_t *)r, (uint64_t *)b, len); + imask = ~mask; /* r was >= b */ + q[0] = fio_math_addc64(q[0], (imask & 1), 0, &c); + for (size_t i = 1; i < len; ++i) { + q[i] = fio_math_addc64(q[i], 0, c, &c); + } + if (dest) { + FIO_MEMCPY(dest, q, len * sizeof(uint64_t)); + } + if (reminder) { + for (size_t i = 0; i < len; ++i) { + reminder[i] = (t[i] & imask) | (r[i] & mask); + } + } + return; +divide_by_zero: + FIO_LOG_ERROR("divide by zero!"); + if (dest) + FIO_MEMSET(dest, 0xFFFFFFFF, sizeof(*dest) * len); + if (reminder) + FIO_MEMSET(reminder, 0xFFFFFFFF, sizeof(*dest) * len); + return; +} + +/* ***************************************************************************** +Math - cleanup +***************************************************************************** */ +#endif /* FIO_MATH */ +#undef FIO_MATH +#undef FIO_MIFN +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_RAND /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Psedo-Random Generator Functions + and friends - risky hash / stable hash + + + +Copyright and License: see header file (000 copyright.h) / top of file +***************************************************************************** */ +#if defined(FIO_RAND) && !defined(H___FIO_RAND_H) +#define H___FIO_RAND_H + +/* ***************************************************************************** +Random - API +***************************************************************************** */ + +/** Returns 64 psedo-random bits. Probably not cryptographically safe. */ +SFUNC uint64_t fio_rand64(void); + +/** Writes `len` bytes of psedo-random bits to the target buffer. */ +SFUNC void fio_rand_bytes(void *target, size_t len); + +/** Feeds up to 1023 bytes of entropy to the random state. */ +IFUNC void fio_rand_feed2seed(void *buf_, size_t len); + +/** Reseeds the random engine using system state (rusage / jitter). */ +SFUNC void fio_rand_reseed(void); + +/* ***************************************************************************** +Risky / Stable Hash - API +***************************************************************************** */ + +/** Computes a facil.io Risky Hash (Risky v.3). */ +SFUNC uint64_t fio_risky_hash(const void *buf, size_t len, uint64_t seed); + +/** Adds a bit of entropy to pointer values. Designed to be unsafe. */ +FIO_IFUNC uint64_t fio_risky_ptr(void *ptr); + +/** Adds a bit of entropy to numeral values. Designed to be unsafe. */ +FIO_IFUNC uint64_t fio_risky_num(uint64_t number, uint64_t seed); + +/** Computes a facil.io Stable Hash (will not be updated, even if broken). */ +SFUNC uint64_t fio_stable_hash(const void *data, size_t len, uint64_t seed); + +/** Computes a facil.io Stable Hash (will not be updated, even if broken). */ +SFUNC void fio_stable_hash128(void *restrict dest, + const void *restrict data, + size_t len, + uint64_t seed); + +#define FIO_USE_STABLE_HASH_WHEN_CALLING_RISKY_HASH 0 +/* ***************************************************************************** +Risky Hash - Implementation + +Note: I don't remember what information I used when designing this, but Risky +Hash is probably NOT cryptographically safe (though I wish it was). + +Here's a few resources about hashes that might explain more: +- https://komodoplatform.com/cryptographic-hash-function/ +- https://en.wikipedia.org/wiki/Avalanche_effect +- http://ticki.github.io/blog/designing-a-good-non-cryptographic-hash-function/ + +***************************************************************************** */ + +/** Adds bit entropy to a pointer values. Designed to be unsafe. */ +FIO_IFUNC uint64_t fio_risky_num(uint64_t n, uint64_t seed) { + seed ^= fio_lrot64(seed, 47); + seed += FIO_U64_HASH_PRIME0; + seed = seed | 1; + uint64_t h = n + seed; + h += fio_lrot64(seed, 5); + h += fio_bswap64(seed); + h += fio_lrot64(h, 27); + h += fio_lrot64(h, 49); + return h; +} + +/** Adds bit entropy to a pointer values. Designed to be unsafe. */ +FIO_IFUNC uint64_t fio_risky_ptr(void *ptr) { + return fio_risky_num((uint64_t)(uintptr_t)ptr, FIO_U64_HASH_PRIME9); +} + +/* ***************************************************************************** +Possibly `extern` Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* Computes a facil.io Risky Hash. */ +SFUNC uint64_t fio_risky_hash(const void *data_, size_t len, uint64_t seed) { +#if FIO_USE_STABLE_HASH_WHEN_CALLING_RISKY_HASH + return fio_stable_hash(data_, len, seed); +#endif +#define FIO___RISKY_HASH_ROUND64() \ + do { \ + for (size_t i = 0; i < 8; ++i) /* use little endian? */ \ + w[i] = fio_ltole64(w[i]); \ + for (size_t i = 0; i < 8; ++i) { /* xor vector with input (words) */ \ + v[i] ^= w[i]; \ + } \ + for (size_t i = 0; i < 4; ++i) { /* MUL folding, adding high bits */ \ + v[i] += fio_math_mulc64(v[i], v[i + 4], w + i); \ + v[i + 4] += w[i]; \ + } \ + } while (0) + /* Approach inspired by komihash, copyrighted: Aleksey Vaneev, MIT license */ + const uint8_t *data = (const uint8_t *)data_; + uint64_t v[8] FIO_ALIGN(16), w[8] FIO_ALIGN(16) = {0}; + uint64_t const prime[8] FIO_ALIGN(16) = { + FIO_U64_HASH_PRIME1, + FIO_U64_HASH_PRIME2, + FIO_U64_HASH_PRIME3, + FIO_U64_HASH_PRIME4, + FIO_U64_HASH_PRIME5, + FIO_U64_HASH_PRIME6, + FIO_U64_HASH_PRIME7, + FIO_U64_HASH_PRIME0, + }; + /* seed mixing is constant time to avoid leaking seed data */ + seed += len; + seed ^= fio_lrot64(seed, 47); + /* initialize vector with mixed secret */ + for (size_t i = 0; i < 8; ++i) + v[i] = seed + prime[i]; + /* pad uneven head with zeros and consume (if any) */ + if ((len & 63)) { + for (size_t i = 0; i < 8; ++i) + w[i] = 0; + fio_memcpy63x(w, data, len); + data += (len & 63); + ((uint8_t *)w)[63] = (uint8_t)(len & 63); + FIO___RISKY_HASH_ROUND64(); + } + /* consumes remaining 64 bytes (512 bits) blocks */ + for (size_t j = 63; j < len; j += 64) { + for (size_t i = 0; i < 4; ++i) + v[i] += prime[i]; /* mark each round, may double mark if(!(len & 63)) */ + fio_memcpy64(w, data); + data += 64; + FIO___RISKY_HASH_ROUND64(); + } + + w[4] = (v[0] ^ v[1]) + (v[1] ^ v[2]) + (v[2] ^ v[3]); + w[5] = (v[4] + v[5]) ^ (v[5] + v[6]) ^ (v[6] + v[7]); + w[6] = (w[0] + w[1]) ^ (w[1] + w[2]) ^ (w[2] + w[3]); + v[0] = w[5] + fio_math_mulc64(w[4], w[6], v + 1); + v[0] += v[1]; + return v[0]; +#undef FIO___RISKY_HASH_ROUND64 +} + +/* ***************************************************************************** +Stable Hash (unlike Risky Hash, this can be used for non-ephemeral hashing) +***************************************************************************** */ +#define FIO_STABLE_HASH_ROUND_WORD(i) \ + v[i] += w[i]; \ + v[i] += prime[i]; \ + v[i] *= prime[i]; \ + w[i] = fio_lrot64(w[i], 19); \ + v[i] += w[i] + seed; + +FIO_IFUNC void fio_stable_hash___inner(uint64_t dest[4], + const void *restrict data_, + const size_t len, + uint64_t seed) { + const uint8_t *data = (const uint8_t *)data_; + /* seed selection is constant time to avoid leaking seed data */ + seed += len; + seed ^= fio_lrot64(seed, 47); + seed = (seed << 1) + 1; + uint64_t v[4] = {seed, seed, seed, seed}; + uint64_t const prime[4] = {FIO_U32_HASH_PRIME0, + FIO_U32_HASH_PRIME1, + FIO_U32_HASH_PRIME2, + FIO_U32_HASH_PRIME3}; + + for (size_t j = 31; j < len; j += 32) { + /* consumes 32 bytes (256 bits) each loop */ + uint64_t w[4]; + for (size_t i = 0; i < 4; ++i) { + w[i] = fio_ltole64(fio_buf2u64u(data)); + data += 8; + FIO_STABLE_HASH_ROUND_WORD(i); + } + } + { /* pad with zeros (even if %32 == 0) and add len to last word */ + uint64_t w[4] = {0}; + fio_memcpy31x(w, data, len); /* copies `len & 31` bytes */ + for (size_t i = 0; i < 4; ++i) + w[i] = fio_ltole64(w[i]); + w[3] += len; + for (size_t i = 0; i < 4; ++i) { + FIO_STABLE_HASH_ROUND_WORD(i); + } + } + for (size_t i = 0; i < 4; ++i) { + dest[i] = v[i]; + } +} + +/* Computes a facil.io Stable Hash. */ +SFUNC uint64_t fio_stable_hash(const void *data_, size_t len, uint64_t seed) { + uint64_t r; + uint64_t v[4]; + fio_stable_hash___inner(v, data_, len, seed); + /* summing & avalanche */ + r = v[0] + v[1] + v[2] + v[3]; + for (size_t i = 0; i < 4; ++i) + v[i] = fio_bswap64(v[i]); + r ^= fio_lrot64(r, 5); + r += v[0] ^ v[1]; + r ^= fio_lrot64(r, 27); + r += v[1] ^ v[2]; + r ^= fio_lrot64(r, 49); + r += v[2] ^ v[3]; + r ^= (r >> 29) * FIO_U64_HASH_PRIME0; + r ^= fio_lrot64(r, 29); + return r; +} + +SFUNC void fio_stable_hash128(void *restrict dest, + const void *restrict data_, + size_t len, + uint64_t seed) { + + uint64_t v[4]; + fio_stable_hash___inner(v, data_, len, seed); + uint64_t r[2]; + uint64_t prime[2] = {FIO_U64_HASH_PRIME0, FIO_U64_HASH_PRIME1}; + r[0] = v[0] + v[1] + v[2] + v[3]; + r[1] = v[0] ^ v[1] ^ v[2] ^ v[3]; + for (size_t i = 0; i < 4; ++i) + v[i] = fio_bswap64(v[i]); + for (size_t i = 0; i < 2; ++i) { + r[i] ^= fio_lrot64(r[i], 5); + r[i] += v[0] ^ v[1]; + r[i] ^= fio_lrot64(r[i], 27); + r[i] += v[1] ^ v[2]; + r[i] ^= fio_lrot64(r[i], 49); + r[i] += v[2] ^ v[3]; + r[i] ^= (r[i] >> 29) * prime[i]; + r[i] ^= fio_lrot64(r[i], 29); + } + fio_memcpy16(dest, r); +} + +#undef FIO_STABLE_HASH_ROUND_WORD +/* ***************************************************************************** +Random - Implementation +***************************************************************************** */ + +#if FIO_OS_POSIX || \ + (__has_include("sys/resource.h") && __has_include("sys/time.h")) +#include +#include +#endif + +static volatile uint64_t fio___rand_state[4]; /* random state */ +static volatile size_t fio___rand_counter; /* seed counter */ +/* feeds random data to the algorithm through this 256 bit feed. */ +static volatile uint64_t fio___rand_buffer[4] = {0x9c65875be1fce7b9ULL, + 0x7cc568e838f6a40d, + 0x4bb8d885a0fe47d5, + 0x95561f0927ad7ecd}; + +IFUNC void fio_rand_feed2seed(void *buf_, size_t len) { + len &= 1023; + uint8_t *buf = (uint8_t *)buf_; + uint8_t offset = (fio___rand_counter & 3); + uint64_t tmp = 0; + for (size_t i = 0; i < (len >> 3); ++i) { + tmp = fio_buf2u64u(buf); + fio___rand_buffer[(offset++ & 3)] ^= tmp; + buf += 8; + } + if ((len & 7)) { + tmp = 0; + fio_memcpy7x(&tmp, buf, len); + fio___rand_buffer[(offset++ & 3)] ^= tmp; + } +} + +SFUNC void fio_rand_reseed(void) { + const size_t jitter_samples = 16 | (fio___rand_state[0] & 15); +#if defined(RUSAGE_SELF) + { + struct rusage rusage; + getrusage(RUSAGE_SELF, &rusage); + fio___rand_state[0] ^= + fio_risky_hash(&rusage, sizeof(rusage), fio___rand_state[0]); + } +#endif + for (size_t i = 0; i < jitter_samples; ++i) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + uint64_t clk = + (uint64_t)((t.tv_sec << 30) + (int64_t)t.tv_nsec) + fio___rand_counter; + fio___rand_state[0] ^= fio_risky_num(clk, fio___rand_state[0] + i); + fio___rand_state[1] ^= fio_risky_num(clk, fio___rand_state[1] + i); + } + { + uint64_t tmp[2]; + tmp[0] = fio_risky_num(fio___rand_buffer[0], fio___rand_state[0]) + + fio_risky_num(fio___rand_buffer[1], fio___rand_state[1]); + tmp[1] = fio_risky_num(fio___rand_buffer[2], fio___rand_state[0]) + + fio_risky_num(fio___rand_buffer[3], fio___rand_state[1]); + fio___rand_state[2] ^= tmp[0]; + fio___rand_state[3] ^= tmp[1]; + } + fio___rand_buffer[0] = fio_lrot64(fio___rand_buffer[0], 31); + fio___rand_buffer[1] = fio_lrot64(fio___rand_buffer[1], 29); + fio___rand_buffer[2] ^= fio___rand_buffer[0]; + fio___rand_buffer[3] ^= fio___rand_buffer[1]; + fio___rand_counter += jitter_samples; +} + +/* tested for randomness using code from: http://xoshiro.di.unimi.it/hwd.php */ +SFUNC uint64_t fio_rand64(void) { + /* modeled after xoroshiro128+, by David Blackman and Sebastiano Vigna */ + uint64_t r = 0; + if (!((fio___rand_counter++) & (((size_t)1 << 12) - 1))) { + /* re-seed state every 524,288 requests / 2^19-1 attempts */ + fio_rand_reseed(); + } + const uint64_t s0[] = {fio___rand_state[0], + fio___rand_state[1], + fio___rand_state[2], + fio___rand_state[3]}; /* load to registers */ + uint64_t s1[4]; + { + const uint64_t mulp[] = {0x37701261ED6C16C7ULL, + 0x764DBBB75F3B3E0DULL, + ~(0x37701261ED6C16C7ULL), + ~(0x764DBBB75F3B3E0DULL)}; /* load to registers */ + const uint64_t addc[] = {fio___rand_counter, 0, fio___rand_counter, 0}; + for (size_t i = 0; i < 4; ++i) { + s1[i] = fio_lrot64(s0[i], 33); + s1[i] += addc[i]; + s1[i] *= mulp[i]; + s1[i] += s0[i]; + } + } + for (size_t i = 0; i < 4; ++i) { /* store to memory */ + fio___rand_state[i] = s1[i]; + } + { + uint8_t rotc[] = {31, 29, 27, 30}; + for (size_t i = 0; i < 4; ++i) { + s1[i] = fio_lrot64(s1[i], rotc[i]); + r += s1[i]; + } + } + return r; +} + +/* copies 64 bits of randomness (8 bytes) repeatedly. */ +SFUNC void fio_rand_bytes(void *data_, size_t len) { + if (!data_ || !len) + return; + uint8_t *data = (uint8_t *)data_; + for (unsigned i = 31; i < len; i += 32) { + uint64_t rv[4] = {fio_rand64(), fio_rand64(), fio_rand64(), fio_rand64()}; + fio_memcpy32(data, rv); + data += 32; + } + if (len & 31) { + uint64_t rv[4] = {fio_rand64(), fio_rand64(), fio_rand64(), fio_rand64()}; + fio_memcpy31x(data, rv, len); + } +} +/* ***************************************************************************** +Random - Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_RAND */ +#undef FIO_RAND +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SIGNAL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Signal Monitoring + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SIGNAL) && !defined(H___FIO_SIGNAL___H) +#define H___FIO_SIGNAL___H + +#ifndef FIO_SIGNAL_MONITOR_MAX +/* The maximum number of signals the implementation will be able to monitor */ +#define FIO_SIGNAL_MONITOR_MAX 24 +#endif + +#if !(FIO_OS_POSIX) && !(FIO_OS_WIN) /* use FIO_HAVE_UNIX_TOOLS instead? */ +#error Either POSIX or Windows are required for the fio_signal API. +#endif + +#include +/* ***************************************************************************** +Signal Monitoring API +***************************************************************************** */ + +/** + * Starts to monitor for the specified signal, setting an optional callback. + */ +SFUNC int fio_signal_monitor(int sig, + void (*callback)(int sig, void *), + void *udata); + +/** Reviews all signals, calling any relevant callbacks. */ +SFUNC int fio_signal_review(void); + +/** Stops monitoring the specified signal. */ +SFUNC int fio_signal_forget(int sig); + +/* ***************************************************************************** + + + + + Signal Monitoring Implementation + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Signal Monitoring Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +POSIX implementation +***************************************************************************** */ +#ifdef FIO_OS_POSIX + +static struct { + int32_t sig; + volatile unsigned flag; + void (*callback)(int sig, void *); + void *udata; + struct sigaction old; +} fio___signal_watchers[FIO_SIGNAL_MONITOR_MAX]; + +FIO_SFUNC void fio___signal_catcher(int sig) { + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + if (!fio___signal_watchers[i].sig && !fio___signal_watchers[i].udata) + return; /* initialized list is finishe */ + if (fio___signal_watchers[i].sig != sig) + continue; + /* mark flag */ + fio___signal_watchers[i].flag = 1; + /* pass-through if exists */ + if (fio___signal_watchers[i].old.sa_handler != SIG_IGN && + fio___signal_watchers[i].old.sa_handler != SIG_DFL) + fio___signal_watchers[i].old.sa_handler(sig); + return; + } +} + +/** + * Starts to monitor for the specified signal, setting an optional callback. + */ +SFUNC int fio_signal_monitor(int sig, + void (*callback)(int sig, void *), + void *udata) { + if (!sig) + return -1; + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + /* updating an existing monitor */ + if (fio___signal_watchers[i].sig == sig) { + fio___signal_watchers[i].callback = callback; + fio___signal_watchers[i].udata = udata; + return 0; + } + /* slot busy */ + if (fio___signal_watchers[i].sig || fio___signal_watchers[i].callback) + continue; + /* place monitor in this slot */ + struct sigaction act; + memset(&act, 0, sizeof(act)); + memset(fio___signal_watchers + i, 0, sizeof(fio___signal_watchers[i])); + fio___signal_watchers[i].sig = sig; + fio___signal_watchers[i].callback = callback; + fio___signal_watchers[i].udata = udata; + act.sa_handler = fio___signal_catcher; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(sig, &act, &fio___signal_watchers[i].old)) { + FIO_LOG_ERROR("couldn't set signal handler: %s", strerror(errno)); + fio___signal_watchers[i].callback = NULL; + fio___signal_watchers[i].udata = (void *)1; + fio___signal_watchers[i].sig = 0; + return -1; + } + return 0; + } + return -1; +} + +/** Stops monitoring the specified signal. */ +SFUNC int fio_signal_forget(int sig) { + if (!sig) + return -1; + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + if (!fio___signal_watchers[i].sig && !fio___signal_watchers[i].udata) + return -1; /* initialized list is finishe */ + if (fio___signal_watchers[i].sig != sig) + continue; + fio___signal_watchers[i].callback = NULL; + fio___signal_watchers[i].udata = (void *)1; + fio___signal_watchers[i].sig = 0; + struct sigaction act; + memset(&act, 0, sizeof(act)); + if (sigaction(sig, &fio___signal_watchers[i].old, &act)) { + FIO_LOG_ERROR("couldn't unset signal handler: %s", strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/* ***************************************************************************** +Windows Implementation +***************************************************************************** */ +#elif FIO_OS_WIN + +static struct { + int32_t sig; + volatile unsigned flag; + void (*callback)(int sig, void *); + void *udata; + void (*old)(int sig); +} fio___signal_watchers[FIO_SIGNAL_MONITOR_MAX]; + +FIO_SFUNC void fio___signal_catcher(int sig) { + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + if (!fio___signal_watchers[i].sig && !fio___signal_watchers[i].udata) + return; /* initialized list is finished */ + if (fio___signal_watchers[i].sig != sig) + continue; + /* mark flag */ + fio___signal_watchers[i].flag = 1; + /* pass-through if exists */ + if (fio___signal_watchers[i].old && + (intptr_t)fio___signal_watchers[i].old != (intptr_t)SIG_IGN && + (intptr_t)fio___signal_watchers[i].old != (intptr_t)SIG_DFL) { + fio___signal_watchers[i].old(sig); + fio___signal_watchers[i].old = signal(sig, fio___signal_catcher); + } else { + fio___signal_watchers[i].old = signal(sig, fio___signal_catcher); + } + break; + } +} + +/** + * Starts to monitor for the specified signal, setting an optional callback. + */ +SFUNC int fio_signal_monitor(int sig, + void (*callback)(int sig, void *), + void *udata) { + if (!sig) + return -1; + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + /* updating an existing monitor */ + if (fio___signal_watchers[i].sig == sig) { + fio___signal_watchers[i].callback = callback; + fio___signal_watchers[i].udata = udata; + return 0; + } + /* slot busy */ + if (fio___signal_watchers[i].sig || fio___signal_watchers[i].callback) + continue; + /* place monitor in this slot */ + fio___signal_watchers[i].sig = sig; + fio___signal_watchers[i].callback = callback; + fio___signal_watchers[i].udata = udata; + fio___signal_watchers[i].old = signal(sig, fio___signal_catcher); + if ((intptr_t)SIG_ERR == (intptr_t)fio___signal_watchers[i].old) { + fio___signal_watchers[i].sig = 0; + fio___signal_watchers[i].callback = NULL; + fio___signal_watchers[i].udata = (void *)1; + fio___signal_watchers[i].old = NULL; + FIO_LOG_ERROR("couldn't set signal handler: %s", strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/** Stops monitoring the specified signal. */ +SFUNC int fio_signal_forget(int sig) { + if (!sig) + return -1; + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + if (!fio___signal_watchers[i].sig && !fio___signal_watchers[i].udata) + return -1; /* initialized list is finished */ + if (fio___signal_watchers[i].sig != sig) + continue; + fio___signal_watchers[i].callback = NULL; + fio___signal_watchers[i].udata = (void *)1; + fio___signal_watchers[i].sig = 0; + if (fio___signal_watchers[i].old) { + if ((intptr_t)signal(sig, fio___signal_watchers[i].old) == + (intptr_t)SIG_ERR) + goto sig_error; + } else { + if ((intptr_t)signal(sig, SIG_DFL) == (intptr_t)SIG_ERR) + goto sig_error; + } + return 0; + } + return -1; +sig_error: + FIO_LOG_ERROR("couldn't unset signal handler: %s", strerror(errno)); + return -1; +} +#endif /* POSIX vs WINDOWS */ + +/* ***************************************************************************** +Common OS implementation +***************************************************************************** */ + +/** Reviews all signals, calling any relevant callbacks. */ +SFUNC int fio_signal_review(void) { + int c = 0; + for (size_t i = 0; i < FIO_SIGNAL_MONITOR_MAX; ++i) { + if (!fio___signal_watchers[i].sig && !fio___signal_watchers[i].udata) + return c; + if (fio___signal_watchers[i].flag) { + fio___signal_watchers[i].flag = 0; + ++c; + if (fio___signal_watchers[i].callback) + fio___signal_watchers[i].callback(fio___signal_watchers[i].sig, + fio___signal_watchers[i].udata); + } + } + return c; +} + +/* ***************************************************************************** +Signal Monitoring Testing? +***************************************************************************** */ +#ifdef FIO_TEST_ALL +FIO_SFUNC void FIO_NAME_TEST(stl, signal)(void) { + +#define FIO___SIGNAL_MEMBER(a) \ + { (int)a, #a } + struct { + int sig; + const char *name; + } t[] = { + FIO___SIGNAL_MEMBER(SIGINT), + FIO___SIGNAL_MEMBER(SIGILL), + FIO___SIGNAL_MEMBER(SIGABRT), + FIO___SIGNAL_MEMBER(SIGSEGV), + FIO___SIGNAL_MEMBER(SIGTERM), +#if FIO_OS_POSIX + FIO___SIGNAL_MEMBER(SIGQUIT), + FIO___SIGNAL_MEMBER(SIGHUP), + FIO___SIGNAL_MEMBER(SIGTRAP), + FIO___SIGNAL_MEMBER(SIGBUS), + FIO___SIGNAL_MEMBER(SIGFPE), + FIO___SIGNAL_MEMBER(SIGUSR1), + FIO___SIGNAL_MEMBER(SIGUSR2), + FIO___SIGNAL_MEMBER(SIGPIPE), + FIO___SIGNAL_MEMBER(SIGALRM), + FIO___SIGNAL_MEMBER(SIGCHLD), + FIO___SIGNAL_MEMBER(SIGCONT), +#endif + }; +#undef FIO___SIGNAL_MEMBER + size_t e = 0; + fprintf(stderr, "* testing signal monitoring (setup / cleanup only).\n"); + for (size_t i = 0; i < sizeof(t) / sizeof(t[0]); ++i) { + if (fio_signal_monitor(t[i].sig, NULL, NULL)) { + FIO_LOG_ERROR("couldn't set signal monitoring for %s (%d)", + t[i].name, + t[i].sig); + e = 1; + } + } + for (size_t i = 0; i < sizeof(t) / sizeof(t[0]); ++i) { + if (fio_signal_forget(t[i].sig)) { + FIO_LOG_ERROR("couldn't stop signal monitoring for %s (%d)", + t[i].name, + t[i].sig); + e = 1; + } + } + FIO_ASSERT(!e, "signal monitoring error"); +} + +#endif /* FIO_TEST_ALL */ +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_SIGNAL_MONITOR_MAX +#endif /* FIO_SIGNAL */ +#undef FIO_SIGNAL +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SORT_NAME num /* Development inclusion - ignore line */ +#define FIO_SORT_TYPE size_t /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + A Good Enough Sorting Helper + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef FIO_SORT_NAME + +/* ***************************************************************************** +Sort Settings +***************************************************************************** */ + +#ifndef FIO_SORT_TYPE +#error FIO_SORT_TYPE must contain a valid type name! +#endif + +#ifndef FIO_SORT_THRESHOLD +/** The default threshold below which quicksort delegates to insert sort. */ +#define FIO_SORT_THRESHOLD 96 +#endif + +#ifndef FIO_SORT_SWAP +/** Default swap operation assumes an array and swaps array members */ +#define FIO_SORT_SWAP(a, b) \ + do { \ + FIO_SORT_TYPE tmp__ = (a); \ + (a) = (b); \ + (b) = tmp__; \ + } while (0) +#endif + +#ifndef FIO_SORT_IS_BIGGER +/** MUST evaluate as 1 if a > b (zero if equal or smaller). */ +#define FIO_SORT_IS_BIGGER(a, b) ((a) > (b)) +#endif + +/* ***************************************************************************** +Sort API +***************************************************************************** */ + +/* Sorts a `FIO_SORT_TYPE` array with `count` members (quicksort). */ +FIO_IFUNC void FIO_NAME(FIO_SORT_NAME, sort)(FIO_SORT_TYPE *array, + size_t count); + +/* Insert sort, for small arrays of `FIO_SORT_TYPE`. */ +SFUNC void FIO_NAME(FIO_SORT_NAME, isort)(FIO_SORT_TYPE *array, size_t count); + +/* Quick sort, for larger arrays of `FIO_SORT_TYPE`. */ +SFUNC void FIO_NAME(FIO_SORT_NAME, qsort)(FIO_SORT_TYPE *array, size_t count); + +/* ***************************************************************************** +Sort Implementation - inlined static functions +see ideas from: https://youtu.be/FJJTYQYB1JQ +***************************************************************************** */ + +/* Sorts a `FIO_SORT_TYPE` array with `count` members (quicksort). */ +FIO_IFUNC void FIO_NAME(FIO_SORT_NAME, sort)(FIO_SORT_TYPE *array, + size_t count) { + FIO_NAME(FIO_SORT_NAME, qsort)(array, count); +} + +/* ***************************************************************************** +Sort Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* Insert sort, for small arrays of `FIO_SORT_TYPE`. */ +SFUNC void FIO_NAME(FIO_SORT_NAME, isort)(FIO_SORT_TYPE *array, size_t count) { + /* a fast(ish) small sort on small arrays */ + if ((!count | !array)) + return; + if (count < 3) { /* special case */ + if (FIO_SORT_IS_BIGGER((array[0]), (array[count == 2]))) + FIO_SORT_SWAP((array[0]), (array[1])); + return; + } + /* place smallest item in position array[0] (guard element) */ + for (size_t pos = 1; pos < count; ++pos) { + if (FIO_SORT_IS_BIGGER((array[0]), (array[pos]))) { + FIO_SORT_SWAP((array[0]), (array[pos])); + } + } + /* perform insert sort */ + for (size_t i = 2; i < count; ++i) { + for (size_t a = i - 1; FIO_SORT_IS_BIGGER((array[a]), (array[a + 1])); + --a) { + FIO_SORT_SWAP((array[a]), (array[a + 1])); + } + } +} + +/* Sorts a `FIO_SORT_TYPE` array with `count` members. */ +SFUNC void FIO_NAME(FIO_SORT_NAME, qsort)(FIO_SORT_TYPE *array, size_t count) { + /* With thanks to Douglas C. Schmidt, as I used his code for reference: + * https://code.woboq.org/userspace/glibc/stdlib/qsort.c.html + */ + if ((!count | !array)) + return; + if (count < FIO_SORT_THRESHOLD) { + FIO_NAME(FIO_SORT_NAME, isort)(array, count); + return; + } + /* no recursion, setup a stack that can hold log2(count). */ + struct { + FIO_SORT_TYPE *lo; + FIO_SORT_TYPE *hi; + } queue[CHAR_BIT * sizeof(count) + 1], *top = queue; +#define fio_sort___queue_push(l, h) \ + top->lo = l; \ + top->hi = h; \ + ++top; + /* push all the array as the first queued partition */ + fio_sort___queue_push(array, array + (count - 1)); + for (;;) { + FIO_SORT_TYPE *lo; + FIO_SORT_TYPE *hi; + FIO_SORT_TYPE *mid; + --top; /* pop stack */ + lo = top->lo; + hi = top->hi; + const size_t slice_len = (hi - lo) + 1; + + /* sort small ranges using insert sort */ + if (slice_len < FIO_SORT_THRESHOLD) { + FIO_NAME(FIO_SORT_NAME, isort)(lo, slice_len); + if (queue == top) + return; + continue; + } + + /* select a median element (1 of 3, fist, middle, last). */ + /* this also promises ordering between these 3 elements. */ + mid = lo + ((slice_len) >> 1); + if (FIO_SORT_IS_BIGGER((lo[0]), (hi[0]))) + FIO_SORT_SWAP((lo[0]), (hi[0])); + if (FIO_SORT_IS_BIGGER((lo[0]), (mid[0]))) + FIO_SORT_SWAP((lo[0]), (mid[0])); + else if (FIO_SORT_IS_BIGGER((mid[0]), (hi[0]))) + FIO_SORT_SWAP((hi[0]), (mid[0])); + + /* partition: swap elements and pointers so mid is a partition pivot */ + FIO_SORT_TYPE *left = lo + 1; + FIO_SORT_TYPE *right = hi - 2; + /* place mid in the lower partition and update pointer, as it's known */ + FIO_SORT_SWAP((right[1]), (mid[0])); + mid = right + 1; + for (;;) { + /* while order is fine, move on. */ + while (FIO_SORT_IS_BIGGER((mid[0]), (left[0]))) + ++left; + while (FIO_SORT_IS_BIGGER((right[0]), (mid[0]))) + --right; + /* order issue encountered (relative to pivot / mid)... */ + if (left < right) { + /* right now, left is bigger than mid *and* right is smaller... swap. */ + FIO_SORT_SWAP(left[0], right[0]); + ++left; + --right; + continue; + } + /* we passed the middle point and so, we can finish partitioning */ + if (left > right) + break; + /* left == right (odd numbered array) */ + ++left; + --right; + break; + } + /* push partitions in order of size to the stack (clears smaller first) */ + if ((right - lo) > (hi - left)) { + fio_sort___queue_push(lo, right); + fio_sort___queue_push(left, hi); + } else { + fio_sort___queue_push(left, hi); + fio_sort___queue_push(lo, right); + } + } +} +#undef fio_sort___queue_push + +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_SORT_TYPE +#undef FIO_SORT_TEST +#undef FIO_SORT_SWAP +#undef FIO_SORT_IS_BIGGER +#undef FIO_SORT_NAME +#endif /* FIO_SORT_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_THREADS /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Simple Portable Threads + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_THREADS) && !defined(H___FIO_THREADS___H) +#define H___FIO_THREADS___H + +/* ***************************************************************************** +Module Settings + +At this point, define any MACROs and customizable settings available to the +developer. +***************************************************************************** */ + +#if FIO_OS_POSIX /* POSIX Systems */ +#include +#include +#include + +#ifndef FIO_THREADS_BYO +typedef pthread_t fio_thread_t; +#endif + +#ifndef FIO_THREADS_FORK_BYO +typedef pid_t fio_thread_pid_t; +#endif + +#ifndef FIO_THREADS_MUTEX_BYO +typedef pthread_mutex_t fio_thread_mutex_t; +/** Used this macro for static initialization. */ +#define FIO_THREAD_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER +#endif + +#ifndef FIO_THREADS_COND_BYO +typedef pthread_cond_t fio_thread_cond_t; +#endif + +#elif FIO_OS_WIN /* Windows Systems */ +#include + +#ifndef FIO_THREADS_BYO +typedef HANDLE fio_thread_t; +#endif + +#ifndef FIO_THREADS_FORK_BYO +typedef DWORD fio_thread_pid_t; +#endif + +#ifndef FIO_THREADS_MUTEX_BYO +typedef CRITICAL_SECTION fio_thread_mutex_t; +/** Used this macro for static initialization. */ +#define FIO_THREAD_MUTEX_INIT ((fio_thread_mutex_t){0}) +#endif + +#ifndef FIO_THREADS_COND_BYO +typedef CONDITION_VARIABLE fio_thread_cond_t; +#endif + +#else /* No Known System */ +#if !defined(FIO_THREADS_BYO) || !defined(FIO_THREADS_FORK_BYO) || \ + !defined(FIO_THREADS_MUTEX_BYO) || !defined(FIO_THREADS_COND_BYO) +#error facil.io Simple Portable Threads require a POSIX system or Windows +#endif +#endif /* system detection */ + +/* ***************************************************************************** +API for forking processes +***************************************************************************** */ + +/** Should behave the same as the POSIX system call `fork`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_fork(void); + +/** Should behave the same as the POSIX system call `getpid`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_getpid(void); + +/** Should behave the same as the POSIX system call `kill`. */ +FIO_IFUNC int fio_thread_kill(fio_thread_pid_t pid, int sig); + +/** Should behave the same as the POSIX system call `waitpid`. */ +FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t pid, + int *stat_loc, + int options); + +/* ***************************************************************************** +API for spawning threads +***************************************************************************** */ + +/** Starts a new thread, returns 0 on success and -1 on failure. */ +FIO_IFUNC int fio_thread_create(fio_thread_t *t, + void *(*fn)(void *), + void *arg); + +/** Waits for the thread to finish. */ +FIO_IFUNC int fio_thread_join(fio_thread_t *t); + +/** Detaches the thread, so thread resources are freed automatically. */ +FIO_IFUNC int fio_thread_detach(fio_thread_t *t); + +/** Ends the current running thread. */ +FIO_IFUNC void fio_thread_exit(void); + +/* Returns non-zero if both threads refer to the same thread. */ +FIO_IFUNC int fio_thread_equal(fio_thread_t *a, fio_thread_t *b); + +/** Returns the current thread. */ +FIO_IFUNC fio_thread_t fio_thread_current(void); + +/** Yields thread execution. */ +FIO_IFUNC void fio_thread_yield(void); + +/** Possible thread priority values. */ +typedef enum { + FIO_THREAD_PRIORITY_ERROR = -1, + FIO_THREAD_PRIORITY_LOWEST = 0, + FIO_THREAD_PRIORITY_LOW, + FIO_THREAD_PRIORITY_NORMAL, + FIO_THREAD_PRIORITY_HIGH, + FIO_THREAD_PRIORITY_HIGHEST, +} fio_thread_priority_e; + +/** Returns a thread's priority level. */ +FIO_SFUNC fio_thread_priority_e fio_thread_priority(void); + +/** Sets a thread's priority level. */ +FIO_SFUNC int fio_thread_priority_set(fio_thread_priority_e); + +/* ***************************************************************************** +API for mutexes +***************************************************************************** */ + +/** + * Initializes a simple Mutex. + * + * Or use the static initialization value: FIO_THREAD_MUTEX_INIT + */ +FIO_IFUNC int fio_thread_mutex_init(fio_thread_mutex_t *m); + +/** Locks a simple Mutex, returning -1 on error. */ +FIO_IFUNC int fio_thread_mutex_lock(fio_thread_mutex_t *m); + +/** Attempts to lock a simple Mutex, returning zero on success. */ +FIO_IFUNC int fio_thread_mutex_trylock(fio_thread_mutex_t *m); + +/** Unlocks a simple Mutex, returning zero on success or -1 on error. */ +FIO_IFUNC int fio_thread_mutex_unlock(fio_thread_mutex_t *m); + +/** Destroys the simple Mutex (cleanup). */ +FIO_IFUNC void fio_thread_mutex_destroy(fio_thread_mutex_t *m); + +/* ***************************************************************************** +API for conditional variables +***************************************************************************** */ + +/** Initializes a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_init(fio_thread_cond_t *c); + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_wait(fio_thread_cond_t *c, fio_thread_mutex_t *m); + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_timedwait(fio_thread_cond_t *c, + fio_thread_mutex_t *m, + size_t milliseconds); + +/** Signals a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_signal(fio_thread_cond_t *c); + +/** Destroys a simple conditional variable. */ +FIO_IFUNC void fio_thread_cond_destroy(fio_thread_cond_t *c); + +/* ***************************************************************************** + + +POSIX Implementation - inlined static functions + + +***************************************************************************** */ +#if FIO_OS_POSIX + +#ifndef FIO_THREADS_FORK_BYO +/** Should behave the same as the POSIX system call `getpid`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_getpid(void) { + return (fio_thread_pid_t)getpid(); +} +/** Should behave the same as the POSIX system call `fork`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_fork(void) { + return (fio_thread_pid_t)fork(); +} + +/** Should behave the same as the POSIX system call `kill`. */ +FIO_IFUNC int fio_thread_kill(fio_thread_pid_t i, int s) { + return kill((pid_t)i, s); +} + +/** Should behave the same as the POSIX system call `waitpid`. */ +FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t i, int *s, int o) { + return waitpid((pid_t)i, s, o); +} +#endif /* FIO_THREADS_FORK_BYO */ + +#ifndef FIO_THREADS_BYO +// clang-format off + +/** Starts a new thread, returns 0 on success and -1 on failure. */ +FIO_IFUNC int fio_thread_create(fio_thread_t *t, void *(*fn)(void *), void *arg) { return pthread_create(t, NULL, fn, arg); } + +FIO_IFUNC int fio_thread_join(fio_thread_t *t) { return pthread_join(*t, NULL); } + +/** Detaches the thread, so thread resources are freed automatically. */ +FIO_IFUNC int fio_thread_detach(fio_thread_t *t) { return pthread_detach(*t); } + +/** Ends the current running thread. */ +FIO_IFUNC void fio_thread_exit(void) { pthread_exit(NULL); } + +/* Returns non-zero if both threads refer to the same thread. */ +FIO_IFUNC int fio_thread_equal(fio_thread_t *a, fio_thread_t *b) { return pthread_equal(*a, *b); } + +/** Returns the current thread. */ +FIO_IFUNC fio_thread_t fio_thread_current(void) { return pthread_self(); } + +/** Yields thread execution. */ +FIO_IFUNC void fio_thread_yield(void) { sched_yield(); } +#endif /* FIO_THREADS_BYO */ + + +#if defined(__APPLE__) && __has_include("sys/qos.h") /* MacOS with QoS */ +#include "sys/qos.h" + +/** Returns a thread's priority level. */ +FIO_SFUNC fio_thread_priority_e fio_thread_priority(void) { + qos_class_t qos; + int rel; + if(pthread_get_qos_class_np(pthread_self(), &qos, &rel)) return FIO_THREAD_PRIORITY_ERROR; + switch(qos) { + case QOS_CLASS_BACKGROUND: return FIO_THREAD_PRIORITY_LOWEST; + case QOS_CLASS_UTILITY: return FIO_THREAD_PRIORITY_LOW; + case QOS_CLASS_DEFAULT: return FIO_THREAD_PRIORITY_NORMAL; + case QOS_CLASS_UNSPECIFIED: return FIO_THREAD_PRIORITY_NORMAL; + case QOS_CLASS_USER_INITIATED: return FIO_THREAD_PRIORITY_HIGH; + case QOS_CLASS_USER_INTERACTIVE: return FIO_THREAD_PRIORITY_HIGHEST; + } + return FIO_THREAD_PRIORITY_ERROR; +} + +/** Sets a thread's priority level. */ +FIO_SFUNC int fio_thread_priority_set(fio_thread_priority_e pr) { + // pthread_get_qos_class_np(pthread_t _Nonnull __pthread, qos_class_t * _Nullable __qos_class, int * _Nullable __relative_priority) + qos_class_t qos = QOS_CLASS_DEFAULT; + int rel = 0; + switch(pr) { + case FIO_THREAD_PRIORITY_LOWEST: qos = QOS_CLASS_BACKGROUND; rel = -4; break; + case FIO_THREAD_PRIORITY_LOW: qos = QOS_CLASS_UTILITY; rel = -2; break; + case FIO_THREAD_PRIORITY_NORMAL: qos = QOS_CLASS_DEFAULT; rel = 0; break; + case FIO_THREAD_PRIORITY_HIGH: qos = QOS_CLASS_USER_INITIATED; rel = 2; break; + case FIO_THREAD_PRIORITY_HIGHEST: qos = QOS_CLASS_USER_INTERACTIVE; rel = 4; break; + case FIO_THREAD_PRIORITY_ERROR: qos = QOS_CLASS_DEFAULT; rel = 0; break; + } + return pthread_set_qos_class_self_np(qos, rel); +} + +#else /* portable POSIX */ + +/** Returns a thread's priority level. */ +FIO_SFUNC fio_thread_priority_e fio_thread_priority(void) { + int policy; + struct sched_param schd; + if(pthread_getschedparam(pthread_self(), &policy, &schd)) + return FIO_THREAD_PRIORITY_ERROR; + int min = sched_get_priority_min(policy); + int max = sched_get_priority_max(policy); + size_t steps = (size_t)(max - min) / 5; + size_t priority_value = (schd.sched_priority - min); + if(steps) priority_value /= steps; + switch(priority_value) { + case 0: return FIO_THREAD_PRIORITY_LOWEST; + case 1: return FIO_THREAD_PRIORITY_LOW; + case 2: return FIO_THREAD_PRIORITY_NORMAL; + case 3: return FIO_THREAD_PRIORITY_HIGH; + case 4: return FIO_THREAD_PRIORITY_HIGHEST; + } + return FIO_THREAD_PRIORITY_ERROR; +} + +/** Sets a thread's priority level. */ +FIO_SFUNC int fio_thread_priority_set(fio_thread_priority_e priority_value) { + int policy; + struct sched_param schd; + if(pthread_getschedparam(pthread_self(), &policy, &schd)) + return -1; + int min = sched_get_priority_min(policy); + int max = sched_get_priority_max(policy); + size_t steps = (size_t)(max - min) / 5; + switch(priority_value) { + case FIO_THREAD_PRIORITY_LOWEST: schd.sched_priority = min + steps; break; + case FIO_THREAD_PRIORITY_LOW: schd.sched_priority = min + (steps << 1); break; + case FIO_THREAD_PRIORITY_NORMAL: schd.sched_priority = max - (steps << 1); break; + case FIO_THREAD_PRIORITY_HIGH: schd.sched_priority = max - steps; break; + case FIO_THREAD_PRIORITY_HIGHEST: schd.sched_priority = max; break; + case FIO_THREAD_PRIORITY_ERROR: return -1; + } + return pthread_setschedparam(pthread_self(), policy, &schd); +} + +#endif /* MacOS vs. portable POSIX */ + +#ifndef FIO_THREADS_MUTEX_BYO + +/** Initializes a simple Mutex. */ +FIO_IFUNC int fio_thread_mutex_init(fio_thread_mutex_t *m) { return pthread_mutex_init(m, NULL); } + +/** Locks a simple Mutex, returning -1 on error. */ +FIO_IFUNC int fio_thread_mutex_lock(fio_thread_mutex_t *m) { return pthread_mutex_lock(m); } + +/** Attempts to lock a simple Mutex, returning zero on success. */ +FIO_IFUNC int fio_thread_mutex_trylock(fio_thread_mutex_t *m) { return pthread_mutex_trylock(m); } + +/** Unlocks a simple Mutex, returning zero on success or -1 on error. */ +FIO_IFUNC int fio_thread_mutex_unlock(fio_thread_mutex_t *m) { return pthread_mutex_unlock(m); } + +/** Destroys the simple Mutex (cleanup). */ +FIO_IFUNC void fio_thread_mutex_destroy(fio_thread_mutex_t *m) { pthread_mutex_destroy(m); *m = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; } + +#endif /* FIO_THREADS_MUTEX_BYO */ +// clang-format on + +#ifndef FIO_THREADS_COND_BYO +/** Initializes a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_init(fio_thread_cond_t *c) { + return pthread_cond_init(c, NULL); +} + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_wait(fio_thread_cond_t *c, + fio_thread_mutex_t *m) { + return pthread_cond_wait(c, m); +} + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_timedwait(fio_thread_cond_t *c, + fio_thread_mutex_t *m, + size_t milliseconds) { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + milliseconds += t.tv_nsec / 1000000; + t.tv_sec += (long)(milliseconds / 1000); + t.tv_nsec = (long)((milliseconds % 1000) * 1000000); + return pthread_cond_timedwait(c, m, &t); +} + +/** Signals a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_signal(fio_thread_cond_t *c) { + return pthread_cond_signal(c); +} + +/** Destroys a simple conditional variable. */ +FIO_IFUNC void fio_thread_cond_destroy(fio_thread_cond_t *c) { + pthread_cond_destroy(c); +} +#endif /* FIO_THREADS_COND_BYO */ + +/* ***************************************************************************** + + +Windows Implementation - inlined static functions + + +***************************************************************************** */ +#elif FIO_OS_WIN +#include +#include +#include + +#ifndef FIO_THREADS_FORK_BYO + +FIO_IFUNC fio_thread_pid_t fio_thread_getpid(void) { + return (fio_thread_pid_t)GetCurrentProcessId(); +} + +#if defined(fork) && defined(WEXITSTATUS) /* unix features pre-patched */ +FIO_IFUNC fio_thread_pid_t fio_thread_fork(void) { + return (fio_thread_pid_t)fork(); +} +FIO_IFUNC int fio_thread_kill(fio_thread_pid_t i, int s) { + return kill((pid_t)i, s); +} +FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t i, int *s, int o) { + return waitpid((pid_t)i, s, o); +} + +#else /* defined(fork) && defined(WEXITSTATUS) */ + +FIO_IFUNC fio_thread_pid_t fio_thread_fork(void) { + FIO_LOG_ERROR("`fork` not implemented, cannot spawn child processes."); + return (fio_thread_pid_t)-1; +} + +FIO_IFUNC int fio_thread_kill(fio_thread_pid_t pid, int sig) { + /* Credit to Jan Biedermann (GitHub: @janbiedermann) */ + HANDLE handle; + DWORD status; + if (sig < 0 || sig >= NSIG) { + errno = EINVAL; + return -1; + } +#ifdef SIGCONT + if (sig == SIGCONT) { + errno = ENOSYS; + return -1; + } +#endif + + if (pid == -1) + pid = 0; + + if (!pid) + handle = GetCurrentProcess(); + else + handle = + OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!handle) + goto something_went_wrong; + + switch (sig) { +#ifdef SIGKILL + case SIGKILL: +#endif + case SIGTERM: + case SIGINT: /* terminate */ + if (!TerminateProcess(handle, 1)) + goto something_went_wrong; + break; + case 0: /* check status */ + if (!GetExitCodeProcess(handle, &status)) + goto something_went_wrong; + if (status != STILL_ACTIVE) { + errno = ESRCH; + goto cleanup_after_error; + } + break; + default: /* not supported? */ errno = ENOSYS; goto cleanup_after_error; + } + + if (pid) { + CloseHandle(handle); + } + return 0; + +something_went_wrong: + + switch (GetLastError()) { + case ERROR_INVALID_PARAMETER: errno = ESRCH; break; + case ERROR_ACCESS_DENIED: + errno = EPERM; + if (handle && GetExitCodeProcess(handle, &status) && status != STILL_ACTIVE) + errno = ESRCH; + break; + default: errno = GetLastError(); + } +cleanup_after_error: + if (handle && pid) + CloseHandle(handle); + return -1; +} + +#ifndef WNOHANG +#define WNOHANG 1 +#endif /* WNOHANG */ + +#ifndef WUNTRACED +#define WUNTRACED 2 +#endif /* WUNTRACED */ + +#ifndef WCONTINUED +#define WCONTINUED 8 +#endif /* WCONTINUED */ + +#ifndef WNOWAIT +#define WNOWAIT 0x01000000 +#endif /* WNOWAIT */ + +#ifndef WEXITSTATUS +#define WEXITSTATUS(status) (((status)&0xFF00) >> 8) +#endif /* WEXITSTATUS */ + +#ifndef WIFEXITED +#define WIFEXITED(status) (WTERMSIG(status) == 0) +#endif /* WIFEXITED */ + +#ifndef WIFSIGNALED +#define WIFSIGNALED(status) (((signed char)(__WTERMSIG(status) + 1) >> 1) > 0) +#endif /* WIFSIGNALED */ + +#ifndef WTERMSIG +#define WTERMSIG(status) ((status)&0x7F) +#endif /* WTERMSIG */ + +#ifndef WIFSTOPPED +#define WIFSTOPPED(status) (((status)&0xFF) == 0x7F) +#endif /* WIFSTOPPED */ + +#ifndef WSTOPSIG +#define WSTOPSIG(status) WEXITSTATUS(status) +#endif /* WSTOPSIG */ + +static int fio___thread_waitpid_anychild(PROCESSENTRY32W *pe, DWORD pid) { + return pe->th32ParentProcessID == GetCurrentProcessId(); +} + +static int fio___thread_waitpid_pid(PROCESSENTRY32W *pe, DWORD pid) { + return pe->th32ProcessID == pid; +} + +FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t pid, int *status, int opt) { + /* adopted from: + * https://github.com/win32ports/sys_wait_h/blob/master/sys/wait.h Copyright + * Copyright (c) 2019 win32ports, MIT license + */ + int saved_status = 0; + HANDLE hProcess = INVALID_HANDLE_VALUE, hSnapshot = INVALID_HANDLE_VALUE; + int (*are_these_the_druides_were_looking_for)(PROCESSENTRY32W *, DWORD); + PROCESSENTRY32W pe; + DWORD wait_status = 0, exit_code = 0; + int nohang = WNOHANG == (WNOHANG & opt); + opt &= ~(WUNTRACED | WNOWAIT | WCONTINUED | WNOHANG); + if (opt) { + errno = -EINVAL; + return -1; + } + + if (pid > 0 || pid == -1) { + FIO_LOG_ERROR( + "fio_thread_waitpid not implemented for pid < -1 || pid ==0."); + return -1; + } + + are_these_the_druides_were_looking_for = fio___thread_waitpid_pid; + if (pid == -1) /* wait for any child */ + are_these_the_druides_were_looking_for = fio___thread_waitpid_anychild; + + hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == hSnapshot) { + errno = ECHILD; + return -1; + } + + pe.dwSize = sizeof(pe); + if (!Process32FirstW(hSnapshot, &pe)) { + CloseHandle(hSnapshot); + errno = ECHILD; + return -1; + } + do { + if (are_these_the_druides_were_looking_for(&pe, pid)) { + hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + 0, + pe.th32ProcessID); + if (INVALID_HANDLE_VALUE == hProcess) { + CloseHandle(hSnapshot); + errno = ECHILD; + return -1; + } + break; + } + } while (Process32NextW(hSnapshot, &pe)); + if (INVALID_HANDLE_VALUE == hProcess) { + CloseHandle(hSnapshot); + errno = ECHILD; + return -1; + } + + wait_status = WaitForSingleObject(hProcess, nohang ? 0 : INFINITE); + + if (WAIT_OBJECT_0 == wait_status) { + if (GetExitCodeProcess(hProcess, &exit_code)) + saved_status |= (exit_code & 0xFF) << 8; + } else if (WAIT_TIMEOUT == wait_status && nohang) { + return 0; + } else { + CloseHandle(hProcess); + CloseHandle(hSnapshot); + errno = ECHILD; + return -1; + } + + CloseHandle(hProcess); + CloseHandle(hSnapshot); + + if (status) + *status = saved_status; + + return pe.th32ParentProcessID; +} + +#endif /* already patched by some other implementation */ +#endif /* FIO_THREADS_FORK_BYO */ + +#ifndef FIO_THREADS_BYO +/** Starts a new thread, returns 0 on success and -1 on failure. */ +FIO_IFUNC int fio_thread_create(fio_thread_t *t, + void *(*fn)(void *), + void *arg) { + *t = (HANDLE)_beginthreadex(NULL, + 0, + (_beginthreadex_proc_type)(uintptr_t)fn, + arg, + 0, + NULL); + return (!!t) - 1; +} + +FIO_IFUNC int fio_thread_join(fio_thread_t *t) { + int r = 0; + if (WaitForSingleObject(*t, INFINITE) == WAIT_FAILED) { + errno = GetLastError(); + r = -1; + } else + CloseHandle(*t); + return r; +} + +// clang-format off +/** Detaches the thread, so thread resources are freed automatically. */ +FIO_IFUNC int fio_thread_detach(fio_thread_t *t) { return CloseHandle(*t) - 1; } + +/** Ends the current running thread. */ +FIO_IFUNC void fio_thread_exit(void) { _endthread(); } + +/* Returns non-zero if both threads refer to the same thread. */ +FIO_IFUNC int fio_thread_equal(fio_thread_t *a, fio_thread_t *b) { return GetThreadId(*a) == GetThreadId(*b); } + +/** Returns the current thread. */ +FIO_IFUNC fio_thread_t fio_thread_current(void) { return GetCurrentThread(); } + +/** Yields thread execution. */ +FIO_IFUNC void fio_thread_yield(void) { Sleep(0); } + +#endif /* FIO_THREADS_BYO */ + +/** Returns a thread's priority level. */ +FIO_SFUNC fio_thread_priority_e fio_thread_priority(void) { + switch(GetThreadPriority(GetCurrentThread())) { + case THREAD_PRIORITY_LOWEST: return FIO_THREAD_PRIORITY_LOWEST; + case THREAD_PRIORITY_BELOW_NORMAL: return FIO_THREAD_PRIORITY_LOW; + case THREAD_PRIORITY_NORMAL: return FIO_THREAD_PRIORITY_NORMAL; + case THREAD_PRIORITY_ABOVE_NORMAL: return FIO_THREAD_PRIORITY_HIGH; + case THREAD_PRIORITY_HIGHEST: return FIO_THREAD_PRIORITY_HIGHEST; + default: return FIO_THREAD_PRIORITY_ERROR; + } +} + +/** Sets a thread's priority level. */ +FIO_SFUNC int fio_thread_priority_set(fio_thread_priority_e pr) { + int priority; + switch(pr){ + case FIO_THREAD_PRIORITY_ERROR: return -1; + case FIO_THREAD_PRIORITY_LOWEST: priority = THREAD_PRIORITY_LOWEST; break; + case FIO_THREAD_PRIORITY_LOW: priority = THREAD_PRIORITY_BELOW_NORMAL; break; + case FIO_THREAD_PRIORITY_NORMAL: priority = THREAD_PRIORITY_NORMAL; break; + case FIO_THREAD_PRIORITY_HIGH: priority = THREAD_PRIORITY_ABOVE_NORMAL; break; + case FIO_THREAD_PRIORITY_HIGHEST: priority = THREAD_PRIORITY_HIGHEST; break; + } + return 0 - !SetThreadPriority(GetCurrentThread(), priority); +} + +#ifndef FIO_THREADS_MUTEX_BYO + +SFUNC int fio___thread_mutex_lazy_init(fio_thread_mutex_t *m); + +FIO_IFUNC int fio_thread_mutex_init(fio_thread_mutex_t *m) { InitializeCriticalSection(m); return 0; } + +/** Destroys the simple Mutex (cleanup). */ +FIO_IFUNC void fio_thread_mutex_destroy(fio_thread_mutex_t *m) { DeleteCriticalSection(m); memset(m,0,sizeof(*m)); } +// clang-format on +/** Unlocks a simple Mutex, returning zero on success or -1 on error. */ +FIO_IFUNC int fio_thread_mutex_unlock(fio_thread_mutex_t *m) { + if (!m) + return -1; + LeaveCriticalSection(m); + return 0; +} + +/** Locks a simple Mutex, returning -1 on error. */ +FIO_IFUNC int fio_thread_mutex_lock(fio_thread_mutex_t *m) { + const fio_thread_mutex_t zero = {0}; + if (!FIO_MEMCMP(m, &zero, sizeof(zero)) && fio___thread_mutex_lazy_init(m)) + return -1; + EnterCriticalSection(m); + return 0; +} + +/** Attempts to lock a simple Mutex, returning zero on success. */ +FIO_IFUNC int fio_thread_mutex_trylock(fio_thread_mutex_t *m) { + const fio_thread_mutex_t zero = {0}; + if (!FIO_MEMCMP(m, &zero, sizeof(zero)) && fio___thread_mutex_lazy_init(m)) + return -1; + return TryEnterCriticalSection(m) - 1; +} +#endif /* FIO_THREADS_MUTEX_BYO */ + +#ifndef FIO_THREADS_COND_BYO +/** Initializes a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_init(fio_thread_cond_t *c) { + InitializeConditionVariable(c); + return 0; +} + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_wait(fio_thread_cond_t *c, + fio_thread_mutex_t *m) { + return 0 - !SleepConditionVariableCS(c, m, INFINITE); +} + +/** Waits on a conditional variable (MUST be previously locked). */ +FIO_IFUNC int fio_thread_cond_timedwait(fio_thread_cond_t *c, + fio_thread_mutex_t *m, + size_t milliseconds) { + return 0 - !SleepConditionVariableCS(c, m, milliseconds); +} + +/** Signals a simple conditional variable. */ +FIO_IFUNC int fio_thread_cond_signal(fio_thread_cond_t *c) { + WakeConditionVariable(c); + return 0; +} + +/** Destroys a simple conditional variable. */ +FIO_IFUNC void fio_thread_cond_destroy(fio_thread_cond_t *c) { (void)(c); } +#endif /* FIO_THREADS_COND_BYO */ + +#endif /* FIO_OS_WIN */ + +/* ***************************************************************************** + + +Multi-Threaded `memcpy` (naive and slow) + + +***************************************************************************** */ + +#ifndef FIO_MEMCPY_THREADS +#define FIO_MEMCPY_THREADS 8 +#endif +#undef FIO_MEMCPY_THREADS___MINCPY +#define FIO_MEMCPY_THREADS___MINCPY (1ULL << 23) +typedef struct { + const char *restrict dest; + void *restrict src; + size_t bytes; +} fio___thread_memcpy_s; + +FIO_SFUNC void *fio___thread_memcpy_task(void *v_) { + fio___thread_memcpy_s *v = (fio___thread_memcpy_s *)v_; + FIO_MEMCPY((void *)(v->dest), (void *)(v->src), v->bytes); + return NULL; +} + +/** Multi-threaded memcpy using up to FIO_MEMCPY_THREADS threads */ +FIO_SFUNC size_t fio_thread_memcpy(const void *restrict dest, + void *restrict src, + size_t bytes) { + size_t i = 0, r; + const char *restrict d = (const char *restrict)dest; + char *restrict s = (char *restrict)src; + fio_thread_t threads[FIO_MEMCPY_THREADS - 1]; + fio___thread_memcpy_s info[FIO_MEMCPY_THREADS - 1]; + size_t bytes_per_thread = bytes / FIO_MEMCPY_THREADS; + if (bytes < FIO_MEMCPY_THREADS___MINCPY) + goto finished_creating_thread; + + for (; i < (FIO_MEMCPY_THREADS - 1); ++i) { + info[i] = (fio___thread_memcpy_s){d, s, bytes_per_thread}; + if (fio_thread_create(threads + i, fio___thread_memcpy_task, info + i)) + goto finished_creating_thread; + d += bytes_per_thread; + s += bytes_per_thread; + bytes -= bytes_per_thread; + } +finished_creating_thread: + r = i + 1; + FIO_MEMCPY((void *)d, (void *)s, bytes); /* memcpy reminder */ + while (i) { + --i; + fio_thread_join(threads + i); + } + return r; +} + +/* ***************************************************************************** +Module Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +#if FIO_OS_WIN +#ifndef FIO_THREADS_MUTEX_BYO +/** Initializes a simple Mutex */ +SFUNC int fio___thread_mutex_lazy_init(fio_thread_mutex_t *m) { + int r = 0; + static fio_lock_i lock = FIO_LOCK_INIT; + /* lazy initialization */ + fio_thread_mutex_t zero = {0}; + fio_lock(&lock); + if (!FIO_MEMCMP(m, + &zero, + sizeof(zero))) { /* retest, as this may have changed... */ + r = fio_thread_mutex_init(m); + } + fio_unlock(&lock); + return r; +} +#endif /* FIO_THREADS_MUTEX_BYO */ +#endif /* FIO_OS_WIN */ +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_THREADS */ +#undef FIO_THREADS +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_URL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + URI Parsing + + + +Copyright: Boaz Segev, 2019-2021; License: ISC / MIT (choose your license) +***************************************************************************** */ +#if (defined(FIO_URL) || defined(FIO_URI)) && !defined(H___FIO_URL___H) +#define H___FIO_URL___H +/** the result returned by `fio_url_parse` */ +typedef struct { + fio_buf_info_s scheme; + fio_buf_info_s user; + fio_buf_info_s password; + fio_buf_info_s host; + fio_buf_info_s port; + fio_buf_info_s path; + fio_buf_info_s query; + fio_buf_info_s target; +} fio_url_s; + +/** + * Parses the URI returning it's components and their lengths (no decoding + * performed, doesn't accept decoded URIs). + * + * The returned string are NOT NUL terminated, they are merely locations within + * the original string. + * + * This function attempts to accept many different formats, including any of the + * following: + * + * * `/complete_path?query#target` + * + * i.e.: /index.html?page=1#list + * + * * `host:port/complete_path?query#target` + * + * i.e.: + * example.com + * example.com:8080 + * example.com/index.html + * example.com:8080/index.html + * example.com:8080/index.html?key=val#target + * + * * `user:password@host:port/path?query#target` + * + * i.e.: user:1234@example.com:8080/index.html + * + * * `username[:password]@host[:port][...]` + * + * i.e.: john:1234@example.com + * + * * `schema://user:password@host:port/path?query#target` + * + * i.e.: http://example.com/index.html?page=1#list + * + * Invalid formats might produce unexpected results. No error testing performed. + * + * NOTE: the `unix`, `file` and `priv` schemas are reserved for file paths. + */ +SFUNC fio_url_s fio_url_parse(const char *url, size_t len); + +/** The type used by the `FIO_URL_QUERY_EACH` iterator macro. */ +typedef struct { + fio_buf_info_s name; + fio_buf_info_s value; + fio_buf_info_s private___; +} fio_url_query_each_s; + +/** A helper function for the `FIO_URL_QUERY_EACH` macro implementation. */ +FIO_SFUNC fio_url_query_each_s fio_url_query_each_next(fio_url_query_each_s); + +/** Iterates through each of the query elements. */ +#define FIO_URL_QUERY_EACH(query_buf, i) \ + for (fio_url_query_each_s i = fio_url_query_each_next( \ + (fio_url_query_each_s){.private___ = (query_buf)}); \ + i.name.buf; \ + i = fio_url_query_each_next(i)) + +/* Return type for `fio_url_is_tls` */ +typedef struct { + fio_buf_info_s key; + fio_buf_info_s cert; + fio_buf_info_s pass; + bool tls; +} fio_url_tls_info_s; + +/** + * Returns TLS data associated with the URL. + * + * This function supports implicit TLS by scheme data for the following possible + * values: + * + * - `wss` - Secure WebSockets. + * - `sses` - Secure SSE (Server Sent Events). + * - `https` - Secure HTTP. + * - `tcps` - Secure TCP/IP. + * - `tls` - Secure TCP/IP. + * - `udps` - Secure UDP. + * + * i.e.: + * tls://example.com/ + * tcps://example.com/ + * udps://example.com/ + * + * wss://example.com/ + * https://example.com/ + * sses://example.com/ + * + * This function also supports explicit TLS by query data for the following + * possible key-pair values: + * + * - `tls` - self-signed TLS (unless key / cert are provided). + * - `tls=true` - self-signed TLS (unless key / cert are provided). + * - `tls=` - key and certificate files (same path, different + * file extensions). + * - `key=` - path or env variable name for the private key. + * - `cert=` - path or env variable name for the public + * certificate. + * + * - `pass` - password for decrypting key / cert data. + * + *i.e.: + * + * tcp://example.com/?tls (anonymous TLS) + * udp://example.com/?tls=true + * + * https://example.com/?tls=key_cert_folder_or_prefix&pass=key_password + * + * https://example.com/?key=key_file_or_env_var&cert=cert_file_or_env_var&pass=key_password + * wss://example.com/?key=key_file_or_env_var&cert=cert_file_or_env_var&pass=key_password + * tcp://example.com/?key=key_file_or_env_var&cert=cert_file_or_env_var&pass=key_password + */ +SFUNC fio_url_tls_info_s fio_url_is_tls(fio_url_s u); +/* ***************************************************************************** +FIO_URL - Implementation (static) +***************************************************************************** */ + +/** A helper function for the `FIO_URL_QUERY_EACH` macro implementation. */ +FIO_SFUNC fio_url_query_each_s fio_url_query_each_next(fio_url_query_each_s i) { + i.name = i.private___; + if (!i.name.buf) + return i; + char *amp = (char *)FIO_MEMCHR(i.name.buf, '&', i.name.len); + if (amp) { + i.name.len = amp - i.name.buf; + i.private___.len -= i.name.len + 1; + i.private___.buf += i.name.len + 1; + } else { + i.private___ = FIO_BUF_INFO0; + } + char *equ = (char *)FIO_MEMCHR(i.name.buf, '=', i.name.len); + if (equ) { + i.value.buf = equ + 1; + i.value.len = (i.name.buf + i.name.len) - i.value.buf; + i.name.len = equ - i.name.buf; + } else { + i.value = FIO_BUF_INFO0; + } + return i; +} + +/* ***************************************************************************** +FIO_URL - Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/** + * Parses the URI returning it's components and their lengths (no decoding + * performed, doesn't accept decoded URIs). + * + * The returned string are NOT NUL terminated, they are merely locations within + * the original string. + * + * This function expects any of the following formats: + * + * * `/complete_path?query#target` + * + * i.e.: /index.html?page=1#list + * + * * `host:port/complete_path?query#target` + * + * i.e.: + * example.com/index.html + * example.com:8080/index.html + * + * * `schema://user:password@host:port/path?query#target` + * + * i.e.: http://example.com/index.html?page=1#list + * + * Invalid formats might produce unexpected results. No error testing performed. + */ +SFUNC fio_url_s fio_url_parse(const char *url, size_t len) { + /* + Intention: + [schema://][[user][:password]@][host.com][:port][path][?quary][#target] + */ + const char *end = url + len; + const char *pos = url; + fio_url_s r = {.scheme = {.buf = (char *)url}}; + if (len == 0) { + goto finish; + } + + if (*pos == '/') /* start at path */ + goto start_path; + + while (pos < end && *pos && *pos != ':' && *pos != '/' && *pos != '@' && + *pos != '#' && *pos != '?') + ++pos; + + if (pos == end) { + /* was only host (path starts with '/') */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + goto finish; + } + + switch (*pos) { + case '@': + /* username@[host] */ + r.user = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_host; + case '/': + /* host[/path] */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + goto start_path; + case '?': + /* host?[query] */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_query; + case '#': + /* host#[target] */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_target; + case ':': + if (pos + 2 <= end && pos[1] == '/' && pos[2] == '/') { + /* scheme:// */ + r.scheme.len = pos - url; + pos += 3; + } else { + /* username:[password] OR */ + /* host:[port] */ + r.user = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_password; + } + break; + } + + /* start_username: */ + url = pos; + while (pos < end && *pos && *pos != ':' && *pos != '/' && *pos != '@' && + *pos != '#' && *pos != '?') + ++pos; + + if (pos >= end) { /* scheme://host */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + goto finish; + } + + switch (*pos) { + case '/': + /* scheme://host[/path] */ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + goto start_path; + case '@': + /* scheme://username@[host]... */ + r.user = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_host; + case '?': + /* scheme://host[?query] (bad)*/ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_query; + case '#': + /* scheme://host[#target] (bad)*/ + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + goto start_query; + case ':': + /* scheme://username:[password]@[host]... OR */ + /* scheme://host:[port][/...] */ + r.user = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + break; + } + +start_password: + url = pos; + while (pos < end && *pos && *pos != '/' && *pos != '@' && *pos != '?') + ++pos; + + if (pos >= end) { + /* was host:port */ + r.port = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + r.host = r.user; + r.user.len = 0; + goto finish; + } + + switch (*pos) { + case '?': /* fall through */ + case '/': + r.port = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + r.host = r.user; + r.user.len = 0; + goto start_path; + case '@': + r.password = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + break; + } + +start_host: + url = pos; + while (pos < end && *pos && *pos != '/' && *pos != ':' && *pos != '#' && + *pos != '?') + ++pos; + + r.host = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + if (pos >= end) { + goto finish; + } + switch (*pos) { + case '/': + /* scheme://[...@]host[/path] */ + goto start_path; + case '?': + /* scheme://[...@]host?[query] (bad)*/ + ++pos; + goto start_query; + case '#': + /* scheme://[...@]host#[target] (bad)*/ + ++pos; + goto start_target; + // case ':': + /* scheme://[...@]host:[port] */ + } + ++pos; + + // start_port: + url = pos; + while (pos < end && *pos && *pos != '/' && *pos != '#' && *pos != '?') + ++pos; + + r.port = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + + if (pos >= end) { + /* scheme://[...@]host:port */ + goto finish; + } + switch (*pos) { + case '?': + /* scheme://[...@]host:port?[query] (bad)*/ + ++pos; + goto start_query; + case '#': + /* scheme://[...@]host:port#[target] (bad)*/ + ++pos; + goto start_target; + // case '/': + /* scheme://[...@]host:port[/path] */ + } + +start_path: + url = pos; + while (pos < end && *pos && *pos != '#' && *pos != '?') + ++pos; + + r.path = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + + if (pos >= end) { + goto finish; + } + ++pos; + if (pos[-1] == '#') + goto start_target; + +start_query: + url = pos; + while (pos < end && *pos && *pos != '#') + ++pos; + + r.query = FIO_BUF_INFO2((char *)url, (size_t)(pos - url)); + ++pos; + + if (pos >= end) + goto finish; + +start_target: + r.target = FIO_BUF_INFO2((char *)pos, (size_t)(end - pos)); + +finish: + + if (r.scheme.len == 4 && r.host.buf) { /* recognize file paths */ + uint32_t s, file_str, unix_str, priv_str; + fio_memcpy4(&file_str, "file"); + fio_memcpy4(&unix_str, "unix"); + fio_memcpy4(&priv_str, "priv"); + fio_memcpy4(&s, r.scheme.buf); + s |= 0x20202020U; /* downcase */ + if (s == file_str || s == unix_str || s == priv_str) { + r.path.buf = r.scheme.buf + 7; + r.path.len = end - (r.scheme.buf + 7); + if (r.query.len) + r.path.len = r.query.buf - (r.path.buf + 1); + else if (r.target.len) + r.path.len = r.target.buf - (r.path.buf + 1); + r.user.len = r.password.len = r.port.len = r.host.len = 0; + } + } else if (!r.scheme.len && r.host.buf && r.host.buf[0] == '.') { + r.path.len = end - r.host.buf; + r.path.buf = r.host.buf; + r.query.len = r.target.len = r.host.len = 0; + } + + /* set any empty values to NULL */ + if (!r.scheme.len) + r.scheme.buf = NULL; + if (!r.user.len) + r.user.buf = NULL; + if (!r.password.len) + r.password.buf = NULL; + if (!r.host.len) + r.host.buf = NULL; + if (!r.port.len) + r.port.buf = NULL; + if (!r.path.len) + r.path.buf = NULL; + if (!r.query.len) + r.query.buf = NULL; + if (!r.target.len) + r.target.buf = NULL; + + return r; +} + +/* Returns TLS data associated with the URL. */ +SFUNC fio_url_tls_info_s fio_url_is_tls(fio_url_s u) { + fio_url_tls_info_s r = {0}; + switch (u.scheme.len) { + case 3: /* wss || tls || ssl */ + r.tls = + ((fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("wss:") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("tls:") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("ssl:")); + + break; + case 4: /* ssse || sses || tcps || stcp || udps || sudp */ + r.tls = + ((fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("sses") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("ssse") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("tcps") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("stcp") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("udps") || + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("sudp")); + break; + case 5: /* https */ + r.tls = ((u.scheme.buf[4] | 32) == 's' && + (fio_buf2u32u(u.scheme.buf) | 0x20202020) == fio_buf2u32u("http")); + break; + } + if (u.query.len) { /* key=, cert=, pass=, password=*/ + fio_buf_info_s key = {0}; + fio_buf_info_s cert = {0}; + fio_buf_info_s pass = {0}; + uint32_t name; + const uint32_t wrd_key = fio_buf2u32u("key="); /* keyword's value */ + const uint32_t wrd_tls = fio_buf2u32u("tls="); + const uint32_t wrd_ssl = fio_buf2u32u("ssl="); + const uint32_t wrd_cert = fio_buf2u32u("cert"); + const uint32_t wrd_true = fio_buf2u32u("true"); + const uint32_t wrd_pass = fio_buf2u32u("pass"); + const uint64_t wrd_password = fio_buf2u64u("password"); + FIO_URL_QUERY_EACH(u.query, i) { /* iterates each name=value pair */ + switch (i.name.len) { + case 8: + if ((fio_buf2u64u(i.name.buf) | 0x2020202020202020ULL) == wrd_password) + pass = i.value; + break; + case 3: + name = fio_buf2u32u(i.name.buf) | 0x20202020UL; /* '=' stays the same */ + name &= fio_buf2u32u("\xFF\xFF\xFF\x00"); + name |= fio_buf2u32u("\x00\x00\x00="); /* in case there was no = sign */ + if (name == wrd_key) { + if (i.value.len) + key = i.value; + } else if (name == wrd_tls || name == wrd_ssl) { + r.tls = 1; + if (i.value.len && !(i.value.len == 1 && i.value.buf[0] == '1') && + !(i.value.len == 4 && + (fio_buf2u32u(i.value.buf) | 0x20202020UL) == wrd_true)) + cert = key = i.value; + } + break; + case 4: + name = fio_buf2u32u(i.name.buf) | 0x20202020UL; + if (name == wrd_cert) + cert = i.value; + else if (name == wrd_pass) + pass = i.value; + break; + } + } + if (key.len && cert.len) { + r.key = key; + r.cert = cert; + if (pass.len) + r.pass = pass; + r.tls = 1; + } + } + FIO_LOG_DDEBUG2( + "URL TLS detection:\n\t%s\n\tkey: %.*s\n\tcert: %.*s\n\tpass: %.*s", + (r.tls ? "Secure" : "plaintext"), + (int)r.key.len, + r.key.buf, + (int)r.cert.len, + r.cert.buf, + (int)r.pass.len, + r.pass.buf); + return r; +} +/* ***************************************************************************** +FIO_URL - Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_URL || FIO_URI */ +#undef FIO_URL +#undef FIO_URI +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_FILES /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Common File Operations (POSIX style) + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_FILES) && !defined(H___FIO_FILES___H) +#define H___FIO_FILES___H + +#include +#include +#include + +/* ***************************************************************************** +File Helper API +***************************************************************************** */ + +/** + * Opens `filename`, returning the same as values as `open` on POSIX systems. + * + * If `path` starts with a `"~/"` than it will be relative to the user's home + * folder (on Windows, testing for `"~\"`). + */ +SFUNC int fio_filename_open(const char *filename, int flags); + +/** Returns 1 if `path` does folds backwards (OS separator dependent). */ +SFUNC int fio_filename_is_unsafe(const char *path); + +/** Returns 1 if `path` does folds backwards (has "/../" or "//"). */ +SFUNC int fio_filename_is_unsafe_url(const char *path); + +/** Creates a temporary file, returning its file descriptor. */ +SFUNC int fio_filename_tmp(void); + +/** + * Overwrites `filename` with the data in the buffer. + * + * If `path` starts with a `"~/"` than it will be relative to the user's home + * folder (on Windows, testing for `"~\"`). + * + * Returns -1 on error or 0 on success. On error, the state of the file is + * undefined (may be doesn't exit / nothing written / partially written). + */ +FIO_IFUNC int fio_filename_overwrite(const char *filename, + const void *buf, + size_t len); + +/** Returns the file size (or 0 on both error / empty file). */ +FIO_IFUNC size_t fio_filename_size(const char *filename); + +/** Returns the file size (or 0 on both error / empty file). */ +FIO_IFUNC size_t fio_fd_size(int fd); + +/** + * Returns the file type (or 0 on both error). + * + * See: https://www.man7.org/linux/man-pages/man7/inode.7.html + */ +FIO_IFUNC size_t fio_filename_type(const char *filename); + +/** + * Returns the file type (or 0 on both error). + * + * See: https://www.man7.org/linux/man-pages/man7/inode.7.html + */ +FIO_IFUNC size_t fio_fd_type(int fd); + +/** Tests if `filename` references a folder. Returns -1 on error. */ +#define fio_filename_is_folder(filename) \ + (fio_filename_type((filename)) == S_IFDIR) + +/** + * Writes data to a file handle, returning the number of bytes written. + * + * Returns -1 on error. + * + * Since some systems have a limit on the number of bytes that can be written at + * a time, this function fragments the system calls into smaller `write` blocks, + * allowing large data to be written. + * + * If the file descriptor is non-blocking, test errno for EAGAIN / EWOULDBLOCK. + */ +FIO_IFUNC ssize_t fio_fd_write(int fd, const void *buf, size_t len); + +/** + * Reads up to `len` bytes from `fd`, returning the number of bytes read. + * + * Returns 0 if no bytes were read or on error. + * + * Since some systems have a limit on the number of bytes that can be read at + * a time, this function fragments the system calls into smaller `read` blocks, + * allowing large data to be read. + * + * If the file descriptor is non-blocking, test errno for EAGAIN / EWOULDBLOCK. + */ +FIO_IFUNC size_t fio_fd_read(int fd, void *buf, size_t len, off_t start_at); + +/** A result type for the filename parsing helper. */ +typedef struct { + fio_buf_info_s folder; + fio_buf_info_s basename; + fio_buf_info_s ext; +} fio_filename_s; + +/** Parses a file name to folder, base name and extension (zero-copy). */ +SFUNC fio_filename_s fio_filename_parse(const char *filename); + +/** Parses a file name to folder, base name and extension (zero-copy). */ +SFUNC fio_filename_s fio_filename_parse2(const char *filename, size_t len); +/** + * Returns offset for the next `token` in `fd`, or -1 if reached EOF. + * + * This will use `FIO_FD_FIND_BLOCK` bytes on the stack to read the file in a + * loop. + * + * Pros: limits memory use and (re)allocations, easier overflow protection. + * + * Cons: may be slower, as data will most likely be copied again from the file. + */ +SFUNC size_t fio_fd_find_next(int fd, char token, size_t start_at); +/** End of file value for `fio_fd_find_next` */ +#define FIO_FD_FIND_EOF ((size_t)-1) +#ifndef FIO_FD_FIND_BLOCK +/** Size on the stack used by `fio_fd_find_next` for each read cycle. */ +#define FIO_FD_FIND_BLOCK 4096 +#endif + +#if FIO_OS_WIN +#define FIO_FOLDER_SEPARATOR '\\' +/** Duplicates the file handle (int)*/ +#define fio_file_dup(fd) _dup(fd) +#else +#define FIO_FOLDER_SEPARATOR '/' +/** Duplicates the file handle (int)*/ +#define fio_file_dup(fd) dup(fd) +#endif /* FIO_OS_WIN */ + +/* ***************************************************************************** +File Helper Inline Implementation +***************************************************************************** */ + +/** + * Writes data to a file, returning the number of bytes written. + * + * Returns -1 on error. + * + * Since some systems have a limit on the number of bytes that can be written at + * a single time, this function fragments the system calls into smaller `write` + * blocks, allowing large data to be written. + * + * If the file descriptor is non-blocking, test errno for EAGAIN / EWOULDBLOCK. + */ +FIO_IFUNC ssize_t fio_fd_write(int fd, const void *buf_, size_t len) { + if (fd == -1 || !buf_ || !len) + return -1; + ssize_t total = 0; + const char *buf = (const char *)buf_; + const size_t write_limit = (1ULL << 17); + while (len > (write_limit - 1)) { + ssize_t w = write(fd, buf, write_limit); + if (w > 0) { + len -= w; + buf += w; + total += w; + continue; + } + if (w == -1 && errno == EINTR) + continue; + if (total == 0) + return -1; + return total; + } + while (len) { + ssize_t w = write(fd, buf, len); + if (w > 0) { + len -= w; + buf += w; + total += w; + continue; + } + if (w == -1 && errno == EINTR) + continue; + if (total == 0) + return -1; + return total; + } + return total; +} + +/** + * Overwrites `filename` with the data in the buffer. + * + * If `path` starts with a `"~/"` than it will be relative to the user's home + * folder (on Windows, testing for `"~\"`). + */ +FIO_IFUNC int fio_filename_overwrite(const char *filename, + const void *buf, + size_t len) { + int fd = fio_filename_open(filename, O_RDWR | O_CREAT | O_TRUNC); + if (fd == -1) + return -1; + ssize_t w = fio_fd_write(fd, buf, len); + close(fd); + if ((size_t)w != len) + return -1; + return 0; +} + +/** + * Reads up to `len` bytes from `fd`, returning the number of bytes read. + * + * Since some systems have a limit on the number of bytes that can be read at + * a time, this function fragments the system calls into smaller `read` blocks, + * allowing large data to be read. + * + * If the file descriptor is non-blocking, test errno for EAGAIN / EWOULDBLOCK. + */ +FIO_IFUNC size_t fio_fd_read(int fd, void *buf, size_t len, off_t start_at) { + size_t r = 0; + if (fd == -1 || !len || !buf) { + errno = ENOENT; + return r; + } + char *d = (char *)buf; + for (;;) { + const size_t to_read = /* use read sizes of up to 27 bits */ + (len & (((size_t)1 << 27) - 1)) | ((!!(len >> 27)) << 27); + ssize_t act; +#if (_POSIX_C_SOURCE + 1) > 200809L + if ((act = pread(fd, d + r, to_read, start_at)) > 0) { + r += act; + len -= act; + start_at += act; + if (!len) + return r; + continue; + } +#else + if ((off_t)lseek(fd, + (start_at + (start_at < 0)), + ((start_at < 0) ? SEEK_END : SEEK_SET)) == (off_t)-1) { + if (errno == EINTR) + continue; + return -1; + } + if ((act = read(fd, d + r, to_read)) > 0) { + r += act; + len -= act; + start_at += act; + if (!len) + return r; + continue; + } +#endif + if (act == -1 && errno == EINTR) + continue; + return r; + } +} + +/* ***************************************************************************** +File Stat In-lined Helpers +***************************************************************************** */ + +FIO_IFUNC size_t fio_filename_size(const char *filename) { + size_t r = 0; + struct stat stt; + if (stat(filename, &stt)) + return r; + return (r = stt.st_size); +} + +FIO_IFUNC size_t fio_fd_size(int fd) { + size_t r = 0; + struct stat stt; + if (fd == -1) + return r; + if (fstat(fd, &stt)) + return r; + return (r = stt.st_size); +} + +FIO_IFUNC size_t fio_filename_type(const char *filename) { + size_t r = 0; + struct stat stt; + if (stat(filename, &stt)) + return r; + return (r = (size_t)((stt.st_mode & S_IFMT))); +} + +FIO_IFUNC size_t fio_fd_type(int fd) { + size_t r = 0; + struct stat stt; + if (fd == -1) + return r; + if (fstat(fd, &stt)) + return r; + return (r = (size_t)((stt.st_mode & S_IFMT))); +} + +/* ***************************************************************************** +File Helper Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/** + * Opens `filename`, returning the same as values as `open` on POSIX systems. + * + * If `path` starts with a `"~/"` than it will be relative to the user's home + * folder (on Windows, testing for `"~\"`). + */ +SFUNC int fio_filename_open(const char *filename, int flags) { + int fd = -1; + /* POSIX implementations. */ + if (filename == NULL) + return fd; + char *path = NULL; + size_t path_len = 0; + + if (filename[0] == '~' && + (filename[1] == FIO_FOLDER_SEPARATOR || filename[1] == '/')) { + char *home = getenv("HOME"); + if (home) { + size_t filename_len = FIO_STRLEN(filename); + size_t home_len = FIO_STRLEN(home); + if ((home_len + filename_len) >= (1 << 16)) { + /* too long */ + FIO_LOG_ERROR("couldn't open file, as filename is too long %.*s...", + (int)16, + (filename_len >= 16 ? filename : home)); + return fd; + } + if (home[home_len - 1] == FIO_FOLDER_SEPARATOR || + home[home_len - 1] == '/') + --home_len; + path_len = home_len + filename_len - 1; + path = + (char *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*path) * (path_len + 1), 0); + if (!path) + return fd; + FIO_MEMCPY(path, home, home_len); + FIO_MEMCPY(path + home_len, filename + 1, filename_len); + path[path_len] = 0; + filename = path; + } + } + fd = open(filename, flags, (S_IWUSR | S_IRUSR)); + if (path) { + FIO_MEM_FREE_(path, path_len + 1); + } + return fd; +} + +/** Returns 1 if `path` does folds backwards (has "/../" or "//"). */ +SFUNC int fio_filename_is_unsafe(const char *path) { +#if FIO_OS_WIN + const char sep = '\\'; +#else + const char sep = '/'; +#endif + for (;;) { + if (!path) + return 0; + if (path[0] == sep && path[1] == sep) + return 1; + if (path[0] == sep && path[1] == '.' && path[2] == '.' && path[3] == sep) + return 1; + ++path; + path = strchr(path, sep); + } +} + +/** Returns 1 if `path` does folds backwards (has "/../" or "//"). */ +SFUNC int fio_filename_is_unsafe_url(const char *path) { + const char sep = '/'; + for (;;) { + if (!path) + return 0; + if (path[0] == sep && path[1] == sep) + return 1; + if (path[0] == sep && path[1] == '.' && path[2] == '.' && path[3] == sep) + return 1; + ++path; + path = strchr(path, sep); + } +} + +/** Creates a temporary file, returning its file descriptor. */ +SFUNC int fio_filename_tmp(void) { + // create a temporary file to contain the data. + int fd; + char name_template[512]; + size_t len = 0; + const char sep = FIO_FOLDER_SEPARATOR; + const char *tmp = NULL; + + if (!tmp) + tmp = getenv("TMPDIR"); + if (!tmp) + tmp = getenv("TMP"); + if (!tmp) + tmp = getenv("TEMP"); +#if defined(P_tmpdir) + if (!tmp && sizeof(P_tmpdir) < 464 && sizeof(P_tmpdir) > 0) { + tmp = P_tmpdir; + } +#endif + if (tmp && (len = FIO_STRLEN(tmp)) && len < 464) { + FIO_MEMCPY(name_template, tmp, len); + len -= (tmp[len - 1] == sep || tmp[len - 1] == '/'); + } else { + /* use current folder */ + name_template[len++] = '.'; + } +#ifdef O_TMPFILE + name_template[len] = 0; + fd = open(name_template, O_TMPFILE | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (fd != -1) + return fd; +#endif + name_template[len++] = sep; + FIO_MEMCPY(name_template + len, "facil_io_tmp_", 13); + len += 13; + len += fio_ltoa(name_template + len, fio_rand64(), 32); + do { + fio_ltoa(name_template + len, fio_rand64(), 32); + fd = open(name_template, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + } while (fd == -1 && errno == EEXIST); + return fd; + (void)tmp; +} + +/** Parses a file name to folder, base name and extension (zero-copy). */ +SFUNC fio_filename_s fio_filename_parse(const char *filename) { + fio_filename_s r = {{0}}; + if (!filename || !filename[0]) + return r; + const char *pos = filename; + r.basename.buf = (char *)filename; + for (;;) { + switch (*pos) { + case 0: + if (r.ext.buf) { + r.ext.len = pos - r.ext.buf; + if (!r.basename.len) { + r.basename = FIO_BUF_INFO2(--r.ext.buf, ++r.ext.len); + r.ext.buf = NULL; + r.ext.len = 0; + } + } else { + r.basename.len = (size_t)(pos - r.basename.buf); + } + if (!r.folder.len) + r.folder.buf = NULL; + if (!r.basename.len) + r.basename.buf = NULL; + if (!r.ext.len) + r.ext.buf = NULL; + return r; +#ifdef FIO_OS_WIN + case '/': /* pass through (on windows test both variants) */ +#endif + case FIO_FOLDER_SEPARATOR: + r.folder.buf = (char *)filename; + r.folder.len = (size_t)(pos - filename) + 1; + r.basename.buf = (char *)pos + 1; + r.ext.buf = NULL; + r.basename.len = 0; + break; + case '.': + if (!r.ext.buf) { + r.ext.buf = (char *)pos + 1; + r.basename.len = (char *)pos - r.basename.buf; + } + break; + } + ++pos; + } +} + +/** Parses a file name to folder, base name and extension (zero-copy). */ +SFUNC fio_filename_s fio_filename_parse2(const char *filename, size_t len) { + fio_filename_s r = {{0}}; + if (!filename || !filename[0]) + return r; + const char *pos = filename; + const char *end = filename + len; + r.basename.buf = (char *)filename; + for (;;) { + if (pos == end) + goto done; + switch (*pos) { + case 0: + done: + if (r.ext.buf) { + r.ext.len = pos - r.ext.buf; + if (!r.basename.len) { + r.basename = FIO_BUF_INFO2(--r.ext.buf, ++r.ext.len); + r.ext.buf = NULL; + r.ext.len = 0; + } + } else { + r.basename.len = (size_t)(pos - r.basename.buf); + } + if (!r.folder.len) + r.folder.buf = NULL; + if (!r.basename.len) + r.basename.buf = NULL; + if (!r.ext.len) + r.ext.buf = NULL; + return r; +#ifdef FIO_OS_WIN + case '/': /* pass through (on windows test both variants) */ +#endif + case FIO_FOLDER_SEPARATOR: + r.folder.buf = (char *)filename; + r.folder.len = (size_t)(pos - filename) + 1; + r.basename.buf = (char *)pos + 1; + r.ext.buf = NULL; + r.basename.len = 0; + break; + case '.': + if (!r.ext.buf) { + r.ext.buf = (char *)pos + 1; + r.basename.len = (char *)pos - r.basename.buf; + } + break; + } + ++pos; + } +} +/** Returns index for next `token` in `fd`, or -1 at EOF. */ +SFUNC size_t fio_fd_find_next(int fd, char token, size_t start_at) { + size_t r = FIO_FD_FIND_EOF; + if (fd == -1 || start_at == FIO_FD_FIND_EOF) + return r; + char buf[FIO_FD_FIND_BLOCK]; + for (;;) { + size_t l = fio_fd_read(fd, buf, (size_t)FIO_FD_FIND_BLOCK, (off_t)start_at); + if (!l) + return r; + char *pos = (char *)FIO_MEMCHR(buf, token, l); + if (!pos) { + start_at += l; + continue; + } + r = start_at + (size_t)(pos - buf); + return r; + } +} + +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_FILES */ +#undef FIO_FILES +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ATOL /* Development inclusion - ignore line */ +#define FIO_JSON /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + JSON Parsing + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_JSON) && !defined(H___FIO_JSON___H) +#define H___FIO_JSON___H + +#ifndef FIO_JSON_MAX_DEPTH +/** Maximum allowed JSON nesting level. Values above 64K might fail. */ +#define FIO_JSON_MAX_DEPTH 512 +#endif + +#ifndef FIO_JSON_USE_FIO_ATON +#define FIO_JSON_USE_FIO_ATON 0 +#endif + +/** The JSON parser settings. */ +typedef struct { + /** NULL object was detected. Returns new object as `void *`. */ + void *(*on_null)(void); + /** TRUE object was detected. Returns new object as `void *`. */ + void *(*on_true)(void); + /** FALSE object was detected. Returns new object as `void *`. */ + void *(*on_false)(void); + /** Number was detected (long long). Returns new object as `void *`. */ + void *(*on_number)(int64_t i); + /** Float was detected (double).Returns new object as `void *`. */ + void *(*on_float)(double f); + /** (escaped) String was detected. Returns a new String as `void *`. */ + void *(*on_string)(const void *start, size_t len); + /** (unescaped) String was detected. Returns a new String as `void *`. */ + void *(*on_string_simple)(const void *start, size_t len); + /** Dictionary was detected. Returns ctx to hash map or NULL on error. */ + void *(*on_map)(void *ctx, void *at); + /** Array was detected. Returns ctx to array or NULL on error. */ + void *(*on_array)(void *ctx, void *at); + /** Array was detected. Returns non-zero on error. */ + int (*map_push)(void *ctx, void *key, void *value); + /** Array was detected. Returns non-zero on error. */ + int (*array_push)(void *ctx, void *value); + /** Called when an array object (`ctx`) appears done. */ + int (*array_finished)(void *ctx); + /** Called when a map object (`ctx`) appears done. */ + int (*map_finished)(void *ctx); + /** Called when context is expected to be an array (i.e., fio_json_update). */ + int (*is_array)(void *ctx); + /** Called when context is expected to be a map (i.e., fio_json_update). */ + int (*is_map)(void *ctx); + /** Called for the `key` element in case of error or NULL value. */ + void (*free_unused_object)(void *ctx); + /** the JSON parsing encountered an error - what to do with ctx? */ + void *(*on_error)(void *ctx); +} fio_json_parser_callbacks_s; + +/** The JSON return type. */ +typedef struct { + void *ctx; + size_t stop_pos; + int err; +} fio_json_result_s; + +/** + * The facil.io JSON parser is a non-strict parser, with support for trailing + * commas in collections, new-lines in strings, extended escape characters and + * octal, hex and binary numbers. + * + * The parser allows for streaming data and decouples the parsing process from + * the resulting data-structure by calling static callbacks for JSON related + * events. + * + * Returns the number of bytes consumed before parsing stopped (due to either + * error or end of data). Stops as close as possible to the end of the buffer or + * once an object parsing was completed. + */ +SFUNC fio_json_result_s fio_json_parse(fio_json_parser_callbacks_s *settings, + const char *json_string, + const size_t len); + +/* ***************************************************************************** +JSON Parsing - Implementation - Helpers and Callbacks + + +Note: static Callacks must be implemented in the C file that uses the parser + +Note: a Helper API is provided for the parsing implementation. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +// typedef struct { +// struct { +// uintptr_t start; +// uintptr_t end; +// } instructions[16]; +// uint32_t count; +// } fio___json_cb_queue_s; + +typedef struct { + fio_json_parser_callbacks_s cb; + void *ctx; + void *key; + const char *pos; + const char *end; + uint32_t depth; + int32_t error; +} fio___json_state_s; + +FIO_SFUNC void *fio___json_consume(fio___json_state_s *s); + +#if 0 /* Used for Debugging */ +#define FIO_JSON___PRINT_STEP(s, step_name) \ + FIO_LOG_DEBUG2("JSON " step_name " starting at: %.*s", \ + (int)((s->end - s->pos) > 16 ? 16 : (s->end - s->pos)), \ + s->pos) +#else +#define FIO_JSON___PRINT_STEP(s, step_name) +#endif + +FIO_IFUNC int fio___json_consume_whitespace(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "white space"); + while (s->pos < s->end) { + if (!(((uint8_t)*s->pos == 0x09U) | ((uint8_t)*s->pos == 0x0AU) | + ((uint8_t)*s->pos == 0x0DU) | ((uint8_t)*s->pos == 0x20U))) + return 0; + ++s->pos; + } + return (s->error = -1); +} +FIO_IFUNC int fio___json_consume_comma(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "comma"); + fio___json_consume_whitespace(s); + if (*s->pos != ',') + return -1; + fio___json_consume_whitespace(s); + return 0; +} +FIO_IFUNC int fio___json_consume_colon(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "colon"); + fio___json_consume_whitespace(s); + if (*s->pos != ':') + return -1; + fio___json_consume_whitespace(s); + return 0; +} + +FIO_SFUNC void *fio___json_consume_infinit(fio___json_state_s *s, + _Bool negative) { + FIO_JSON___PRINT_STEP(s, "infinity"); + const uint64_t inf64 = fio_buf2u64u("infinity"); + const uint16_t inf16 = fio_buf2u16u("nf"); + uint64_t tst64; + uint16_t tst16; + if (s->pos + 3 > s->end) + goto buffer_error; + tst16 = (fio_buf2u16u(s->pos + 1) | (uint16_t)0x2020U); + if (tst16 == inf16) { + if (s->pos + 7 < s->end) { + tst64 = (fio_buf2u64u(s->pos) | (uint64_t)0x2020202020202020ULL); + s->pos += (tst64 == inf64) * 5; + } + s->pos += 3; + return s->cb.on_float(negative ? (INFINITY * -1) : INFINITY); + } +buffer_error: + s->error = 1; + return NULL; +} + +FIO_SFUNC void *fio___json_consume_number(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "number"); +#if FIO_JSON_USE_FIO_ATON + fio_aton_s aton = fio_aton((char **)&s->pos); + return aton.is_float ? s->cb.on_float(aton.f) : s->cb.on_number(aton.i); +#else + const char *tst = s->pos; + uint64_t i; + double f; + _Bool negative = (tst[0] == '-') | (tst[0] == '+'); + _Bool hex = 0; + _Bool binary = 0; + long ilimit = 19 + negative; + tst += negative; + if (tst + 1 > s->end) + goto buffer_overflow; + if ((tst[0] | 0x20) == 'i') + goto is_inifinity; + tst += (tst[0] == '0' && tst + 2 < s->end); + if ((tst[0] | 32) == 'x') { + hex = 1; + while ((tst < s->end) & (((tst[0] >= '0') & (tst[0] <= '9')) | + (((tst[0] | 32) >= 'a') & ((tst[0] | 32) <= 'f')))) + ++tst; + } else if ((tst[0] | 32) == 'b') { + binary = 1; + ilimit = 66 + negative; + while (((tst < s->end) & (tst[0] >= '0') & (tst[0] <= '1'))) + ++tst; + } else { + while (((tst < s->end) & (tst[0] >= '0') & (tst[0] <= '9'))) + ++tst; + } + if (tst > (s->pos + ilimit) || + ((tst < s->end) && + (tst[0] == '.' || (tst[0] | 32) == 'e' || (tst[0] | 32) == 'p'))) + goto is_float; + tst = s->pos; + s->pos += negative; + errno = 0; + i = (hex ? fio_atol16u((char **)&s->pos) + : binary ? fio_atol_bin((char **)&s->pos) + : *s->pos == '0' ? fio_atol8u((char **)&s->pos) + : fio_atol10u((char **)&s->pos)); + if (errno == E2BIG || (((uint64_t)(1 ^ hex ^ binary) << 63) & i)) + goto is_float_from_error; + // s->error = (errno == E2BIG); + return s->cb.on_number(fio_u2i_limit(i, negative)); +is_float_from_error: + s->pos = tst; + errno = 0; + +is_float: + f = fio_atof((char **)&s->pos); + s->error = (errno == E2BIG); + return s->cb.on_float(f); + +buffer_overflow: + s->error = 1; + return NULL; +is_inifinity: + ++s->pos; + return fio___json_consume_infinit(s, negative); +#endif /* FIO_JSON_USE_FIO_ATON */ +} + +FIO_SFUNC void *fio___json_consume_string(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "string"); + void *(*cb)(const void *start, size_t len) = s->cb.on_string_simple; + const char *start = ++s->pos; + for (; s->pos < s->end; ++s->pos) { + if (*s->pos == '"') + return cb(start, (s->pos++) - start); + if (*s->pos == '\\') + cb = s->cb.on_string; + s->pos += (*s->pos == '\\'); + } + s->error = 1; + return NULL; +} + +FIO_SFUNC void *fio___json_consume_map(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "map"); + void *old = s->ctx; + void *old_key = s->key; + void *map = s->cb.on_map(s, s->key); + s->ctx = map; + s->key = NULL; + if (++s->depth == FIO_JSON_MAX_DEPTH) + goto too_deep; + for (;;) { + ++s->pos; + s->key = fio___json_consume(s); + if (s->error || !s->key) + break; + if (*s->pos != ':') + break; + ++s->pos; + if (fio___json_consume_whitespace(s)) + break; + void *value = fio___json_consume(s); + s->error |= s->cb.map_push(s->ctx, s->key, value); + s->key = NULL; + if (s->error || fio___json_consume_whitespace(s)) + break; + if (*s->pos != ',') + break; + } + if (s->key) { + s->error = 1; + s->cb.free_unused_object(s->key); + } else if (*s->pos != '}' || s->error) { + s->error = 1; + } else { + ++s->pos; + } + --s->depth; + s->ctx = old; + s->key = old_key; + s->error |= s->cb.map_finished(map); + return map; +too_deep: + s->ctx = old; + s->key = old_key; + s->error = 1; + --s->depth; + return map; +} +FIO_SFUNC void *fio___json_consume_array(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "array"); + void *old = s->ctx; + void *array = s->ctx = s->cb.on_array(s, s->key); + if (++s->depth == FIO_JSON_MAX_DEPTH) + goto too_deep; + for (;;) { + ++s->pos; + if (fio___json_consume_whitespace(s)) + break; + if (*s->pos == ']') + break; + void *value = fio___json_consume(s); + if (value) + s->error |= s->cb.array_push(s->ctx, value); + if (s->error || fio___json_consume_comma(s)) + break; + if (*s->pos != ',') + break; + } + if (*s->pos != ']' || s->error) { + s->error = 1; + } else { + ++s->pos; + } + s->ctx = old; + --s->depth; + s->error |= s->cb.array_finished(array); + return array; +too_deep: + s->ctx = old; + s->error = 1; + --s->depth; + return array; +} +FIO_SFUNC void *fio___json_consume_null(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "null"); + const uint32_t wrd = fio_buf2u32u("null"); + uint32_t data; + if (s->end - s->pos < 4) + goto on_error; + data = fio_buf2u32u(s->pos) | (uint32_t)0x20202020; + if (data != wrd) + goto on_error; + s->pos += 4; + return s->cb.on_null(); +on_error: + s->error = 1; + return NULL; +} +FIO_SFUNC void *fio___json_consume_true(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "true"); + const uint32_t wrd = fio_buf2u32u("true"); + uint32_t data; + if (s->end - s->pos < 4) + goto on_error; + data = fio_buf2u32u(s->pos) | (uint32_t)0x20202020; + if (data != wrd) + goto on_error; + s->pos += 4; + return s->cb.on_true(); +on_error: + s->error = 1; + return NULL; +} +FIO_SFUNC void *fio___json_consume_false(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "false"); + const uint32_t wrd = fio_buf2u32u("alse"); + uint32_t data; + if (s->end - s->pos < 5) + goto on_error; + data = fio_buf2u32u(s->pos + 1) | (uint32_t)0x20202020; + if (data != wrd) + goto on_error; + s->pos += 5; + return s->cb.on_false(); +on_error: + s->error = 1; + return NULL; +} +FIO_SFUNC void *fio___json_consume_nan(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "nan"); + const uint16_t wrd = fio_buf2u16u("an"); + uint16_t data; + if (s->end - s->pos < 3) + goto on_error; + data = fio_buf2u16u(s->pos + 1) | (uint16_t)0x2020; + if (data != wrd) + goto on_error; + s->pos += 3; + return s->cb.on_float(NAN); +on_error: + s->error = 1; + return NULL; +} +FIO_SFUNC int fio___json_consume_comment(fio___json_state_s *s) { + FIO_JSON___PRINT_STEP(s, "comment"); + const size_t len = (size_t)(s->end - s->pos); + if (*s->pos == '#' || (len > 2 && s->pos[0] == '/' && s->pos[1] == '/')) { + /* EOL style comment, C style or Bash/Ruby style*/ + const char *tmp = (const char *)FIO_MEMCHR(s->pos, '\n', len); + if (tmp) { + s->pos = tmp; + return 0; + } + s->error = 1; + return -1; + } + if ((len > 3 && s->pos[0] == '/' && s->pos[1] == '*')) { + const char *tmp = s->pos; + while (tmp < s->end && + (tmp = (const char *)FIO_MEMCHR(s->pos, '/', s->end - tmp))) { + s->pos = ++tmp; + if (tmp[-2] == '*') + return 0; + } + s->error = 1; + return -1; + } + s->error = 1; + return -1; +} + +void *fio___json_consume(fio___json_state_s *s) { + for (;;) { + FIO_JSON___PRINT_STEP(s, "consumption type test"); + switch (*s->pos) { + case 0x09: /* fall through */ + case 0x0A: /* fall through */ + case 0x0D: /* fall through */ + case 0x20: + ++s->pos; + if (fio___json_consume_whitespace(s)) + goto set_error; + continue; + case '+': /* fall through */ + case '-': /* fall through */ + case '0': /* fall through */ + case '1': /* fall through */ + case '2': /* fall through */ + case '3': /* fall through */ + case '4': /* fall through */ + case '5': /* fall through */ + case '6': /* fall through */ + case '7': /* fall through */ + case '8': /* fall through */ + case '9': /* fall through */ + case 'x': /* fall through */ + case '.': /* fall through */ + case 'e': /* fall through */ + case 'E': return fio___json_consume_number(s); + case 'i': /* fall through */ + case 'I': return fio___json_consume_infinit(s, 0); + case '"': return fio___json_consume_string(s); + case '{': return fio___json_consume_map(s); + case '}': return NULL; /* don't progress, just stop. */ + case '[': return fio___json_consume_array(s); + case ']': return NULL; /* don't progress, just stop. */ + case 'T': /* fall through */ + case 't': return fio___json_consume_true(s); + case 'F': /* fall through */ + case 'f': return fio___json_consume_false(s); + case 'N': /* fall through */ + case 'n': + return (((s->pos[1] | 32) == 'u') ? fio___json_consume_null + : fio___json_consume_nan)(s); + case '#': + case '/': + if (fio___json_consume_comment(s)) + goto set_error; + continue; + } + set_error: + s->error = 1; + return NULL; + } +} + +static int fio___json_callback_noop(void *ctx) { + return 0; + (void)ctx; +} +static void *fio___json_callback_noop2(void *ctx) { return ctx; } + +FIO_SFUNC int fio___json_callbacks_validate(fio_json_parser_callbacks_s *cb) { + if (!cb) + goto is_invalid; + + if (!cb->on_string) + cb->on_string = cb->on_string_simple; + if (!cb->on_string_simple) + cb->on_string_simple = cb->on_string; + + if (!cb->on_null) + goto is_invalid; + if (!cb->on_true) + goto is_invalid; + if (!cb->on_false) + goto is_invalid; + if (!cb->on_number) + goto is_invalid; + if (!cb->on_float) + goto is_invalid; + if (!cb->on_string) + goto is_invalid; + if (!cb->on_map) + goto is_invalid; + if (!cb->on_array) + goto is_invalid; + if (!cb->map_push) + goto is_invalid; + if (!cb->array_push) + goto is_invalid; + if (!cb->free_unused_object) + goto is_invalid; + if (!cb->array_finished) + cb->array_finished = fio___json_callback_noop; + if (!cb->map_finished) + cb->map_finished = fio___json_callback_noop; + if (!cb->is_array) + cb->is_array = fio___json_callback_noop; + if (!cb->is_map) + cb->is_map = fio___json_callback_noop; + if (!cb->on_error) + cb->on_error = fio___json_callback_noop2; + return 0; +is_invalid: + return -1; +} +/** + * Returns the number of bytes consumed. Stops as close as possible to the end + * of the buffer or once an object parsing was completed. + */ +SFUNC fio_json_result_s fio_json_parse(fio_json_parser_callbacks_s *callbacks, + const char *start, + const size_t len) { + fio_json_result_s r = {.stop_pos = 0, .err = 0}; + fio___json_state_s state; + if (fio___json_callbacks_validate(callbacks)) + goto missing_callback; + + state = (fio___json_state_s){ + .cb = callbacks[0], + .pos = start, + .end = start + len, + }; + + /* skip BOM, if exists */ + if (len >= 3 && state.pos[0] == (char)0xEF && state.pos[1] == (char)0xBB && + state.pos[2] == (char)0xBF) { + state.pos += 3; + if (len == 3) + goto finish; + } + r.ctx = fio___json_consume(&state); + r.err = state.error; + r.stop_pos = state.pos - start; + if (state.error) + goto failed; +finish: + return r; +failed: + FIO_LOG_DEBUG( + "JSON parsing failed after:\n%.*s", + ((state.end - state.pos > 48) ? 48 : ((int)(state.end - state.pos))), + state.pos); + r.ctx = callbacks->on_error(r.ctx); + return r; + +missing_callback: + FIO_LOG_ERROR("JSON parser missing a critical callback!"); + r.err = 1; + return r; +} + +/** Dictionary was detected. Returns ctx to hash map or NULL on error. */ +FIO_SFUNC void *fio___json_parse_update_on_map(void *ctx, void *at) { + void **ex_data = (void **)ctx; + fio_json_parser_callbacks_s *cb = (fio_json_parser_callbacks_s *)ex_data[0]; + ctx = ex_data[1]; + fio___json_state_s *s = (fio___json_state_s *)ex_data[2]; + s->cb.on_map = cb->on_map; + s->cb.on_array = cb->on_array; + if (ctx && !s->cb.is_map(ctx)) + return NULL; + else if (!ctx) + ctx = cb->on_map(ctx, at); + return ctx; +} +/** Array was detected. Returns ctx to array or NULL on error. */ +FIO_SFUNC void *fio___json_parse_update_on_array(void *ctx, void *at) { + void **ex_data = (void **)ctx; + fio_json_parser_callbacks_s *cb = (fio_json_parser_callbacks_s *)ex_data[0]; + ctx = ex_data[1]; + fio___json_state_s *s = (fio___json_state_s *)ex_data[2]; + s->cb.on_map = cb->on_map; + s->cb.on_array = cb->on_array; + if (ctx && !s->cb.is_array(ctx)) + return NULL; + else if (!ctx) + ctx = cb->on_array(ctx, at); + + return ctx; +} + +/** + * Use only when `ctx` is an object and JSON data is wrapped in an object (of + * the same type). + * + * i.e., update an array or hash map. + */ +SFUNC fio_json_result_s fio_json_parse_update(fio_json_parser_callbacks_s *s, + void *ctx, + const char *start, + const size_t len) { + fio_json_result_s r = {.stop_pos = 0, .err = 0}; + fio_json_parser_callbacks_s callbacks; + callbacks.on_map = fio___json_parse_update_on_map; + callbacks.on_array = fio___json_parse_update_on_array; + fio___json_state_s state; + void *ex_data[3] = {s, ctx, &state}; + + if (!s->is_array) + goto missing_callback; + if (!s->is_map) + goto missing_callback; + if (fio___json_callbacks_validate(s)) + goto missing_callback; + + callbacks = *s; + state = (fio___json_state_s){ + .cb = callbacks, + .pos = start, + .end = start + len, + }; + state.ctx = (void *)ex_data; + /* skip BOM, if exists */ + if (len >= 3 && state.pos[0] == (char)0xEF && state.pos[1] == (char)0xBB && + state.pos[2] == (char)0xBF) { + state.pos += 3; + if (len == 3) + goto finish; + } + r.ctx = fio___json_consume(&state); + r.err = state.error; + r.stop_pos = state.pos - start; + if (state.error) + goto failed; +finish: + return r; +failed: + FIO_LOG_DEBUG( + "JSON parsing failed after:\n%.*s", + ((state.end - state.pos > 48) ? 48 : ((int)(state.end - state.pos))), + state.pos); + r.ctx = s->on_error(r.ctx); + return r; +missing_callback: + FIO_LOG_ERROR("JSON parser missing a critical callback!"); + r.err = 1; + return r; +} +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_JSON +#endif /* FIO_JSON */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SOCK /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Basic Socket Helpers + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SOCK) && !defined(H___FIO_SOCK___H) +#define H___FIO_SOCK___H + +/* ***************************************************************************** +OS specific patches. +***************************************************************************** */ +#if FIO_OS_WIN +#if _MSC_VER +#pragma comment(lib, "Ws2_32.lib") +#endif +#include +#include +#include +#ifdef AF_UNIX +#include +#endif +#ifndef FIO_SOCK_FD_ISVALID +#define FIO_SOCK_FD_ISVALID(fd) ((size_t)(fd) <= (size_t)0x7FFFFFFF) +#endif +/** Acts as POSIX write. Use this macro for portability with WinSock2. */ +#define fio_sock_write(fd, data, len) send((fd), (data), (len), 0) +/** Acts as POSIX read. Use this macro for portability with WinSock2. */ +#define fio_sock_read(fd, buf, len) recv((fd), (buf), (len), 0) +/** Acts as POSIX close. Use this macro for portability with WinSock2. */ +#define fio_sock_close(fd) closesocket(fd) +/** Protects against type size overflow on Windows, where FD > MAX_INT. */ +FIO_IFUNC int fio_sock_accept(int s, struct sockaddr *addr, int *addrlen) { + int r = -1; + SOCKET c = accept(s, addr, addrlen); + if (c == INVALID_SOCKET) + return r; + if (FIO_SOCK_FD_ISVALID(c)) { + r = (int)c; + return r; + } + closesocket(c); + errno = ERANGE; + FIO_LOG_ERROR("Windows SOCKET value overflowed int limits (was: %zu)", + (size_t)c); + return r; +} +#define accept fio_sock_accept +#define poll WSAPoll +/** Acts as POSIX dup. Use this for portability with WinSock2. */ +FIO_IFUNC int fio_sock_dup(int original) { + int fd = -1; + SOCKET tmpfd = INVALID_SOCKET; + WSAPROTOCOL_INFO info; + if (!WSADuplicateSocket(original, GetCurrentProcessId(), &info) && + (tmpfd = WSASocket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, &info, 0, 0)) != + INVALID_SOCKET) { + if (FIO_SOCK_FD_ISVALID(tmpfd)) + fd = (int)tmpfd; + else + fio_sock_close(tmpfd); + } + return fd; +} + +#elif FIO_HAVE_UNIX_TOOLS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef FIO_SOCK_FD_ISVALID +#define FIO_SOCK_FD_ISVALID(fd) ((int)(fd) != (int)-1) +#endif +/** Acts as POSIX write. Use this macro for portability with WinSock2. */ +#define fio_sock_write(fd, data, len) write((fd), (data), (len)) +/** Acts as POSIX read. Use this macro for portability with WinSock2. */ +#define fio_sock_read(fd, buf, len) read((fd), (buf), (len)) +/** Acts as POSIX dup. Use this macro for portability with WinSock2. */ +#define fio_sock_dup(fd) dup(fd) +/** Acts as POSIX close. Use this macro for portability with WinSock2. */ +#define fio_sock_close(fd) close(fd) +/** Acts as POSIX accept. Use this macro for portability with WinSock2. */ +#define fio_sock_accept(fd, addr, addrlen) accept(fd, addr, addrlen) +#else +#error FIO_SOCK requires a supported OS (Windows / POSIX). +#endif + +/* Set to 1 if in need to debug unexpected IO closures. */ +#if defined(DEBUG) && 0 +#define close(fd) \ + do { \ + FIO_LOG_DWARNING("(%d) (" FIO__FILE__ ":" FIO_MACRO2STR( \ + __LINE__) ") fio_sock_close called for fd %d", \ + fio_getpid(), \ + (int)fd); \ + close(fd); \ + } while (0) +#endif /* DEBUG */ + +/* ***************************************************************************** +Socket OS abstraction - API +***************************************************************************** */ + +#ifndef FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT +#define FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT (1ULL << 24) +#endif + +/** Socket type flags */ +typedef enum { + FIO_SOCK_SERVER = 0, + FIO_SOCK_CLIENT = 1, + FIO_SOCK_NONBLOCK = 2, + FIO_SOCK_TCP = 4, + FIO_SOCK_UDP = 8, +#ifdef AF_UNIX + FIO_SOCK_UNIX = 16, + FIO_SOCK_UNIX_PRIVATE = (16 | 32), +#else +#define FIO_SOCK_UNIX 0 +#define FIO_SOCK_UNIX_PRIVATE 0 +#endif +} fio_sock_open_flags_e; + +/** + * Creates a new socket according to the provided flags. + * + * The `port` string will be ignored when `FIO_SOCK_UNIX` is set. + */ +FIO_IFUNC int fio_sock_open(const char *restrict address, + const char *restrict port, + uint16_t flags); + +/** Creates a new socket, according to the provided flags. */ +SFUNC int fio_sock_open2(const char *url, uint16_t flags); + +/** + * Attempts to resolve an address to a valid IP6 / IP4 address pointer. + * + * The `sock_type` element should be a socket type, such as `SOCK_DGRAM` (UDP) + * or `SOCK_STREAM` (TCP/IP). + * + * The address should be freed using `fio_sock_address_free`. + */ +FIO_IFUNC struct addrinfo *fio_sock_address_new(const char *restrict address, + const char *restrict port, + int sock_type); + +/** Frees the pointer returned by `fio_sock_address_new`. */ +FIO_IFUNC void fio_sock_address_free(struct addrinfo *a); + +/** + * Returns a human readable address representation of the socket's peer address. + * + * On error, returns a NULL buffer with zero length. + * + * Buffer lengths are limited to 63 bytes. + * + * This function is limited in its thread safety to 128 threads / calls. + */ +SFUNC fio_buf_info_s fio_sock_peer_addr(int s); + +/** Creates a new network socket and binds it to a local address. */ +SFUNC int fio_sock_open_local(struct addrinfo *addr, int nonblock); + +/** Creates a new network socket and connects it to a remote address. */ +SFUNC int fio_sock_open_remote(struct addrinfo *addr, int nonblock); + +/** Creates a new Unix socket and binds it to a local address. */ +SFUNC int fio_sock_open_unix(const char *address, uint16_t flags); + +/** Sets a file descriptor / socket to non blocking state. */ +SFUNC int fio_sock_set_non_block(int fd); + +/** Attempts to maximize the allowed open file limits. returns known limit */ +SFUNC size_t fio_sock_maximize_limits(size_t maximum_limit); + +/** + * Returns 0 on timeout, -1 on error or the events that are valid. + * + * Possible events are POLLIN | POLLOUT + */ +SFUNC short fio_sock_wait_io(int fd, short events, int timeout); + +/** A helper macro that waits on a single IO with no callbacks (0 = no event) */ +#define FIO_SOCK_WAIT_RW(fd, timeout_) \ + fio_sock_wait_io(fd, POLLIN | POLLOUT, timeout_) + +/** A helper macro that waits on a single IO with no callbacks (0 = no event) */ +#define FIO_SOCK_WAIT_R(fd, timeout_) fio_sock_wait_io(fd, POLLIN, timeout_) + +/** A helper macro that waits on a single IO with no callbacks (0 = no event) */ +#define FIO_SOCK_WAIT_W(fd, timeout_) fio_sock_wait_io(fd, POLLOUT, timeout_) + +/* ***************************************************************************** +IO Poll - Implementation (always static / inlined) +***************************************************************************** */ + +/** + * Creates a new socket according to the provided flags. + * + * The `port` string will be ignored when `FIO_SOCK_UNIX` is set. + */ +FIO_IFUNC int fio_sock_open(const char *restrict address, + const char *restrict port, + uint16_t flags) { + struct addrinfo *addr = NULL; + int fd; +#ifdef AF_UNIX + if ((flags & FIO_SOCK_UNIX)) + return fio_sock_open_unix(address, flags); +#endif + + switch ((flags & ((uint16_t)FIO_SOCK_TCP | (uint16_t)FIO_SOCK_UDP))) { + case 0: /* fall through - default to TCP/IP*/ + case FIO_SOCK_TCP: + addr = fio_sock_address_new(address, port, SOCK_STREAM); + if (!addr) { + FIO_LOG_ERROR("(fio_sock_open) address error: %s", strerror(errno)); + return -1; + } + if ((flags & FIO_SOCK_CLIENT)) { + fd = fio_sock_open_remote(addr, (flags & FIO_SOCK_NONBLOCK)); + } else { + fd = fio_sock_open_local(addr, (flags & FIO_SOCK_NONBLOCK)); + if (fd != -1 && listen(fd, SOMAXCONN) == -1) { + FIO_LOG_ERROR("(fio_sock_open) failed on call to listen: %s", + strerror(errno)); + fio_sock_close(fd); + fd = -1; + } + } + fio_sock_address_free(addr); + return fd; + case FIO_SOCK_UDP: + addr = fio_sock_address_new(address, port, SOCK_DGRAM); + if (!addr) { + FIO_LOG_ERROR("(fio_sock_open) address error: %s", strerror(errno)); + return -1; + } + if ((flags & FIO_SOCK_CLIENT)) { + fd = fio_sock_open_remote(addr, (flags & FIO_SOCK_NONBLOCK)); + } else { + fd = fio_sock_open_local(addr, (flags & FIO_SOCK_NONBLOCK)); + } + fio_sock_address_free(addr); + return fd; + } + + FIO_LOG_ERROR( + "(fio_sock_open) the FIO_SOCK_TCP and FIO_SOCK_UDP flags are exclusive"); + return -1; +} + +FIO_IFUNC struct addrinfo *fio_sock_address_new( + const char *restrict address, + const char *restrict port, + int sock_type /*i.e., SOCK_DGRAM */) { + struct addrinfo addr_hints = (struct addrinfo){0}, *a; + int e; + addr_hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4 + addr_hints.ai_socktype = sock_type; + addr_hints.ai_flags = AI_PASSIVE; // use my IP + + size_t port_len = (port ? FIO_STRLEN(port) : 0U); + switch (port_len) { /* skip system service lookup for common web stuff */ + case 2: + if ((port[0] | 32) == 'w' && (port[1] | 32) == 's') + port = "80"; + break; + case 3: + if ((port[0] | 32) == 'w' && (port[1] | 32) == 's' && (port[2] | 32) == 's') + port = "443"; + else if ((port[0] | 32) == 's' && (port[1] | 32) == 's' && + (port[2] | 32) == 'e') + port = "80"; + break; + case 4: + if ((port[0] | 32) == 'h' && (port[1] | 32) == 't' && + (port[2] | 32) == 't' && (port[3] | 32) == 'p') + port = "80"; + else if ((port[0] | 32) == 's' && (port[1] | 32) == 's' && + (port[2] | 32) == 'e' && (port[3] | 32) == 's') + port = "443"; + break; + case 5: + if ((port[0] | 32) == 'h' && (port[1] | 32) == 't' && + (port[2] | 32) == 't' && (port[3] | 32) == 'p' && (port[4] | 32) == 's') + port = "443"; + break; + } + +#if 1 /* override system resolution for localhost ? */ + size_t address_len = (address ? FIO_STRLEN(address) : 0U); + if (address && address_len == 9 && (address[0] | 32) == 'l' && + (fio_buf2u64u(address + 1) | (uint64_t)0x2020202020202020ULL) == + fio_buf2u64u("ocalhost")) + address = "127.0.0.1"; + else if (sock_type != SOCK_DGRAM && address_len == 7 && + (fio_buf2u64u("0.0.0.0") | + fio_buf2u64u("\x00\x00\x00\x00\x00\x00\x00\xFF")) == + (fio_buf2u64u(address) | + fio_buf2u64u("\x00\x00\x00\x00\x00\x00\x00\xFF"))) + address = NULL; /* bind to everything INADDR_ANY */ +#endif + /* call for OS address resolution */ + if ((e = getaddrinfo(address, (port ? port : "0"), &addr_hints, &a)) != 0) { + FIO_LOG_ERROR("(fio_sock_address_new(\"%s\", \"%s\")) error: %s", + (address ? address : "NULL"), + (port ? port : "0"), + gai_strerror(e)); + return NULL; + } + return a; +} + +FIO_IFUNC void fio_sock_address_free(struct addrinfo *a) { freeaddrinfo(a); } + +/* ***************************************************************************** +FIO_SOCK - Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/** Creates a new socket, according to the provided flags. */ +SFUNC int fio_sock_open2(const char *url, uint16_t flags) { + char buf[2048]; + char port[64]; + char *addr = buf; + char *pr = port; + + /* parse URL */ + fio_url_s u = fio_url_parse(url, FIO_STRLEN(url)); +#ifdef AF_UNIX + if (!u.host.buf && !u.port.buf && u.path.buf) { + /* Unix socket - force flag validation */ + flags &= ~((uint16_t)(FIO_SOCK_UNIX | FIO_SOCK_TCP)); + flags |= (u.scheme.len == 4 && + fio_buf2u32u(u.scheme.buf) == fio_buf2u32u("priv")) + ? FIO_SOCK_UNIX_PRIVATE + : FIO_SOCK_UNIX; + if (u.path.len > 2047) { + errno = EINVAL; + FIO_LOG_ERROR( + "Couldn't open unix socket to %s - host name too long (%zu).", + url, + u.path.len); + return -1; + } + FIO_MEMCPY(buf, u.path.buf, u.path.len); + buf[u.path.len] = 0; + pr = NULL; + return fio_sock_open_unix(buf, flags); + } +#endif + if (!u.port.len) + u.port = u.scheme; + if (!u.port.len) { + pr = NULL; + } else { + if (u.port.len > 63) { + errno = EINVAL; + FIO_LOG_ERROR("Couldn't open socket to %s - port / scheme too long.", + url); + return -1; + } + FIO_MEMCPY(port, u.port.buf, u.port.len); + port[u.port.len] = 0; + if (!(flags & (FIO_SOCK_TCP | FIO_SOCK_UDP))) { + if (u.scheme.len == 3 && (u.scheme.buf[0] | 32) == 't' && + (u.scheme.buf[1] | 32) == 'c' && (u.scheme.buf[2] | 32) == 'p') + flags |= FIO_SOCK_TCP; + else if (u.scheme.len == 3 && (u.scheme.buf[0] | 32) == 'u' && + (u.scheme.buf[1] | 32) == 'd' && (u.scheme.buf[2] | 32) == 'p') + flags |= FIO_SOCK_UDP; + else if ((u.scheme.len == 4 || u.scheme.len == 5) && + (u.scheme.buf[0] | 32) == 'h' && (u.scheme.buf[1] | 32) == 't' && + (u.scheme.buf[2] | 32) == 't' && (u.scheme.buf[3] | 32) == 'p' && + (u.scheme.len == 4 || + (u.scheme.len == 5 && (u.scheme.buf[4] | 32) == 's'))) + flags |= FIO_SOCK_TCP; + } + } + if (u.host.len) { + if (u.host.len > 2047) { + errno = EINVAL; + FIO_LOG_ERROR("Couldn't open socket to %s - host name too long.", url); + return -1; + } + FIO_MEMCPY(buf, u.host.buf, u.host.len); + buf[u.host.len] = 0; + } else { + addr = NULL; + } + return fio_sock_open(addr, pr, flags); +} + +/** Sets a file descriptor / socket to non blocking state. */ +SFUNC int fio_sock_set_non_block(int fd) { +/* If they have O_NONBLOCK, use the Posix way to do it */ +#if defined(O_NONBLOCK) && defined(F_GETFL) && defined(F_SETFL) + /* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */ + int flags; + if (-1 == (flags = fcntl(fd, F_GETFL, 0))) + flags = 0; +#if defined(O_CLOEXEC) + return fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC); +#else + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif +#elif defined(FIONBIO) + /* Otherwise, use the old way of doing it */ +#if FIO_OS_WIN + unsigned long flags = 1; + if (ioctlsocket(fd, FIONBIO, &flags) == SOCKET_ERROR) { + switch (WSAGetLastError()) { + case WSANOTINITIALISED: + FIO_LOG_DEBUG("Windows non-blocking ioctl failed with WSANOTINITIALISED"); + break; + case WSAENETDOWN: + FIO_LOG_DEBUG("Windows non-blocking ioctl failed with WSAENETDOWN"); + break; + case WSAEINPROGRESS: + FIO_LOG_DEBUG("Windows non-blocking ioctl failed with WSAEINPROGRESS"); + break; + case WSAENOTSOCK: + FIO_LOG_DEBUG("Windows non-blocking ioctl failed with WSAENOTSOCK"); + break; + case WSAEFAULT: + FIO_LOG_DEBUG("Windows non-blocking ioctl failed with WSAEFAULT"); + break; + } + return -1; + } + return 0; +#else + int flags = 1; + return ioctl(fd, FIONBIO, &flags); +#endif /* FIO_OS_WIN */ +#else +#error No functions / argumnet macros for non-blocking sockets. +#endif +} + +/** Creates a new network socket and binds it to a local address. */ +SFUNC int fio_sock_open_local(struct addrinfo *addr, int nonblock) { + int fd = -1; + for (struct addrinfo *p = addr; p != NULL; p = p->ai_next) { +#if FIO_OS_WIN + SOCKET fd_tmp; + if ((fd_tmp = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == + INVALID_SOCKET) { + FIO_LOG_DEBUG("socket creation error %s", strerror(errno)); + continue; + } + if (!FIO_SOCK_FD_ISVALID(fd_tmp)) { + FIO_LOG_DEBUG("windows socket value out of valid portable range."); + errno = ERANGE; + } + fd = (int)fd_tmp; +#else + if ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + FIO_LOG_DEBUG("socket creation error %s", strerror(errno)); + continue; + } +#endif + { // avoid the "address taken" + int optval = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval)); + } + if (nonblock && fio_sock_set_non_block(fd) == -1) { + FIO_LOG_DEBUG("Couldn't set socket (%d) to non-blocking mode %s", + fd, + strerror(errno)); + fio_sock_close(fd); + fd = -1; + continue; + } + if (bind(fd, p->ai_addr, p->ai_addrlen) == -1) { + FIO_LOG_DEBUG("Failed attempt to bind socket (%d) to address %s", + fd, + strerror(errno)); + fio_sock_close(fd); + fd = -1; + continue; + } + break; + } + if (fd == -1) { + FIO_LOG_DEBUG("socket binding/creation error %s", strerror(errno)); + } + return fd; +} + +/** Creates a new network socket and connects it to a remote address. */ +SFUNC int fio_sock_open_remote(struct addrinfo *addr, int nonblock) { + int fd = -1; + for (struct addrinfo *p = addr; p != NULL; p = p->ai_next) { +#if FIO_OS_WIN + SOCKET fd_tmp; + if ((fd_tmp = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == + INVALID_SOCKET) { + FIO_LOG_DEBUG("socket creation error %s", strerror(errno)); + continue; + } + if (!FIO_SOCK_FD_ISVALID(fd_tmp)) { + FIO_LOG_DEBUG("windows socket value out of valid portable range."); + errno = ERANGE; + } + fd = (int)fd_tmp; +#else + if ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + FIO_LOG_DEBUG("socket creation error %s", strerror(errno)); + continue; + } +#endif + + if (nonblock && fio_sock_set_non_block(fd) == -1) { + FIO_LOG_DEBUG( + "Failed attempt to set client socket (%d) to non-blocking %s", + fd, + strerror(errno)); + fio_sock_close(fd); + fd = -1; + continue; + } + if (connect(fd, p->ai_addr, p->ai_addrlen) == -1 && +#if FIO_OS_WIN + (WSAGetLastError() != WSAEWOULDBLOCK || errno != EINPROGRESS) +#else + errno != EINPROGRESS +#endif + ) { +#if FIO_OS_WIN + FIO_LOG_DEBUG( + "Couldn't connect client socket (%d) to remote address %s (%d)", + fd, + strerror(errno), + WSAGetLastError()); +#else + FIO_LOG_DEBUG("Couldn't connect client socket (%d) to remote address %s", + fd, + strerror(errno)); +#endif + fio_sock_close(fd); + fd = -1; + continue; + } + break; + } + if (fd == -1) { + FIO_LOG_DEBUG("socket connection/creation error %s", strerror(errno)); + } + return fd; +} + +/** Returns 0 on timeout, -1 on error or the events that are valid. */ +SFUNC short fio_sock_wait_io(int fd, short events, int timeout) { + short r = 0; +#ifdef FIO_OS_WIN + if (fd == -1) { + FIO_THREAD_WAIT((timeout * 1000000)); + return r; + } +#endif + struct pollfd pfd = {.fd = fd, .events = events}; + r = (short)poll(&pfd, 1, timeout); + if (r == 1) + r = pfd.revents; + return r; +} + +/** Attempts to maximize the allowed open file limits. returns known limit */ +SFUNC size_t fio_sock_maximize_limits(size_t max_limit) { + ssize_t capa = 0; + if (!max_limit) + max_limit = FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT; +#if FIO_OS_POSIX + +#ifdef _SC_OPEN_MAX + capa = sysconf(_SC_OPEN_MAX); +#elif defined(FOPEN_MAX) + capa = FOPEN_MAX; +#endif + // try to maximize limits - collect max and set to max + struct rlimit rlim = {.rlim_max = 0}; + if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) { + FIO_LOG_WARNING("`getrlimit` failed (%d): %s", errno, strerror(errno)); + return capa; + } + + FIO_LOG_DEBUG2("existing / maximum open file limit detected: %zd / %zd", + (ssize_t)rlim.rlim_cur, + (ssize_t)rlim.rlim_max); + + if (rlim.rlim_cur >= max_limit) { + FIO_LOG_DEBUG2("open file limit can't be maximized any further (%zd / %zu)", + (ssize_t)rlim.rlim_cur, + max_limit); + return rlim.rlim_cur; + } + + rlim_t original = rlim.rlim_cur; + rlim.rlim_cur = rlim.rlim_max > max_limit ? max_limit : rlim.rlim_max; + while (setrlimit(RLIMIT_NOFILE, &rlim) == -1 && rlim.rlim_cur > original) + rlim.rlim_cur >>= 1; + + FIO_LOG_DEBUG2("new open file limit: %zd", (ssize_t)rlim.rlim_cur); + + getrlimit(RLIMIT_NOFILE, &rlim); + capa = rlim.rlim_cur; +#elif FIO_OS_WIN + capa = 1ULL << 10; + while (_setmaxstdio(capa) > 0) + capa <<= 1; + capa >>= 1; + FIO_LOG_DEBUG("new open file limit: %zd", (ssize_t)capa); +#else + FIO_LOG_ERROR("No OS detected, couldn't maximize open file limit."); +#endif + return capa; +} + +#ifdef AF_UNIX +/** Creates a new Unix socket and binds it to a local address. */ +SFUNC int fio_sock_open_unix(const char *address, uint16_t flags) { + /* Unix socket */ + struct sockaddr_un addr = {0}; + size_t addr_len = strlen(address); + if (addr_len >= sizeof(addr.sun_path)) { + FIO_LOG_ERROR( + "(fio_sock_open_unix) address too long (%zu bytes > %zu bytes).", + addr_len, + sizeof(addr.sun_path) - 1); + errno = ENAMETOOLONG; + return -1; + } + addr.sun_family = AF_UNIX; + FIO_MEMCPY(addr.sun_path, address, addr_len + 1); /* copy the NUL byte. */ +#if defined(__APPLE__) + addr.sun_len = addr_len; +#endif + int fd = + socket(AF_UNIX, (flags & FIO_SOCK_UDP) ? SOCK_DGRAM : SOCK_STREAM, 0); + if (fd == -1) { + FIO_LOG_DEBUG("couldn't open unix socket (flags == %d) %s", + (int)flags, + strerror(errno)); + return -1; + } + if ((flags & FIO_SOCK_NONBLOCK) && fio_sock_set_non_block(fd) == -1) { + FIO_LOG_DEBUG("couldn't set socket to non-blocking mode"); + fio_sock_close(fd); + unlink(addr.sun_path); + return -1; + } + if ((flags & FIO_SOCK_CLIENT)) { + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1 && + errno != EINPROGRESS) { + FIO_LOG_DEBUG("couldn't connect unix client @ %s : %s", + addr.sun_path, + strerror(errno)); + fio_sock_close(fd); + return -1; + } + } else { + unlink(addr.sun_path); + int btmp; // the bind result +#if !defined(FIO_SOCK_AVOID_UMASK) && !defined(FIO_OS_WIN) + if ((flags & FIO_SOCK_UNIX_PRIVATE) == FIO_SOCK_UNIX) { + int umask_org = umask(0x1FF); + btmp = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + int old_err = errno; + umask(umask_org); + errno = old_err; + FIO_LOG_DEBUG("umask was used temporarily for Unix Socket (was 0x%04X)", + umask_org); + } else +#endif /* FIO_SOCK_AVOID_UMASK */ + /* else */ btmp = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (btmp == -1) { + FIO_LOG_DEBUG("couldn't bind unix socket to %s\n\terrno(%d): %s", + address, + errno, + strerror(errno)); + fio_sock_close(fd); + // unlink(addr.sun_path); + return -1; + } +#ifndef FIO_OS_WIN + if ((flags & FIO_SOCK_UNIX_PRIVATE) == FIO_SOCK_UNIX) { + chmod(address, S_IRWXO | S_IRWXG | S_IRWXU); + fchmod(fd, S_IRWXO | S_IRWXG | S_IRWXU); + } +#endif + if (!(flags & FIO_SOCK_UDP) && listen(fd, SOMAXCONN) < 0) { + FIO_LOG_DEBUG("couldn't start listening to unix socket at %s", address); + fio_sock_close(fd); + unlink(addr.sun_path); + return -1; + } + } + return fd; +} +#else +SFUNC int fio_sock_open_unix(const char *address, uint16_t flags) { + (void)address, (void)flags; + FIO_ASSERT(0, "this system does not support Unix sockets."); +} +#endif /* AF_UNIX */ + +/* ***************************************************************************** +Peer Address +***************************************************************************** */ + +/** + * Returns a human readable address representation of the socket's peer address. + * + * On error, returns a NULL buffer with zero length. + * + * Buffer lengths are limited to 63 bytes. + * + * This function is limited in its thread safety to 128 threads / calls. + */ +SFUNC fio_buf_info_s fio_sock_peer_addr(int s) { + static char buffer[8129]; /* 64 byte per buffer x 128 threads */ + static unsigned pos = 0; + fio_buf_info_s r = + FIO_BUF_INFO2(buffer + (fio_atomic_add(&pos, 63) & 127), 0); + struct sockaddr addr[8] = {0}; + socklen_t len = sizeof(addr); + if (!FIO_SOCK_FD_ISVALID(s)) + goto finish; + if (getpeername(s, addr, &len)) + goto finish; + if (getnameinfo(addr, len, r.buf, 64, NULL, 0, NI_NUMERICHOST)) + goto finish; + r.len = FIO_STRLEN(r.buf); +finish: + if (!r.len) + r.buf = NULL; + return r; +} + +/* ***************************************************************************** +WinSock initialization +***************************************************************************** */ +#if FIO_OS_WIN +static WSADATA fio___sock_useless_windows_data; +FIO_CONSTRUCTOR(fio___sock_win_init) { + static uint8_t flag = 0; + if (!flag) { + flag |= 1; + if (WSAStartup(MAKEWORD(2, 2), &fio___sock_useless_windows_data)) { + FIO_LOG_FATAL("WinSock2 unavailable."); + exit(-1); + } + atexit((void (*)(void))(WSACleanup)); + } +} +#endif /* FIO_OS_WIN / FIO_OS_POSIX */ + +/* ***************************************************************************** +FIO_SOCK - cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_SOCK +#endif +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_STATE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + State Callback Management API + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_STATE) && !defined(H___FIO_STATE___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_STATE___H +/* ***************************************************************************** +State Callback API +***************************************************************************** */ + +/* ***************************************************************************** +Startup / State Callbacks (fork, start up, idle, etc') +***************************************************************************** */ + +/** a callback type enum */ +typedef enum { + /** Called once during library initialization. */ + FIO_CALL_ON_INITIALIZE, + /** Called once before starting up the IO reactor. */ + FIO_CALL_PRE_START, + /** Called before each time the IO reactor forks a new worker. */ + FIO_CALL_BEFORE_FORK, + /** Called after each fork (both parent and child), before FIO_CALL_IN_XXX */ + FIO_CALL_AFTER_FORK, + /** Called by a worker process right after forking. */ + FIO_CALL_IN_CHILD, + /** Called by the master process after spawning a worker (after forking). */ + FIO_CALL_IN_MASTER, + /** Called by each worker thread in a Server Async queue as it starts. */ + FIO_CALL_ON_WORKER_THREAD_START, + /** Called every time a *Worker* process starts. */ + FIO_CALL_ON_START, + /** Reserved for internal use. */ + FIO_CALL_RESERVED1, + /** Reserved for internal use. */ + FIO_CALL_RESERVED2, + /** User state event queue (unused, available for the user). */ + FIO_CALL_ON_USER1, + /** User state event queue (unused, available for the user). */ + FIO_CALL_ON_USER2, + /** Called when facil.io enters idling mode. */ + FIO_CALL_ON_IDLE, + /** A reversed user state event queue (unused, available for the user). */ + FIO_CALL_ON_USER1_REVERSE, + /** A reversed user state event queue (unused, available for the user). */ + FIO_CALL_ON_USER2_REVERSE, + /** Reserved for internal use. */ + FIO_CALL_RESERVED1_REVERSED, + /** Reserved for internal use. */ + FIO_CALL_RESERVED2_REVERSED, + /** Called before starting the shutdown sequence. */ + FIO_CALL_ON_SHUTDOWN, + /** Called by each worker the moment it detects the master process crashed. */ + FIO_CALL_ON_PARENT_CRUSH, + /** Called by the parent (master) after a worker process crashed. */ + FIO_CALL_ON_CHILD_CRUSH, + /** Called by each worker thread in a Server Async queue as it ends. */ + FIO_CALL_ON_WORKER_THREAD_END, + /** Called when wither a *Worker* or *Master* stopped. */ + FIO_CALL_ON_STOP, + /** An alternative to the system's at_exit. */ + FIO_CALL_AT_EXIT, + /** used for testing and array allocation - must be last. */ + FIO_CALL_NEVER +} fio_state_event_type_e; + +/** Adds a callback to the list of callbacks to be called for the event. */ +SFUNC void fio_state_callback_add(fio_state_event_type_e, + void (*func)(void *), + void *arg); + +/** Removes a callback from the list of callbacks to be called for the event. */ +SFUNC int fio_state_callback_remove(fio_state_event_type_e, + void (*func)(void *), + void *arg); + +/** + * Forces all the existing callbacks to run, as if the event occurred. + * + * Callbacks for all initialization / idling tasks are called in order of + * creation (where fio_state_event_type_e <= FIO_CALL_ON_IDLE). + * + * Callbacks for all cleanup oriented tasks are called in reverse order of + * creation (where fio_state_event_type_e >= FIO_CALL_ON_SHUTDOWN). + * + * During an event, changes to the callback list are ignored (callbacks can't + * add or remove other callbacks for the same event). + */ +SFUNC void fio_state_callback_force(fio_state_event_type_e); + +/* ***************************************************************************** +State Callback Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +State Callback Map - I'd use the global mapping types... +(Ordered Hash Map) but we can't depend on types yet, possible collisions. +***************************************************************************** */ + +typedef struct { + void (*func)(void *); + void *arg; +} fio___state_task_s; + +FIO_IFUNC uint64_t fio___state_callback_hash_fn(fio___state_task_s *t) { + uint64_t hash = fio_risky_ptr((void *)(uintptr_t)(t->func)); + hash ^= hash + fio_risky_ptr(t->arg); + return hash; +} + +#define FIO_STATE_CALLBACK_IS_VALID(pobj) ((pobj)->func) +#define FIO_STATE_CALLBACK_CMP(a, b) \ + ((a)->func == (b)->func && (a)->arg == (b)->arg) +FIO_TYPEDEF_IMAP_ARRAY(fio___state_map, + fio___state_task_s, + uint32_t, + fio___state_callback_hash_fn, + FIO_STATE_CALLBACK_CMP, + FIO_STATE_CALLBACK_IS_VALID) +#undef FIO_STATE_CALLBACK_CMP +#undef FIO_STATE_CALLBACK_IS_VALID + +/* ***************************************************************************** +Names. +***************************************************************************** */ + +static const char *FIO___STATE_TASKS_NAMES[FIO_CALL_NEVER + 1] = { + [FIO_CALL_ON_INITIALIZE] = "ON_INITIALIZE", + [FIO_CALL_PRE_START] = "PRE_START", + [FIO_CALL_BEFORE_FORK] = "BEFORE_FORK", + [FIO_CALL_AFTER_FORK] = "AFTER_FORK", + [FIO_CALL_IN_CHILD] = "IN_CHILD", + [FIO_CALL_IN_MASTER] = "IN_MASTER", + [FIO_CALL_ON_WORKER_THREAD_START] = "ON_WORKER_THREAD_START", + [FIO_CALL_ON_START] = "ON_START", + [FIO_CALL_RESERVED1] = "RESERVED1", + [FIO_CALL_RESERVED2] = "RESERVED2", + [FIO_CALL_ON_USER1] = "ON_USER1", + [FIO_CALL_ON_USER2] = "ON_USER2", + [FIO_CALL_ON_IDLE] = "ON_IDLE", + [FIO_CALL_ON_USER1_REVERSE] = "ON_USER1_REVERSE", + [FIO_CALL_ON_USER2_REVERSE] = "ON_USER2_REVERSE", + [FIO_CALL_RESERVED1_REVERSED] = "RESERVED1_REVERSED", + [FIO_CALL_RESERVED2_REVERSED] = "RESERVED2_REVERSED", + [FIO_CALL_ON_SHUTDOWN] = "ON_SHUTDOWN", + [FIO_CALL_ON_PARENT_CRUSH] = "ON_PARENT_CRUSH", + [FIO_CALL_ON_CHILD_CRUSH] = "ON_CHILD_CRUSH", + [FIO_CALL_ON_WORKER_THREAD_END] = "ON_WORKER_THREAD_END", + [FIO_CALL_ON_STOP] = "ON_FINISH", + [FIO_CALL_AT_EXIT] = "AT_EXIT", + [FIO_CALL_NEVER] = "NEVER", +}; + +/* ***************************************************************************** +State Callback Global State and Locks +***************************************************************************** */ +/* use `weak` instead of `static` to make sure state callbacks are global. */ +FIO_WEAK fio___state_map_s FIO___STATE_TASKS_ARRAY[FIO_CALL_NEVER + 1]; +FIO_WEAK fio_lock_i FIO___STATE_TASKS_ARRAY_LOCK[FIO_CALL_NEVER + 1]; + +FIO_IFUNC void fio_state_callback_clear_all(void) { + for (size_t i = 0; i < FIO_CALL_NEVER; ++i) { + fio___state_map_destroy(FIO___STATE_TASKS_ARRAY + i); + } + FIO_LOG_DEBUG2("(%d) fio_state_callback maps have been cleared.", + fio_getpid()); +} + +/** Adds a callback to the list of callbacks to be called for the event. */ +SFUNC void fio_state_callback_add(fio_state_event_type_e e, + void (*func)(void *), + void *arg) { + if ((uintptr_t)e >= FIO_CALL_NEVER) + return; + fio___state_task_s t = {.func = func, .arg = arg}; + fio_lock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + fio___state_map_set(FIO___STATE_TASKS_ARRAY + (uintptr_t)e, t, 0); + fio_unlock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + if (e == FIO_CALL_ON_INITIALIZE && + FIO___STATE_TASKS_ARRAY_LOCK[FIO_CALL_NEVER]) { + /* initialization tasks already performed, perform this without delay */ + func(arg); + } +} + +/** Removes a callback from the list of callbacks to be called for the event. */ +SFUNC int fio_state_callback_remove(fio_state_event_type_e e, + void (*func)(void *), + void *arg) { + if ((uintptr_t)e >= FIO_CALL_NEVER) + return -1; + int ret; + fio___state_task_s t = {.func = func, .arg = arg}; + fio_lock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + ret = fio___state_map_remove(FIO___STATE_TASKS_ARRAY + (uintptr_t)e, t); + fio_unlock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + return ret; +} + +/** Clears all the existing callbacks for the event. */ +SFUNC void fio_state_callback_clear(fio_state_event_type_e e) { + if ((uintptr_t)e >= FIO_CALL_NEVER) + return; + fio_lock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + fio___state_map_destroy(FIO___STATE_TASKS_ARRAY + (uintptr_t)e); + fio_unlock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); +} + +FIO_SFUNC void fio_state_callback_force___task(void *fn_p, void *arg) { + union { + void *p; + void (*fn)(void *); + } u = {.p = fn_p}; + u.fn(arg); +} +/** + * Forces all the existing callbacks to run, as if the event occurred. + * + * Callbacks are called from last to first (last callback executes first). + * + * During an event, changes to the callback list are ignored (callbacks can't + * remove other callbacks for the same event). + */ +SFUNC void fio_state_callback_force(fio_state_event_type_e e) { + /** a type-to-string map for callback types */ + + if ((uintptr_t)e >= FIO_CALL_NEVER) + return; + if (e == FIO_CALL_AFTER_FORK) { + /* make sure the `after_fork` events re-initializes all locks. */ + for (size_t i = 0; i < FIO_CALL_NEVER; ++i) { + FIO___STATE_TASKS_ARRAY_LOCK[i] = FIO_LOCK_INIT; + } + } + if (e == FIO_CALL_IN_CHILD) + fio_rand_reseed(); /* re-seed random state in child processes */ + fio___state_task_s *ary = NULL; + size_t ary_capa = (sizeof(*ary) * FIO___STATE_TASKS_ARRAY[e].count); + size_t len = 0; + if (e == FIO_CALL_ON_INITIALIZE) { + fio_trylock(FIO___STATE_TASKS_ARRAY_LOCK + FIO_CALL_NEVER); + } + + FIO_LOG_DDEBUG2("(%d) scheduling %s callbacks (%zu tasks).", + (int)(fio_thread_getpid()), + FIO___STATE_TASKS_NAMES[e], + (size_t)FIO___STATE_TASKS_ARRAY[e].count); + if (!FIO___STATE_TASKS_ARRAY[e].count) + return; + /* copy task queue */ + fio_lock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + if (FIO___STATE_TASKS_ARRAY[e].w) { + ary = (fio___state_task_s *)FIO_MEM_REALLOC(NULL, 0, ary_capa, 0); + FIO_ASSERT_ALLOC(ary); + for (size_t i = 0; i < FIO___STATE_TASKS_ARRAY[e].w; ++i) { + if (!FIO___STATE_TASKS_ARRAY[e].ary[i].func) + continue; + ary[len++] = FIO___STATE_TASKS_ARRAY[e].ary[i]; + } + } + fio_unlock(FIO___STATE_TASKS_ARRAY_LOCK + (uintptr_t)e); + + if (e <= FIO_CALL_PRE_START) { + /* perform copied tasks immediately within system thread */ + for (size_t i = 0; i < len; ++i) + ary[i].func(ary[i].arg); + } else if (e <= FIO_CALL_ON_IDLE) { + /* perform tasks in order */ + for (size_t i = 0; i < len; ++i) + ary[i].func(ary[i].arg); + } else { + /* perform tasks in reverse */ + while (len--) + ary[len].func(ary[len].arg); + } + /* cleanup */ + FIO_MEM_FREE(ary, ary_capa); + (void)FIO___STATE_TASKS_NAMES; /* if unused */ +} + +/* ***************************************************************************** +Debug Helpers +***************************************************************************** */ + +FIO_IFUNC void fio_state_callback_print_state(void) { + FIO_LOG2STDERR("DEBUG: fio_state_callback maps state:"); + for (size_t i = 0; i < FIO_CALL_NEVER; ++i) { + fprintf(stderr, + "\t%-32s %-4zu out of %-4zu\n", + FIO___STATE_TASKS_NAMES[i], + (size_t)FIO___STATE_TASKS_ARRAY[i].count, + fio___state_map_capa(FIO___STATE_TASKS_ARRAY + i)); + } +} + +/* ***************************************************************************** +State constructor / destructor +***************************************************************************** */ + +FIO_SFUNC void fio___state_cleanup_task_at_exit(void *ignr_) { + fio_state_callback_clear_all(); + (void)ignr_; +} + +FIO_CONSTRUCTOR(fio___state_constructor) { + FIO_LOG_DEBUG2("fio_state_callback maps are now active."); + fio_state_callback_force(FIO_CALL_ON_INITIALIZE); + fio_state_callback_add(FIO_CALL_AT_EXIT, + fio___state_cleanup_task_at_exit, + NULL); +} + +FIO_DESTRUCTOR(fio___state_at_exit_hook) { + fio_state_callback_force(FIO_CALL_AT_EXIT); +} + +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_STATE +#endif /* FIO_STATE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TIME /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Time Helpers + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TIME) && !defined(H___FIO_TIME___H) +#define H___FIO_TIME___H + +/* ***************************************************************************** +Collecting Monotonic / Real Time +***************************************************************************** */ + +/** Returns human (watch) time... this value isn't as safe for measurements. */ +FIO_IFUNC struct timespec fio_time_real(void); + +/** Returns monotonic time. */ +FIO_IFUNC struct timespec fio_time_mono(void); + +/** Returns monotonic time in nano-seconds (now in 1 billionth of a second). */ +FIO_IFUNC int64_t fio_time_nano(void); + +/** Returns monotonic time in micro-seconds (now in 1 millionth of a second). */ +FIO_IFUNC int64_t fio_time_micro(void); + +/** Returns monotonic time in milliseconds. */ +FIO_IFUNC int64_t fio_time_milli(void); + +/** Converts a `struct timespec` to milliseconds. */ +FIO_IFUNC int64_t fio_time2milli(struct timespec); + +/** Converts a `struct timespec` to microseconds. */ +FIO_IFUNC int64_t fio_time2micro(struct timespec); + +/** + * A faster (yet less localized) alternative to `gmtime_r`. + * + * See the libc `gmtime_r` documentation for details. + * + * Falls back to `gmtime_r` for dates before epoch. + */ +SFUNC struct tm fio_time2gm(time_t time); + +/** Converts a `struct tm` to time in seconds (assuming UTC). */ +SFUNC time_t fio_gm2time(struct tm tm); + +/** + * Writes an RFC 7231 date representation (HTTP date format) to target. + * + * Usually requires 29 characters, although this may vary. + */ +SFUNC size_t fio_time2rfc7231(char *target, time_t time); + +/** + * Writes an RFC 2109 date representation to target (HTTP Cookie Format). + * + * Usually requires 31 characters, although this may vary. + */ +SFUNC size_t fio_time2rfc2109(char *target, time_t time); + +/** + * Writes an RFC 2822 date representation to target (Internet Message Format). + * + * Usually requires 28 to 29 characters, although this may vary. + */ +SFUNC size_t fio_time2rfc2822(char *target, time_t time); + +/** + * Writes a date representation to target in common log format. i.e., + * + * [DD/MMM/yyyy:hh:mm:ss +0000] + * + * Usually requires 29 characters (including square brackets and NUL). + */ +SFUNC size_t fio_time2log(char *target, time_t time); + +/** + * Writes a date representation to target in ISO 8601 format. i.e., + * + * YYYY-MM-DD HH:MM:SS + * + * Usually requires 20 characters (including NUL). + */ +SFUNC size_t fio_time2iso(char *target, time_t time); + +/** Adds two `struct timespec` objects. */ +FIO_IFUNC struct timespec fio_time_add(struct timespec t, struct timespec t2); + +/** Adds milliseconds to a `struct timespec` object. */ +FIO_IFUNC struct timespec fio_time_add_milli(struct timespec t, int64_t milli); + +/** Compares two `struct timespec` objects. */ +FIO_IFUNC int fio_time_cmp(struct timespec t1, struct timespec t2); + +/* ***************************************************************************** +Time Inline Helpers +***************************************************************************** */ + +/** Returns human (watch) time... this value isn't as safe for measurements. */ +FIO_IFUNC struct timespec fio_time_real(void) { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return t; +} + +/** Returns monotonic time. */ +FIO_IFUNC struct timespec fio_time_mono(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t; +} + +/** Returns monotonic time in nano-seconds (now in 1 micro of a second). */ +FIO_IFUNC int64_t fio_time_nano(void) { + struct timespec t = fio_time_mono(); + return ((int64_t)t.tv_sec * 1000000000) + (int64_t)t.tv_nsec; +} + +/** Returns monotonic time in micro-seconds (now in 1 millionth of a second). */ +FIO_IFUNC int64_t fio_time_micro(void) { + struct timespec t = fio_time_mono(); + return ((int64_t)t.tv_sec * 1000000) + (int64_t)t.tv_nsec / 1000; +} + +/** Returns monotonic time in milliseconds. */ +FIO_IFUNC int64_t fio_time_milli(void) { + return fio_time2milli(fio_time_mono()); +} + +/** Converts a `struct timespec` to milliseconds. */ +FIO_IFUNC int64_t fio_time2milli(struct timespec t) { + return ((int64_t)t.tv_sec * 1000) + (int64_t)t.tv_nsec / 1000000; +} + +/** Converts a `struct timespec` to microseconds. */ +FIO_IFUNC int64_t fio_time2micro(struct timespec t) { + return ((int64_t)t.tv_sec * 1000000) + (int64_t)t.tv_nsec / 1000; +} + +/* Normalizes a timespec struct after an `add` or `sub` operation. */ +FIO_IFUNC void fio_time___normalize(struct timespec *t) { + const long ns_norm[2] = {0, 1000000000LL}; + t->tv_nsec += ns_norm[(t->tv_nsec < 0)]; + t->tv_sec += (t->tv_nsec < 0); + t->tv_nsec -= ns_norm[(1000000000LL < t->tv_nsec)]; + t->tv_sec += (1000000000LL < t->tv_nsec); +} + +/** Adds to timespec. */ +FIO_IFUNC struct timespec fio_time_add(struct timespec t, struct timespec t2) { + t.tv_sec += t2.tv_sec; + t.tv_nsec += t2.tv_nsec; + fio_time___normalize(&t); + return t; +} + +/** Adds milliseconds to timespec. */ +FIO_IFUNC struct timespec fio_time_add_milli(struct timespec t, int64_t milli) { + t.tv_sec += milli >> 10; /* 1024 is close enough, will be normalized */ + t.tv_nsec += (milli & 1023) * 1000000; + fio_time___normalize(&t); + return t; +} + +/** Compares two timespecs. */ +FIO_IFUNC int fio_time_cmp(struct timespec t1, struct timespec t2) { + size_t a = (t2.tv_sec < t1.tv_sec) << 1; + a |= (t2.tv_nsec < t1.tv_nsec); + size_t b = (t1.tv_sec < t2.tv_sec) << 1; + b |= (t1.tv_nsec < t2.tv_nsec); + return (0 - (a < b)) + (b < a); +} + +/* ***************************************************************************** +Time Implementation +***************************************************************************** */ +#if !defined(FIO_EXTERN) || defined(FIO_EXTERN_COMPLETE) + +/** + * A faster (yet less localized) alternative to `gmtime_r`. + * + * See the libc `gmtime_r` documentation for details. + * + * Falls back to `gmtime_r` for dates before epoch. + */ +SFUNC struct tm fio_time2gm(time_t timer) { + struct tm tm; + ssize_t a, b; +#if HAVE_TM_TM_ZONE || defined(BSD) + tm = (struct tm){ + .tm_isdst = 0, + .tm_zone = (char *)"UTC", + }; +#else + tm = (struct tm){ + .tm_isdst = 0, + }; +#endif + + // convert seconds from epoch to days from epoch + extract data + if (timer >= 0) { + // for seconds up to weekdays, we reduce the reminder every step. + a = (ssize_t)timer; + b = a / 60; // b == time in minutes + tm.tm_sec = (int)(a - (b * 60)); + a = b / 60; // b == time in hours + tm.tm_min = (int)(b - (a * 60)); + b = a / 24; // b == time in days since epoch + tm.tm_hour = (int)(a - (b * 24)); + // b == number of days since epoch + // day of epoch was a thursday. Add + 4 so sunday == 0... + tm.tm_wday = (b + 4) % 7; + } else { + // for seconds up to weekdays, we reduce the reminder every step. + a = (ssize_t)timer; + b = a / 60; // b == time in minutes + if (b * 60 != a) { + /* seconds passed */ + tm.tm_sec = (int)((a - (b * 60)) + 60); + --b; + } else { + /* no seconds */ + tm.tm_sec = 0; + } + a = b / 60; // b == time in hours + if (a * 60 != b) { + /* minutes passed */ + tm.tm_min = (int)((b - (a * 60)) + 60); + --a; + } else { + /* no minutes */ + tm.tm_min = 0; + } + b = a / 24; // b == time in days since epoch? + if (b * 24 != a) { + /* hours passed */ + tm.tm_hour = (int)((a - (b * 24)) + 24); + --b; + } else { + /* no hours */ + tm.tm_hour = 0; + } + // day of epoch was a thursday. Add + 4 so sunday == 0... + tm.tm_wday = ((b - 3) % 7); + if (tm.tm_wday) + tm.tm_wday += 7; + /* b == days from epoch */ + } + + // at this point we can apply the algorithm described here: + // http://howardhinnant.github.io/date_algorithms.html#civil_from_days + // Credit to Howard Hinnant. + { + b += 719468L; // adjust to March 1st, 2000 (post leap of 400 year era) + // 146,097 = days in era (400 years) + const size_t era = (b >= 0 ? b : b - 146096) / 146097; + const uint32_t doe = (uint32_t)(b - (era * 146097)); // day of era + const uint16_t yoe = + (uint16_t)((doe - doe / 1460 + doe / 36524 - doe / 146096) / + 365); // year of era + a = yoe; + a += era * 400; // a == year number, assuming year starts on March 1st... + const uint16_t doy = (uint16_t)(doe - (365 * yoe + yoe / 4 - yoe / 100)); + const uint16_t mp = (uint16_t)((5U * doy + 2) / 153); + const uint16_t d = (uint16_t)(doy - (153U * mp + 2) / 5 + 1); + const uint8_t m = (uint8_t)(mp + (mp < 10 ? 2 : -10)); + a += (m <= 1); + tm.tm_year = (int)(a - 1900); // tm_year == years since 1900 + tm.tm_mon = m; + tm.tm_mday = d; + const uint8_t is_leap = (a % 4 == 0 && (a % 100 != 0 || a % 400 == 0)); + tm.tm_yday = (doy + (is_leap) + 28 + 31) % (365 + is_leap); + } + + return tm; +} + +/** Converts a `struct tm` to time in seconds (assuming UTC). */ +SFUNC time_t fio_gm2time(struct tm tm) { + int64_t time = 0; + // we start with the algorithm described here: + // http://howardhinnant.github.io/date_algorithms.html#days_from_civil + // Credit to Howard Hinnant. + { + const int32_t y = (tm.tm_year + 1900) - (tm.tm_mon < 2); + const int32_t era = (y >= 0 ? y : y - 399) / 400; + const uint16_t yoe = (y - era * 400L); // 0-399 + const uint32_t doy = + (153L * (tm.tm_mon + (tm.tm_mon > 1 ? -2 : 10)) + 2) / 5 + tm.tm_mday - + 1; // 0-365 + const uint32_t doe = yoe * 365L + yoe / 4 - yoe / 100 + doy; // 0-146096 + time = era * 146097LL + doe - 719468LL; // time == days from epoch + } + + /* Adjust for hour, minute and second */ + time = time * 24LL + tm.tm_hour; + time = time * 60LL + tm.tm_min; + time = time * 60LL + tm.tm_sec; + + if (tm.tm_isdst > 0) { + time -= 60 * 60; + } +#if HAVE_TM_TM_ZONE || defined(BSD) + if (tm.tm_gmtoff) { + time += tm.tm_gmtoff; + } +#endif + return (time_t)time; +} + +FIO_SFUNC char *fio_time_write_day(char *dest, const struct tm *tm) { + static const char *FIO___DAY_NAMES[] = + {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + dest[0] = FIO___DAY_NAMES[tm->tm_wday][0]; + dest[1] = FIO___DAY_NAMES[tm->tm_wday][1]; + dest[2] = FIO___DAY_NAMES[tm->tm_wday][2]; + return dest + 3; +} + +FIO_SFUNC char *fio_time_write_month(char *dest, const struct tm *tm) { + // clang-format off + static const char *FIO___MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + // clang-format on + dest[0] = FIO___MONTH_NAMES[tm->tm_mon][0]; + dest[1] = FIO___MONTH_NAMES[tm->tm_mon][1]; + dest[2] = FIO___MONTH_NAMES[tm->tm_mon][2]; + return dest + 3; +} + +FIO_SFUNC char *fio_time_write_year(char *dest, const struct tm *tm) { + int64_t year = tm->tm_year + 1900; + const size_t digits = fio_digits10(year); + fio_ltoa10(dest, year, digits); + return dest + digits; +} + +/** Writes an RFC 7231 date representation (HTTP date format) to target. */ +SFUNC size_t fio_time2rfc7231(char *target, time_t time) { + const struct tm tm = fio_time2gm(time); + /* note: day of month is always 2 digits */ + char *pos = target; + uint16_t tmp; + pos = fio_time_write_day(pos, &tm); + *pos++ = ','; + *pos++ = ' '; + tmp = tm.tm_mday / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_mday - (tmp * 10)); + *pos++ = ' '; + pos = fio_time_write_month(pos, &tm); + *pos++ = ' '; + pos = fio_time_write_year(pos, &tm); + *pos++ = ' '; + tmp = tm.tm_hour / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_hour - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_min / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_min - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_sec / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_sec - (tmp * 10)); + *pos++ = ' '; + *pos++ = 'G'; + *pos++ = 'M'; + *pos++ = 'T'; + *pos = 0; + return pos - target; +} +/** Writes an RFC 2109 date representation to target. */ +SFUNC size_t fio_time2rfc2109(char *target, time_t time) { + const struct tm tm = fio_time2gm(time); + /* note: day of month is always 2 digits */ + char *pos = target; + uint16_t tmp; + pos = fio_time_write_day(pos, &tm); + *pos++ = ','; + *pos++ = ' '; + tmp = tm.tm_mday / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_mday - (tmp * 10)); + *pos++ = ' '; + pos = fio_time_write_month(pos, &tm); + *pos++ = ' '; + pos = fio_time_write_year(pos, &tm); + *pos++ = ' '; + tmp = tm.tm_hour / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_hour - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_min / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_min - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_sec / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_sec - (tmp * 10)); + *pos++ = ' '; + *pos++ = '-'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = '0'; + *pos = 0; + return pos - target; +} + +/** Writes an RFC 2822 date representation to target. */ +SFUNC size_t fio_time2rfc2822(char *target, time_t time) { + const struct tm tm = fio_time2gm(time); + /* note: day of month is either 1 or 2 digits */ + char *pos = target; + uint16_t tmp; + pos = fio_time_write_day(pos, &tm); + *pos++ = ','; + *pos++ = ' '; + if (tm.tm_mday < 10) { + *pos++ = '0' + tm.tm_mday; + } else { + tmp = tm.tm_mday / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_mday - (tmp * 10)); + } + *pos++ = '-'; + pos = fio_time_write_month(pos, &tm); + *pos++ = '-'; + pos = fio_time_write_year(pos, &tm); + *pos++ = ' '; + tmp = tm.tm_hour / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_hour - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_min / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_min - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_sec / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_sec - (tmp * 10)); + *pos++ = ' '; + *pos++ = 'G'; + *pos++ = 'M'; + *pos++ = 'T'; + *pos = 0; + return pos - target; +} + +/** + * Writes a date representation to target in common log format. i.e., + * + * [DD/MMM/yyyy:hh:mm:ss +0000] + * + * Usually requires 29 characters (including square brackets and NUL). + */ +SFUNC size_t fio_time2log(char *target, time_t time) { + { + const struct tm tm = fio_time2gm(time); + /* note: day of month is either 1 or 2 digits */ + char *pos = target; + uint16_t tmp; + *pos++ = '['; + tmp = tm.tm_mday / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_mday - (tmp * 10)); + *pos++ = '/'; + pos = fio_time_write_month(pos, &tm); + *pos++ = '/'; + pos = fio_time_write_year(pos, &tm); + *pos++ = ':'; + tmp = tm.tm_hour / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_hour - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_min / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_min - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_sec / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_sec - (tmp * 10)); + *pos++ = ' '; + *pos++ = '+'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = '0'; + *pos++ = ']'; + *(pos) = 0; + return pos - target; + } +} + +/** + * Writes a date representation to target in ISO 8601 format. i.e., + * + * YYYY-MM-DD HH:MM:SS + * + * Usually requires 20 characters (including NUL). + */ +SFUNC size_t fio_time2iso(char *target, time_t time) { + { + const struct tm tm = fio_time2gm(time); + /* note: day of month is either 1 or 2 digits */ + char *pos = target; + uint16_t tmp; + pos = fio_time_write_year(pos, &tm); + *pos++ = '-'; + pos = fio_time_write_month(pos, &tm); + *pos++ = '-'; + tmp = tm.tm_mday / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_mday - (tmp * 10)); + *pos++ = ' '; + tmp = tm.tm_hour / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_hour - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_min / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_min - (tmp * 10)); + *pos++ = ':'; + tmp = tm.tm_sec / 10; + *pos++ = '0' + tmp; + *pos++ = '0' + (tm.tm_sec - (tmp * 10)); + *(pos) = 0; + return pos - target; + } +} +/* ***************************************************************************** +Time Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_TIME +#endif /* FIO_TIME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_CLI /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + CLI helpers - command line interface parsing + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_CLI) && !defined(H___FIO_CLI___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_CLI___H 1 + +/* ***************************************************************************** +Internal Macro Implementation +***************************************************************************** */ + +/** Used internally. */ +typedef enum { + /** A String CLI argument */ + FIO_CLI_ARG_STRING, + /** A Boolean CLI argument */ + FIO_CLI_ARG_BOOL, + /** An integer CLI argument */ + FIO_CLI_ARG_INT, + FIO_CLI_ARG_PRINT, + FIO_CLI_ARG_PRINT_LINE, + FIO_CLI_ARG_PRINT_HEADER, +} fio_cli_arg_e; + +#define FIO_CLI_ARG_NONE FIO_CLI_ARG_PRINT_HEADER + +typedef struct { + fio_cli_arg_e t; + const char *l; +} fio___cli_line_s; + +/** Indicates the CLI argument should be a String (default). */ +#define FIO_CLI_STRING(line) \ + ((fio___cli_line_s){.t = FIO_CLI_ARG_STRING, .l = line}) +/** Indicates the CLI argument is a Boolean value. */ +#define FIO_CLI_BOOL(line) \ + ((fio___cli_line_s){.t = FIO_CLI_ARG_BOOL, .l = line}) +/** Indicates the CLI argument should be an Integer (numerical). */ +#define FIO_CLI_INT(line) ((fio___cli_line_s){.t = FIO_CLI_ARG_INT, .l = line}) +/** Indicates the CLI string should be printed as is with proper offset. */ +#define FIO_CLI_PRINT(line) \ + ((fio___cli_line_s){.t = FIO_CLI_ARG_PRINT, .l = line}) +/** Indicates the CLI string should be printed as is with no offset. */ +#define FIO_CLI_PRINT_LINE(line) \ + ((fio___cli_line_s){.t = FIO_CLI_ARG_PRINT_LINE, .l = line}) +/** Indicates the CLI string should be printed as a header. */ +#define FIO_CLI_PRINT_HEADER(line) \ + ((fio___cli_line_s){.t = FIO_CLI_ARG_PRINT_HEADER, .l = line}) + +/* ***************************************************************************** +CLI API +***************************************************************************** */ + +/** + * This function parses the Command Line Interface (CLI), creating a temporary + * "dictionary" that allows easy access to the CLI using their names or aliases. + * + * Command line arguments may be typed. If an optional type requirement is + * provided and the provided arument fails to match the required type, execution + * will end and an error message will be printed along with a short "help". + * + * The function / macro accepts the following arguments: + * - `argc`: command line argument count. + * - `argv`: command line argument list (array). + * - `unnamed_min`: the required minimum of un-named arguments. + * - `unnamed_max`: the maximum limit of un-named arguments. + * - `description`: a C string containing the program's description. + * - named arguments list: a list of C strings describing named arguments. + * + * The following optional type requirements are: + * + * * FIO_CLI_STRING(desc_line) - (default) string argument. + * * FIO_CLI_BOOL(desc_line) - boolean argument (no value). + * * FIO_CLI_INT(desc_line) - integer argument. + * * FIO_CLI_PRINT_HEADER(desc_line) - extra header for output. + * * FIO_CLI_PRINT(desc_line) - extra information for output. + * + * Argument names MUST start with the '-' character. The first word starting + * without the '-' character will begin the description for the CLI argument. + * + * The arguments "-?", "-h", "-help" and "--help" are automatically handled + * unless overridden. + * + * Un-named arguments shouldn't be listed in the named arguments list. + * + * Example use: + * + * fio_cli_start(argc, argv, 0, 0, "The NAME example accepts the following:", + * FIO_CLI_PRINT_HREADER("Concurrency:"), + * FIO_CLI_INT("-t -thread number of threads to run."), + * FIO_CLI_INT("-w -workers number of workers to run."), + * FIO_CLI_PRINT_HREADER("Address Binding:"), + * "-b, -address the address to bind to.", + * FIO_CLI_INT("-p,-port the port to bind to."), + * FIO_CLI_PRINT("\t\tset port to zero (0) for Unix s."), + * FIO_CLI_PRINT_HREADER("Logging:"), + * FIO_CLI_BOOL("-v -log enable logging.") + * ); + * + * + * This would allow access to the named arguments: + * + * fio_cli_get("-b") == fio_cli_get("-address"); + * + * + * Once all the data was accessed, free the parsed data dictionary using: + * + * fio_cli_end(); + * + * It should be noted, arguments will be recognized in a number of forms, i.e.: + * + * app -t=1 -p3000 -a localhost + * + * This function is NOT thread safe. + */ +#define fio_cli_start(argc, argv, unnamed_min, unnamed_max, description, ...) \ + fio_cli_start((argc), \ + (argv), \ + (unnamed_min), \ + (unnamed_max), \ + (description), \ + (fio___cli_line_s[]){__VA_ARGS__, {0}}) +/** + * Never use the function directly, always use the MACRO, because the macro + * attaches a NULL marker at the end of the `names` argument collection. + */ +SFUNC void fio_cli_start FIO_NOOP(int argc, + char const *argv[], + int unnamed_min, + int unnamed_max, + char const *description, + fio___cli_line_s *arguments); +/** + * Clears the memory used by the CLI dictionary, removing all parsed data. + * + * This function is NOT thread safe. + */ +SFUNC void fio_cli_end(void); + +/** Returns the argument's value as a NUL terminated C String. */ +SFUNC char const *fio_cli_get(char const *name); + +/** Returns the argument's value as a NUL terminated `fio_buf_info_s`. */ +SFUNC fio_buf_info_s fio_cli_get_str(char const *name); + +/** Returns the argument's value as an integer. */ +SFUNC int64_t fio_cli_get_i(char const *name); + +/** This MACRO returns the argument's value as a boolean. */ +#define fio_cli_get_bool(name) (fio_cli_get((name)) != NULL) + +/** Returns the number of unnamed argument. */ +SFUNC unsigned int fio_cli_unnamed_count(void); + +/** Returns the unnamed argument using a 0 based `index`. */ +SFUNC char const *fio_cli_unnamed(unsigned int index); + +/** Returns the unnamed argument using a 0 based `index`. */ +SFUNC fio_buf_info_s fio_cli_unnamed_str(unsigned int index); + +/** + * Sets the argument's value as a NUL terminated C String. + * + * fio_cli_set("-p", "hello"); + * + * This function is NOT thread safe. + */ +SFUNC void fio_cli_set(char const *name, char const *value); + +/** + * Sets the argument's value as a NUL terminated C String. + * + * fio_cli_start(argc, argv, + * "this is example accepts the following options:", + * "-p -port the port to bind to", FIO_CLI_INT; + * + * fio_cli_set("-p", "hello"); // fio_cli_get("-p") == fio_cli_get("-port"); + * + * This function is NOT thread safe. + */ +SFUNC void fio_cli_set_i(char const *name, int64_t i); + +/** Sets / adds an unnamed argument to the 0 based array of unnamed elements. */ +SFUNC unsigned int fio_cli_set_unnamed(unsigned int index, const char *); + +/** Calls `task` for every argument received. */ +SFUNC size_t fio_cli_each(int (*task)(fio_buf_info_s name, + fio_buf_info_s value, + fio_cli_arg_e arg_type, + void *udata), + void *udata); + +/** Returns the argument's expected content type. */ +SFUNC fio_cli_arg_e fio_cli_type(char const *name); + +/* ***************************************************************************** +CLI Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +String for CLI +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio_cli_str) + +typedef struct { + uint8_t em; /* embedded? const? how long? */ + uint8_t pad[3]; /* padding - embedded buffer starts here */ + uint32_t len; /* if not embedded, otherwise see `em` */ + char *str; /* if not embedded, otherwise see `pad` */ +} fio_cli_str_s; + +/** CLI String free / destroy by context */ +FIO_SFUNC void fio_cli_str_destroy(fio_cli_str_s *s) { + if (!s || s->em || !s->str) + return; + FIO_LEAK_COUNTER_ON_FREE(fio_cli_str); + FIO_MEM_FREE_(s->str, s->len); + *s = (fio_cli_str_s){0}; +} + +/** CLI String info */ +FIO_IFUNC fio_buf_info_s fio_cli_str_buf(fio_cli_str_s *s) { + fio_buf_info_s r = {0}; + if (s && (s->em || s->len)) + r = ((s->em) & 127) ? (FIO_BUF_INFO2((char *)s->pad, (size_t)s->em)) + : (FIO_BUF_INFO2(s->str, (size_t)s->len)); + return r; +} + +/** CLI String copy */ +FIO_SFUNC fio_cli_str_s fio_cli_str_new(fio_buf_info_s s) { + fio_cli_str_s r = {0}; + if (s.len < sizeof(r) - 1) { + r.em = s.len; + if (s.len) + FIO_MEMCPY(r.pad, s.buf, s.len); + return r; + } + r.len = (uint32_t)s.len; + r.str = (char *)FIO_MEM_REALLOC_(NULL, 0, s.len + 1, 0); + FIO_ASSERT_ALLOC(r.str); + FIO_LEAK_COUNTER_ON_ALLOC(fio_cli_str); + FIO_MEMCPY(r.str, s.buf, s.len); + r.str[r.len] = 0; + return r; +} + +/** CLI String tmp copy */ +FIO_SFUNC fio_cli_str_s fio_cli_str_tmp(fio_buf_info_s s) { + fio_cli_str_s r = {0}; + if (s.len < sizeof(r) - 1) { + r.em = s.len; + FIO_MEMCPY(r.pad, s.buf, s.len); + return r; + } + r.em = 128; /* mark as const, memory shouldn't be freed */ + r.len = (uint32_t)s.len; + r.str = s.buf; + return r; +} + +/* ***************************************************************************** +String array for CLI +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio_cli_ary) + +typedef struct { + fio_cli_str_s *ary; + uint32_t capa; + uint32_t w; +} fio___cli_ary_s; + +FIO_SFUNC void fio___cli_ary_destroy(fio___cli_ary_s *a) { + if (!a || !a->ary) + return; + for (size_t i = 0; i < a->w; ++i) + fio_cli_str_destroy(a->ary + i); + FIO_LEAK_COUNTER_ON_FREE(fio_cli_ary); + FIO_MEM_FREE_(a->ary, sizeof(*a->ary) * a->capa); + *a = (fio___cli_ary_s){0}; +} +FIO_SFUNC uint32_t fio___cli_ary_new_index(fio___cli_ary_s *a) { + FIO_ASSERT(a, "Internal CLI Error - no CLI array given!"); + if (a->w == a->capa) { + /* increase capacity */ + if (!a->ary) + FIO_LEAK_COUNTER_ON_ALLOC(fio_cli_ary); + size_t new_capa = a->capa + 8; + FIO_ASSERT(new_capa < 0xFFFFFFFFU, "fio_cli data overflow"); + fio_cli_str_s *tmp = + (fio_cli_str_s *)FIO_MEM_REALLOC_(a->ary, + sizeof(*a->ary) * a->capa, + sizeof(*a->ary) * new_capa, + a->capa); + FIO_ASSERT_ALLOC(tmp); + a->ary = tmp; + a->capa = (uint32_t)new_capa; + if (!(FIO_MEM_REALLOC_IS_SAFE_)) + FIO_MEMSET(a->ary + a->w, 0, sizeof(*a->ary) * (new_capa - a->w)); + } + FIO_ASSERT_DEBUG(a->w < (uint32_t)a->capa, "CLI array index error!"); + return a->w++; +} + +FIO_IFUNC fio_buf_info_s fio___cli_ary_get(fio___cli_ary_s *a, uint32_t index) { + fio_buf_info_s r = {0}; + if (index >= a->w) + return r; + return fio_cli_str_buf(a->ary + index); +} +FIO_IFUNC void fio___cli_ary_set(fio___cli_ary_s *a, + uint32_t index, + fio_buf_info_s str) { + FIO_ASSERT(a, "Internal CLI Error - no CLI array given!"); + if (index >= a->w) + return; + fio_cli_str_destroy(a->ary + index); + a->ary[index] = fio_cli_str_new(str); +} + +/* ***************************************************************************** +CLI Alias Index Map +***************************************************************************** */ + +typedef struct { + fio_cli_str_s name; + fio_cli_arg_e t; + uint32_t index; +} fio___cli_aliases_s; + +#define FIO___CLI_ALIAS_HASH(o) \ + fio_risky_hash(fio_cli_str_buf(&o->name).buf, \ + fio_cli_str_buf(&o->name).len, \ + (uint64_t)(uintptr_t)fio_cli_str_destroy) +#define FIO___CLI_ALIAS_IS_EQ(a, b) \ + FIO_BUF_INFO_IS_EQ(fio_cli_str_buf(&a->name), fio_cli_str_buf(&b->name)) +FIO_TYPEDEF_IMAP_ARRAY(fio___cli_amap, + fio___cli_aliases_s, + uint32_t, + FIO___CLI_ALIAS_HASH, + FIO___CLI_ALIAS_IS_EQ, + FIO_IMAP_ALWAYS_VALID) +#undef FIO___CLI_ALIAS_HASH +#undef FIO___CLI_ALIAS_IS_EQ + +/* ***************************************************************************** +CLI Alias and Value Data Store +***************************************************************************** */ + +static struct fio___cli_data_s { + /* maps alias names to value indexes (array) */ + fio___cli_amap_s aliases; + fio___cli_ary_s indexed, unnamed; + const char *description; + fio___cli_line_s *args; + const char *app_name; +} fio___cli_data = {{0}}; + +FIO_SFUNC void fio___cli_data_destroy(void) { + fio___cli_ary_destroy(&fio___cli_data.indexed); + fio___cli_ary_destroy(&fio___cli_data.unnamed); + FIO_IMAP_EACH(fio___cli_amap, &fio___cli_data.aliases, i) { + fio_cli_str_destroy(&fio___cli_data.aliases.ary[i].name); + } + fio___cli_amap_destroy(&fio___cli_data.aliases); + fio___cli_data = (struct fio___cli_data_s){{0}}; +} + +FIO_SFUNC void fio___cli_data_alias(fio_buf_info_s key, + fio_buf_info_s alias, + fio_cli_arg_e t) { + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + fio___cli_aliases_s *a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (!a) { + o.name = fio_cli_str_new(key); + o.index = fio___cli_ary_new_index(&fio___cli_data.indexed); + o.t = t; + fio___cli_amap_set(&fio___cli_data.aliases, o, 1); + } + if (!alias.len) + return; + o.name = fio_cli_str_tmp(alias); + fio___cli_aliases_s *old = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (old) { + FIO_LOG_WARNING("(fio_cli) CLI alias %s already exists! overwriting...", + fio_cli_str_buf(&o.name).buf); + old->index = a->index; + } else { + o.name = fio_cli_str_new(alias); + o.index = a->index; + o.t = a->t; + fio___cli_amap_set(&fio___cli_data.aliases, o, 1); + } +} + +FIO_SFUNC void fio___cli_print_help(void); + +FIO_SFUNC void fio___cli_data_set(fio_buf_info_s key, fio_buf_info_s value) { + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + fio___cli_aliases_s *a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (!a) { + fio___cli_data_alias(key, (fio_buf_info_s){0}, FIO_CLI_ARG_STRING); + a = fio___cli_amap_get(&fio___cli_data.aliases, o); + } + FIO_ASSERT(a && a->index < fio___cli_data.indexed.w, + "(fio_cli) CLI alias initialization error!"); + if (a->t == FIO_CLI_ARG_INT) { + char *start = value.buf; + fio_atol(&start); + if (start != value.buf + value.len) { + FIO_LOG_FATAL("(CLI) %.*s should be an integer!", + (int)value.len, + value.buf); + fio___cli_print_help(); + } + } + fio___cli_ary_set(&fio___cli_data.indexed, a->index, value); +} + +FIO_SFUNC fio_buf_info_s fio___cli_data_get(fio_buf_info_s key) { + fio_buf_info_s r = {0}; + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + fio___cli_aliases_s *a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (a) + r = fio___cli_ary_get(&fio___cli_data.indexed, a->index); + if (!r.len) + r.buf = NULL; + return r; +} + +FIO_SFUNC uint32_t fio___cli_data_get_index(fio_buf_info_s key) { + uint32_t r = (uint32_t)-1; + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + fio___cli_aliases_s *a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (a) + r = a->index; + return r; +} + +/* ***************************************************************************** +CLI Destruction +***************************************************************************** */ + +SFUNC void __attribute__((destructor)) fio_cli_end(void) { + fio___cli_data_destroy(); +} + +/* ***************************************************************************** +CLI Public Get/Set API +***************************************************************************** */ + +/** Returns the argument's expected content type. */ +SFUNC fio_cli_arg_e fio_cli_type(char const *name) { + fio_cli_arg_e r = FIO_CLI_ARG_NONE; + fio___cli_aliases_s o = {.name = + fio_cli_str_tmp(FIO_BUF_INFO1((char *)name))}; + fio___cli_aliases_s *a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (a) + r = a->t; + return r; +} + +/** Returns the argument's value as a NUL terminated C String. */ +SFUNC char const *fio_cli_get(char const *name) { + if (!name) + return fio_cli_unnamed(0); + fio_buf_info_s key = FIO_BUF_INFO1((char *)name); + return fio___cli_data_get(key).buf; +} + +/** Returns the argument's value as a NUL terminated C String. */ +SFUNC fio_buf_info_s fio_cli_get_str(char const *name) { + if (!name) + goto unnamed_zero; + return fio___cli_data_get(FIO_BUF_INFO1((char *)name)); +unnamed_zero: + if (!fio___cli_data.unnamed.w) + return FIO_BUF_INFO0; + return fio___cli_ary_get(&fio___cli_data.unnamed, 0); +} + +/** Returns the argument's value as an integer. */ +SFUNC int64_t fio_cli_get_i(char const *name) { + char *val = (char *)fio_cli_get(name); + if (!val) + return 0; + return fio_atol(&val); +} + +/** Returns the number of unnamed argument. */ +SFUNC unsigned int fio_cli_unnamed_count(void) { + return fio___cli_data.unnamed.w; +} + +/** Returns the unnamed argument using a 0 based `index`. */ +SFUNC char const *fio_cli_unnamed(unsigned int index) { + if (index >= fio___cli_data.unnamed.w) + return NULL; + return fio___cli_ary_get(&fio___cli_data.unnamed, (uint32_t)index).buf; +} + +/** Returns the unnamed argument using a 0 based `index`. */ +SFUNC fio_buf_info_s fio_cli_unnamed_str(unsigned int index) { + if (index >= fio___cli_data.unnamed.w) + return FIO_BUF_INFO0; + return fio___cli_ary_get(&fio___cli_data.unnamed, (uint32_t)index); +} + +/** + * Sets the argument's value as a NUL terminated C String. + * + * fio_cli_set("-p", "hello"); + * + * This function is NOT thread safe. + */ +SFUNC void fio_cli_set(char const *name, char const *value) { + fio_buf_info_s key = FIO_BUF_INFO1((char *)name); + fio_buf_info_s val = FIO_BUF_INFO1((char *)value); + if (!name) { + if (!value) + return; + uint32_t i = fio___cli_ary_new_index(&fio___cli_data.unnamed); + fio___cli_ary_set(&fio___cli_data.unnamed, i, val); + return; + } + fio___cli_data_set(key, val); +} + +/** + * Sets the argument's value as a NUL terminated C String. + * + * fio_cli_start(argc, argv, + * "this is example accepts the following options:", + * "-p -port the port to bind to", FIO_CLI_INT; + * + * fio_cli_set("-p", "hello"); // fio_cli_get("-p") == fio_cli_get("-port"); + * + * This function is NOT thread safe. + */ +SFUNC void fio_cli_set_i(char const *name, int64_t i) { + char buf[32]; + size_t len = fio_digits10(i); + fio_ltoa10(buf, i, len); + buf[len] = 0; + fio_cli_set(name, buf); +} + +/** Sets / adds an unnamed argument to the 0 based array of unnamed elements. */ +SFUNC unsigned int fio_cli_set_unnamed(unsigned int index, const char *value) { + if (!value) + return (uint32_t)-1; + fio_buf_info_s val = FIO_BUF_INFO1((char *)value); + if (!val.len) + return (uint32_t)-1; + if (index >= fio___cli_data.unnamed.w) + index = fio___cli_ary_new_index(&fio___cli_data.unnamed); + fio___cli_ary_set(&fio___cli_data.unnamed, index, val); + return index; +} + +/** Calls `task` for every argument received. */ +SFUNC size_t fio_cli_each(int (*task)(fio_buf_info_s name, + fio_buf_info_s value, + fio_cli_arg_e arg_type, + void *udata), + void *udata) { + size_t r = 0; + if (!task) + return r; + FIO_IMAP_EACH(fio___cli_amap, &(fio___cli_data.aliases), i) { + fio_buf_info_s value = fio_cli_str_buf(fio___cli_data.indexed.ary + + fio___cli_data.aliases.ary[i].index); + if (!value.len) + continue; + ++r; + if (task(fio_cli_str_buf(&fio___cli_data.aliases.ary[i].name), + value, + fio___cli_data.aliases.ary[i].t, + udata)) + break; + } + for (size_t i = 0; i < fio___cli_data.unnamed.w; ++i) { + fio_buf_info_s value = fio_cli_str_buf(fio___cli_data.unnamed.ary + i); + if (!value.len) + continue; + ++r; + if (task(FIO_BUF_INFO2(NULL, 0), + value, + fio___cli_data.aliases.ary[i].t, + udata)) + break; + } + return r; +} + +/* ***************************************************************************** +CLI Name Iterator +***************************************************************************** */ + +typedef struct { + fio___cli_line_s *args; + fio_buf_info_s line; + fio_buf_info_s desc; + size_t index; + fio_cli_arg_e line_type; +} fio___cli_iterator_args_s; + +#define FIO___CLI_EACH_ARG(args_, i) \ + for (fio___cli_iterator_args_s i = \ + { \ + .args = args_, \ + .line = FIO_BUF_INFO1((char *)((args_)[0].l)), \ + .line_type = (args_)[0].t, \ + }; \ + i.line.buf; \ + (++i.index, \ + i.line = FIO_BUF_INFO1((char *)i.args[i.index].l), \ + i.line_type = i.args[i.index].t)) + +typedef struct { + fio_buf_info_s line; + fio_buf_info_s alias; +} fio___cli_iterator_alias_s; + +FIO_IFUNC fio_buf_info_s fio___cli_iterator_alias_first(fio___cli_line_s *arg, + fio_buf_info_s line) { + fio_buf_info_s a = {0}; + if (arg->t > FIO_CLI_ARG_INT) + return a; + if (!line.buf || line.buf[0] != '-') + return a; + char *pos = (char *)FIO_MEMCHR(line.buf, ' ', line.len); + if (!pos) + pos = line.buf + line.len; + a = FIO_BUF_INFO2(line.buf, (size_t)(pos - line.buf)); + return a; +} +FIO_IFUNC fio_buf_info_s fio___cli_iterator_alias_next(fio_buf_info_s line, + fio_buf_info_s prev) { + fio_buf_info_s a = {0}; + if (!prev.buf[prev.len]) + return a; /* eol */ + prev.buf += prev.len + 1; + if (prev.buf[0] != '-') + return a; /* no more aliases */ + char *pos = + (char *)FIO_MEMCHR(prev.buf, ' ', ((line.buf + line.len) - prev.buf)); + if (!pos) + pos = line.buf + line.len; + a = FIO_BUF_INFO2(prev.buf, (size_t)(pos - prev.buf)); + return a; +} + +#define FIO___CLI_EACH_ALIAS(i, alias) \ + for (fio_buf_info_s alias = \ + fio___cli_iterator_alias_first(i.args + i.index, i.line); \ + alias.buf; \ + alias = fio___cli_iterator_alias_next(i.line, alias)) + +FIO_IFUNC fio_buf_info_s +fio___cli_iterator_default_val(fio___cli_iterator_args_s *i) { + fio_buf_info_s a = {0}; + fio_buf_info_s line = i->line; + if (!line.buf || line.buf[0] != '-') { + i->desc = line; + return a; + } + for (;;) { + char *pos = (char *)FIO_MEMCHR(line.buf, ' ', line.len); + if (!pos) + return a; + ++pos; + if (pos[0] == '-') { + line.len = (line.buf + line.len) - pos; + line.buf = pos; + continue; + } + if (pos[0] != '(') { + i->desc.len = (line.buf + line.len) - pos; + i->desc.buf = pos; + return a; + } + if (pos[1] == '"') { + pos += 2; + a.buf = pos; + while (*pos && !(pos[0] == '"' && pos[1] == ')')) + ++pos; + if (!pos[0]) { + /* no default value? */ + i->desc.len = (line.buf + line.len) - a.buf; + i->desc.buf = a.buf; + a = (fio_buf_info_s){0}; + return a; + } + a.len = pos - a.buf; + pos += 2; + } else { + pos += 1; + a.buf = pos; + while (*pos && pos[0] != ')') + ++pos; + if (!pos[0]) { + /* no default value? */ + i->desc.len = (line.buf + line.len) - a.buf; + i->desc.buf = a.buf; + a = (fio_buf_info_s){0}; + return a; + } + a.len = pos - a.buf; + ++pos; + } + pos += *pos == ' '; + line = i->line; + i->desc.len = (line.buf + line.len) - pos; + i->desc.buf = pos; + return a; + } +} + +#define FIO___CLI_EACH_DESC(i, desc_) \ + for (fio_buf_info_s desc_ = i.desc; \ + desc_.len || i.args[i.index + 1].t == FIO_CLI_ARG_PRINT; \ + desc_ = (i.args[i.index + 1].t == FIO_CLI_ARG_PRINT \ + ? (++i.index, FIO_BUF_INFO1((char *)i.args[i.index].l)) \ + : FIO_BUF_INFO2(0, 0))) + +/* ***************************************************************************** +CLI Build + Parsing Arguments +***************************************************************************** */ + +FIO_SFUNC void fio___cli_build_argument_aliases(char const *argv[], + char const *description, + fio___cli_line_s *args) { + /** Setup the CLI argument alias indexing **/ + fio___cli_data.description = description; + fio___cli_data.args = args; + fio___cli_data.app_name = argv[0]; + FIO___CLI_EACH_ARG(args, i) { + fio_buf_info_s first_alias = {0}; + fio_buf_info_s def = fio___cli_iterator_default_val(&i); + switch (i.line_type) { + case FIO_CLI_ARG_STRING: /* fall through */ + case FIO_CLI_ARG_BOOL: /* fall through */ + case FIO_CLI_ARG_INT: /* fall through */ + FIO_ASSERT( + i.line.buf[0] == '-', + "(CLI) argument lines MUST start with an '-argument-name':\n\t%s", + i.line.buf); + FIO___CLI_EACH_ALIAS(i, alias) { + if (first_alias.buf) { + fio___cli_data_alias(first_alias, alias, i.line_type); + continue; + } + fio___cli_data_alias(alias, first_alias, i.line_type); + first_alias = alias; + } + if (def.len) { + FIO_ASSERT( + i.line_type != FIO_CLI_ARG_BOOL, + "(CLI) boolean CLI arguments cannot have a default value:\n\t%s", + i.line.buf); + fio___cli_data_set(first_alias, def); + } + continue; + case FIO_CLI_ARG_PRINT: /* fall through */ + case FIO_CLI_ARG_PRINT_LINE: /* fall through */ + case FIO_CLI_ARG_PRINT_HEADER: continue; + } + } +} + +void fio_cli_start___(void); /* sublime text marker */ +SFUNC void fio_cli_start FIO_NOOP(int argc, + char const *argv[], + int unnamed_min, + int unnamed_max, + char const *description, + fio___cli_line_s *args) { + uint32_t help_value32 = fio_buf2u32u("help"); + + fio___cli_build_argument_aliases(argv, description, args); + if (unnamed_min == -1) { + unnamed_max = -1; + unnamed_min = 0; + } + + /** Consume Arguments **/ + for (size_t i = 1; i < (size_t)argc; ++i) { + fio_buf_info_s key = FIO_BUF_INFO1((char *)argv[i]); + fio_buf_info_s value = {0}; + fio___cli_aliases_s *a = NULL; + if (!key.buf || !key.len) + continue; + if (key.buf[0] != '-') + goto process_unnamed; + /* --help / -h / -? */ + if ((key.len == 2 && ((key.buf[1] | 32) == 'h' || key.buf[1] == '?')) || + (key.len == 5 && + (fio_buf2u32u(key.buf + 1) | 0x20202020UL) == help_value32) || + (key.len == 6 && key.buf[1] == '-' && + (fio_buf2u32u(key.buf + 2) | 0x20202020UL) == help_value32)) + fio___cli_print_help(); + /* look for longest argument match for argument (find, i.e. -arg=val) */ + for (;;) { + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (a) + break; + ++value.len; + --key.len; + value.buf = key.buf + key.len; + if (!key.len) { + key = value; + goto process_unnamed; + } + } + /* boolean values can be chained, but cannot have an actual value. */ + if (a->t == FIO_CLI_ARG_BOOL) { + fio_buf_info_s bool_value = FIO_BUF_INFO2((char *)"1", 1); + char bool_buf[3] = {'-', 0, 0}; + for (;;) { + fio___cli_ary_set(&fio___cli_data.indexed, a->index, bool_value); + while (value.len && value.buf[0] == ',') + (--value.len, ++value.buf); + if (!value.len) + break; + bool_buf[1] = value.buf[0]; + --value.len; + ++value.buf; + key = FIO_BUF_INFO2(bool_buf, 2); + fio___cli_aliases_s o = {.name = fio_cli_str_tmp(key)}; + a = fio___cli_amap_get(&fio___cli_data.aliases, o); + if (!a || a->t != FIO_CLI_ARG_BOOL) { + FIO_LOG_FATAL( + "(CLI) unrecognized boolean value (%s) embedded in argument %s", + bool_buf, + argv[i]); + fio___cli_print_help(); + } + } + continue; + } + + if (value.len) { /* values such as `-arg34` / `-arg=32` */ + value.len -= (value.buf[0] == '='); + value.buf += (value.buf[0] == '='); + } else { /* values such as `-arg 32` (using 2 argv elements)*/ + if ((i + 1) == (size_t)argc) { + FIO_LOG_FATAL("(CLI) argument value missing for (%s)", + key.buf, + argv[i]); + fio___cli_print_help(); + } + ++i; + value = FIO_BUF_INFO1((char *)argv[i]); + } + fio___cli_data_set(key, value); /* use this for type validation */ + continue; + process_unnamed: + + if (!unnamed_max) { + FIO_LOG_FATAL("(CLI) unnamed arguments limit reached at argument: %s", + key.buf); + fio___cli_print_help(); + } + fio___cli_ary_set(&fio___cli_data.unnamed, + fio___cli_ary_new_index(&fio___cli_data.unnamed), + key); + --unnamed_max; + continue; + } + if (unnamed_min && fio___cli_data.unnamed.w < (uint32_t)unnamed_min) { + FIO_LOG_FATAL("(CLI) missing required arguments"); + fio___cli_print_help(); + } +} + +/* ***************************************************************************** +CLI Help Output +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio_cli_help_writer) + +FIO_IFUNC fio_str_info_s fio___cli_write2line(fio_str_info_s d, + fio_buf_info_s s, + uint8_t static_memory) { + if (d.len + s.len + 2 > d.capa) { + size_t new_capa = (d.len + s.len) << 1; + char *tmp = (char *)FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); + FIO_ASSERT_ALLOC(tmp); + FIO_LEAK_COUNTER_ON_ALLOC(fio_cli_help_writer); + FIO_MEMCPY(tmp, d.buf, d.len); + if (!static_memory) { + FIO_LEAK_COUNTER_ON_FREE(fio_cli_help_writer); + FIO_MEM_FREE_(d.buf, d.capa); + } + d.capa = new_capa; + d.buf = tmp; + } + FIO_MEMCPY(d.buf + d.len, s.buf, s.len); + d.len += s.len; + return d; +} + +FIO_SFUNC fio_str_info_s fio___cli_write2line_finalize(fio_str_info_s d, + fio_buf_info_s app_name, + uint8_t static_memory) { + /* replace "NAME" with `app_name` */ + size_t additional_bytes = app_name.len > 4 ? app_name.len - 4 : 0; + char *pos = (char *)FIO_MEMCHR(d.buf, 'N', d.len); + uint32_t name_val = fio_buf2u32u("NAME"); + while (pos) { + if (fio_buf2u32u(pos) != name_val) { + if (pos + 4 > d.buf + d.len) + break; + pos = (char *)FIO_MEMCHR(pos + 1, 'N', (d.buf + d.len) - pos); + continue; + } + if (d.len + additional_bytes + 2 > d.capa) { /* not enough room? */ + size_t new_capa = d.len + ((additional_bytes + 2) << 2); + char *tmp = (char *)FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); + FIO_ASSERT_ALLOC(tmp); + FIO_LEAK_COUNTER_ON_ALLOC(fio_cli_help_writer); + FIO_MEMCPY(tmp, d.buf, d.len); + if (!static_memory) { + FIO_LEAK_COUNTER_ON_FREE(fio_cli_help_writer); + FIO_MEM_FREE_(d.buf, d.capa); + } + static_memory = 0; + pos = tmp + (pos - d.buf); + d.capa = new_capa; + d.buf = tmp; + } + FIO_MEMMOVE(pos + app_name.len, pos + 4, ((d.buf + d.len) - (pos + 4))); + FIO_MEMCPY(pos, app_name.buf, app_name.len); + d.len -= 4; + d.len += app_name.len; + pos += app_name.len; + } + d.buf[d.len] = 0; + return d; +} + +FIO_SFUNC void fio___cli_print_help(void) { + char const *description = fio___cli_data.description; + fio___cli_line_s *args = fio___cli_data.args; + + fio_buf_info_s app_name = FIO_BUF_INFO2( + (char *)fio___cli_data.app_name, + (fio___cli_data.app_name ? FIO_STRLEN(fio___cli_data.app_name) : 0)); + FIO_STR_INFO_TMP_VAR(help, 8191); + fio_str_info_s help_org_state = help; + + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\n"), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)description), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\n"), + help_org_state.buf == help.buf); + + FIO___CLI_EACH_ARG(args, i) { + fio_buf_info_s first_alias = {0}; + fio_buf_info_s def = fio___cli_iterator_default_val(&i); + fio_buf_info_s argument_type_txt = {0}; + switch (i.line_type) { + case FIO_CLI_ARG_STRING: + argument_type_txt = FIO_BUF_INFO1( + (char *)"\x1B[0m \x1B[2m"); /* fall through */ + case FIO_CLI_ARG_BOOL: + FIO_ASSERT(i.line_type != FIO_CLI_ARG_BOOL || !def.len, + "(CLI) boolean values cannot have a default value:\n\t%s", + i.line.buf); + if (!argument_type_txt.buf) + argument_type_txt = FIO_BUF_INFO1( + (char *)"\x1B[0m \x1B[2m(boolean flag)"); /* fall through */ + case FIO_CLI_ARG_INT: + if (!argument_type_txt.buf) + argument_type_txt = + FIO_BUF_INFO1((char *)"\x1B[0m \x1B[2m"); + FIO_ASSERT( + i.line.buf[0] == '-', + "(CLI) argument lines MUST start with an '-argument-name':\n\t%s", + i.line.buf); + FIO___CLI_EACH_ALIAS(i, al) { + if (!first_alias.buf) + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)" \x1B[1m"), + help_org_state.buf == help.buf); + else + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\x1B[0m, \x1B[1m"), + help_org_state.buf == help.buf); + first_alias = al; + help = fio___cli_write2line(help, + FIO_STR2BUF_INFO(al), + help_org_state.buf == help.buf); + } + help = fio___cli_write2line(help, + argument_type_txt, + help_org_state.buf == help.buf); + if (def.len) { + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)", defaults to: "), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_STR2BUF_INFO(def), + help_org_state.buf == help.buf); + } + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\x1B[0m\n"), + help_org_state.buf == help.buf); + FIO___CLI_EACH_DESC(i, desc) { + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\t"), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_STR2BUF_INFO(desc), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\n"), + help_org_state.buf == help.buf); + } + continue; + case FIO_CLI_ARG_PRINT: + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\t"), + help_org_state.buf == + help.buf); /* fall through */ + case FIO_CLI_ARG_PRINT_LINE: + help = fio___cli_write2line(help, + FIO_STR2BUF_INFO(i.line), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\n"), + help_org_state.buf == help.buf); + continue; + case FIO_CLI_ARG_PRINT_HEADER: + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\n\x1B[4m"), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_STR2BUF_INFO(i.line), + help_org_state.buf == help.buf); + help = fio___cli_write2line(help, + FIO_BUF_INFO1((char *)"\x1B[0m\n"), + help_org_state.buf == help.buf); + continue; + } + } + help = fio___cli_write2line( + help, + FIO_BUF_INFO1((char *)"\nUse any of the following input formats:\n" + "\t-arg \t-arg=\t-arg\n" + "\n" + "Use \x1B[1m-h\x1B[0m , \x1B[1m-help\x1B[0m or " + "\x1B[1m-?\x1B[0m " + "to get this information again.\n" + "\n"), + help_org_state.buf == help.buf); + help = fio___cli_write2line_finalize(help, + app_name, + help_org_state.buf == help.buf); + fwrite(help.buf, 1, help.len, stdout); + if (help_org_state.buf != help.buf) { + FIO_LEAK_COUNTER_ON_FREE(fio_cli_help_writer); + FIO_MEM_FREE_(help.buf, help.capa); + } + fio_cli_end(); + exit(0); +} +/* ***************************************************************************** +CLI - cleanup +***************************************************************************** */ +#undef FIO___CLI_ON_ALLOC +#undef FIO___CLI_ON_FREE +#endif /* FIO_EXTERN_COMPLETE*/ +#endif /* FIO_CLI */ +#undef FIO_CLI +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MEMORY_NAME fio /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Custom Memory Allocator / Pooling + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ + +/* ***************************************************************************** +Memory Allocation - Setup Alignment Info +***************************************************************************** */ +#if defined(FIO_MEMORY_NAME) && !defined(FIO___RECURSIVE_INCLUDE) + +#undef FIO_MEM_ALIGN +#undef FIO_MEM_ALIGN_NEW + +#ifndef FIO_MEMORY_ALIGN_LOG +/** Allocation alignment, MUST be >= 3 and <= 10*/ +#define FIO_MEMORY_ALIGN_LOG 4 + +#elif FIO_MEMORY_ALIGN_LOG < 3 +#undef FIO_MEMORY_ALIGN_LOG +#define FIO_MEMORY_ALIGN_LOG 3 +#elif FIO_MEMORY_ALIGN_LOG > 10 +#undef FIO_MEMORY_ALIGN_LOG +#define FIO_MEMORY_ALIGN_LOG 10 +#endif + +/* Helper macro, don't change this */ +#undef FIO_MEMORY_ALIGN_SIZE +/** The minimal allocation size & alignment. */ +#define FIO_MEMORY_ALIGN_SIZE (1UL << FIO_MEMORY_ALIGN_LOG) + +/* inform the compiler that the returned value is aligned on 16 byte marker */ +#if __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 8) +#define FIO_MEM_ALIGN __attribute__((assume_aligned(FIO_MEMORY_ALIGN_SIZE))) +#define FIO_MEM_ALIGN_NEW \ + __attribute__((malloc, assume_aligned(FIO_MEMORY_ALIGN_SIZE))) +#else +#define FIO_MEM_ALIGN +#define FIO_MEM_ALIGN_NEW +#endif /* (__clang__ || __GNUC__)... */ + +/* ***************************************************************************** +Memory Allocation - API +***************************************************************************** */ + +/** + * Allocates memory using a per-CPU core block memory pool. + * Memory is zeroed out. + * + * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT will be redirected to `mmap`, + * as if `mempool_mmap` was called. + * + * `mempool_malloc` promises a best attempt at providing locality between + * consecutive calls, but locality can't be guaranteed. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size); + +/** + * same as calling `fio_malloc(size_per_unit * unit_count)`; + * + * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT will be redirected to `mmap`, + * as if `mempool_mmap` was called. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, + calloc)(size_t size_per_unit, + size_t unit_count); + +/** Frees memory that was allocated using this library. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr); + +/** + * Re-allocates memory. An attempt to avoid copying the data is made only for + * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). + */ +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr, + size_t new_size); + +/** + * Re-allocates memory. An attempt to avoid copying the data is made only for + * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). + * + * This variation can perform better, as it might copy less data. + */ +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr, + size_t new_size, + size_t copy_len); + +/** + * Allocates memory directly using `mmap`, this is preferred for objects that + * both require almost a page of memory (or more) and expect a long lifetime. + * + * However, since this allocation will invoke the system call (`mmap`), it will + * be inherently slower. + * + * `mempoll_free` can be used for deallocating the memory. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size); + +/** + * When forking is called manually, call this function to reset the facil.io + * memory allocator's locks. + */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void); + +/* ***************************************************************************** +Memory Allocation - configuration macros + +NOTE: most configuration values should be a power of 2 or a logarithmic value. +***************************************************************************** */ + +/* Make sure the system's allocator is marked as unsafe. */ +#if FIO_MALLOC_TMP_USE_SYSTEM +#undef FIO_MEMORY_INITIALIZE_ALLOCATIONS +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 0 +#endif + +#ifndef FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG +/** + * The logarithmic size of a single allocation "chunk" (16 blocks). + * + * Limited to >=17 and <=24. + * + * By default 22, which is a ~2Mb allocation per system call, resulting in a + * maximum allocation size of 131Kb. + */ +#define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 21 +#endif + +#ifndef FIO_MEMORY_CACHE_SLOTS +/** + * The number of system allocation "chunks" to cache even if they are not in + * use. + */ +#define FIO_MEMORY_CACHE_SLOTS 4 +#endif + +#ifndef FIO_MEMORY_INITIALIZE_ALLOCATIONS +/** + * Forces the allocator to zero out memory early and often, so allocations + * return initialized memory (bytes are all zeros). + * + * This will make the realloc2 safe for use (all data not copied is zero). + */ +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS \ + FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT +#elif FIO_MEMORY_INITIALIZE_ALLOCATIONS +#undef FIO_MEMORY_INITIALIZE_ALLOCATIONS +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 1 +#else +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 0 +#endif + +#ifndef FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG +/** + * The number of blocks per system allocation. + * + * More blocks protect against fragmentation, but lower the maximum number that + * can be allocated without reverting to mmap. + * + * Range: 0-4 + * Recommended: depends on object allocation sizes, usually 1 or 2. + */ +#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 2 +#endif + +#ifndef FIO_MEMORY_ENABLE_BIG_ALLOC +/** + * Uses a whole system allocation to support bigger allocations. + * + * Could increase fragmentation costs. + */ +#define FIO_MEMORY_ENABLE_BIG_ALLOC 1 +#endif + +#ifndef FIO_MEMORY_ARENA_COUNT +/** + * Memory arenas mitigate thread contention while using more memory. + * + * Note that at some point arenas are statistically irrelevant... except when + * benchmarking contention in multi-core machines. + * + * Negative values will result in dynamic selection based on CPU core count. + */ +#define FIO_MEMORY_ARENA_COUNT -1 +#endif + +#ifndef FIO_MEMORY_ARENA_COUNT_FALLBACK +/* + * Used when dynamic arena count calculations fail. + * + * NOTE: if FIO_MEMORY_ARENA_COUNT is negative, dynamic arena calculation is + * performed using CPU core calculation. + */ +#define FIO_MEMORY_ARENA_COUNT_FALLBACK 24 +#endif + +#ifndef FIO_MEMORY_ARENA_COUNT_MAX +/* + * Used when dynamic arena count calculations fail. + * + * NOTE: if FIO_MEMORY_ARENA_COUNT is negative, dynamic arena calculation is + * performed using CPU core calculation. + */ +#define FIO_MEMORY_ARENA_COUNT_MAX 64 +#endif + +#ifndef FIO_MEMORY_WARMUP +#define FIO_MEMORY_WARMUP 0 +#endif + +#ifndef FIO_MEMORY_USE_THREAD_MUTEX +#if FIO_USE_THREAD_MUTEX_TMP +#define FIO_MEMORY_USE_THREAD_MUTEX FIO_USE_THREAD_MUTEX +#else +#if FIO_MEMORY_ARENA_COUNT > 0 +/** + * If arena count isn't linked to the CPU count, threads might busy-spin. + * It is better to slow wait than fast busy spin when the work in the lock is + * longer... and system allocations are performed inside arena locks. + */ +#define FIO_MEMORY_USE_THREAD_MUTEX 1 +#else +/* defaults to use a spinlock when no contention is expected. */ +#define FIO_MEMORY_USE_THREAD_MUTEX 0 +#endif +#endif +#endif + +#if !defined(FIO_MEM_SYS_ALLOC) || !defined(FIO_MEM_SYS_REALLOC) || \ + !defined(FIO_MEM_SYS_FREE) +/** + * The following MACROS, when all of them are defined, allow the memory + * allocator to collect memory from the system using an alternative method. + * + * - FIO_MEM_SYS_ALLOC(pages, alignment_log) + * + * - FIO_MEM_SYS_REALLOC(ptr, old_pages, new_pages, alignment_log) + * + * - FIO_MEM_SYS_FREE(ptr, pages) FIO_MEM_SYS_FREE_def_func((ptr), (pages)) + * + * Note that the alignment property for the allocated memory is essential and + * may me quite large. + */ +#undef FIO_MEM_SYS_ALLOC +#undef FIO_MEM_SYS_REALLOC +#undef FIO_MEM_SYS_FREE +#endif /* undefined FIO_MEM_SYS_ALLOC... */ + +/* ***************************************************************************** +Memory Allocation - configuration value - results and constants +***************************************************************************** */ + +/* Helper macros, don't change their values */ +#undef FIO_MEMORY_BLOCKS_PER_ALLOCATION +#undef FIO_MEMORY_SYS_ALLOCATION_SIZE +#undef FIO_MEMORY_BLOCK_ALLOC_LIMIT + +#if FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG < 0 || \ + FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG > 5 +#undef FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG +#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 3 +#endif + +/** the number of allocation blocks per system allocation. */ +#define FIO_MEMORY_BLOCKS_PER_ALLOCATION \ + (1UL << FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG) + +/** the total number of bytes consumed per system allocation. */ +#define FIO_MEMORY_SYS_ALLOCATION_SIZE \ + (1UL << FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG) + +/** + * The maximum allocation size, after which a big/system allocation is used. + */ +#define FIO_MEMORY_BLOCK_ALLOC_LIMIT \ + (FIO_MEMORY_SYS_ALLOCATION_SIZE >> (FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG + 2)) + +#if FIO_MEMORY_ENABLE_BIG_ALLOC +/** the limit of a big allocation, if enabled */ +#define FIO_MEMORY_BIG_ALLOC_LIMIT \ + (FIO_MEMORY_SYS_ALLOCATION_SIZE >> \ + (FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG > 3 \ + ? 3 \ + : FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG)) +#define FIO_MEMORY_ALLOC_LIMIT FIO_MEMORY_BIG_ALLOC_LIMIT +#else +#define FIO_MEMORY_ALLOC_LIMIT FIO_MEMORY_BLOCK_ALLOC_LIMIT +#endif + +/* ***************************************************************************** +Memory Allocation - configuration access - UNSTABLE API!!! +***************************************************************************** */ + +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_sys_alloc_size)(void) { + return FIO_MEMORY_SYS_ALLOCATION_SIZE; +} + +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_cache_slots)(void) { + return FIO_MEMORY_CACHE_SLOTS; +} +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment)(void) { + return FIO_MEMORY_ALIGN_SIZE; +} +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment_log)(void) { + return FIO_MEMORY_ALIGN_LOG; +} + +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alloc_limit)(void) { + return (FIO_MEMORY_BLOCK_ALLOC_LIMIT > FIO_MEMORY_ALLOC_LIMIT) + ? FIO_MEMORY_BLOCK_ALLOC_LIMIT + : FIO_MEMORY_ALLOC_LIMIT; +} + +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arena_alloc_limit)(void) { + return FIO_MEMORY_BLOCK_ALLOC_LIMIT; +} + +/* will realloc2 return junk data? */ +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, realloc_is_safe)(void) { + return FIO_MEMORY_INITIALIZE_ALLOCATIONS; +} + +/* Returns the calculated block size. */ +SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void); + +/** Prints the allocator's data structure. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void); + +/** Prints the allocator's free block list. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void); + +/** Prints the settings used to define the allocator. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void); + +/* ***************************************************************************** +Set global macros to use this allocator if FIO_MALLOC +***************************************************************************** */ +#ifdef FIO_MALLOC +/* prevent double declaration of FIO_MALLOC */ +#define H___FIO_MALLOC___H +#undef FIO_MEM_REALLOC +#define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) \ + fio_realloc2((ptr), (new_size), (copy_len)) +#undef FIO_MEM_FREE +#define FIO_MEM_FREE(ptr, size) fio_free((ptr)) +#undef FIO_MEM_REALLOC_IS_SAFE +#define FIO_MEM_REALLOC_IS_SAFE fio_realloc_is_safe() +#undef FIO_MALLOC +#endif /* FIO_MALLOC */ + +/* ***************************************************************************** +Temporarily (at least) set memory allocation macros to use this allocator +***************************************************************************** */ +#ifndef FIO_MALLOC_TMP_USE_SYSTEM + +#undef FIO_MEM_REALLOC_ +#undef FIO_MEM_FREE_ +#undef FIO_MEM_REALLOC_IS_SAFE_ + +#define FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) \ + FIO_NAME(FIO_MEMORY_NAME, realloc2)((ptr), (new_size), (copy_len)) +#define FIO_MEM_FREE_(ptr, size) FIO_NAME(FIO_MEMORY_NAME, free)((ptr)) +#define FIO_MEM_REALLOC_IS_SAFE_ FIO_NAME(FIO_MEMORY_NAME, realloc_is_safe)() + +#endif /* FIO_MALLOC_TMP_USE_SYSTEM */ + +/* ***************************************************************************** + + + + + +Memory Allocation - start implementation + + + + + +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/* internal workings start here */ + +/* ***************************************************************************** + + + + + + + +Helpers and System Memory Allocation + + + + +***************************************************************************** */ +#ifndef H___FIO_MEM_INCLUDE_ONCE___H +#define H___FIO_MEM_INCLUDE_ONCE___H + +#define FIO_MEM_BYTES2PAGES(size) \ + (((size_t)(size) + ((1UL << FIO_MEM_PAGE_SIZE_LOG) - 1)) & \ + ((~(size_t)0) << FIO_MEM_PAGE_SIZE_LOG)) + +/* ***************************************************************************** + + + +POSIX Allocation + + + +***************************************************************************** */ +#if FIO_OS_POSIX || __has_include("sys/mman.h") +#include + +/* Mitigates MAP_ANONYMOUS not being defined on older versions of MacOS */ +#if !defined(MAP_ANONYMOUS) +#if defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#else +#define MAP_ANONYMOUS 0 +#endif /* defined(MAP_ANONYMOUS) */ +#endif /* FIO_MEM_SYS_ALLOC */ + +/* inform the compiler that the returned value is aligned on 16 byte marker */ +#if __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 8) +#define FIO_PAGE_ALIGN \ + __attribute__((assume_aligned((1UL << FIO_MEM_PAGE_SIZE_LOG)))) +#define FIO_PAGE_ALIGN_NEW \ + __attribute__((malloc, assume_aligned((1UL << FIO_MEM_PAGE_SIZE_LOG)))) +#else +#define FIO_PAGE_ALIGN +#define FIO_PAGE_ALIGN_NEW +#endif /* (__clang__ || __GNUC__)... */ + +/* + * allocates memory using `mmap`, but enforces alignment. + */ +FIO_SFUNC void *FIO_MEM_SYS_ALLOC_def_func(size_t bytes, + uint8_t alignment_log) { + void *result; + static void *next_alloc = (void *)0x01; + const size_t alignment_mask = (1ULL << alignment_log) - 1; + const size_t alignment_size = (1ULL << alignment_log); + bytes = FIO_MEM_BYTES2PAGES(bytes); + next_alloc = + (void *)(((uintptr_t)next_alloc + alignment_mask) & alignment_mask); +/* hope for the best? */ +#ifdef MAP_ALIGNED + result = mmap(next_alloc, + bytes, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(alignment_log), + -1, + 0); +#else + result = mmap(next_alloc, + bytes, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); +#endif /* MAP_ALIGNED */ + if (result == MAP_FAILED) + return (void *)NULL; + if (((uintptr_t)result & alignment_mask)) { + munmap(result, bytes); + result = mmap(NULL, + bytes + alignment_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + if (result == MAP_FAILED) { + return (void *)NULL; + } + const uintptr_t offset = + (alignment_size - ((uintptr_t)result & alignment_mask)); + if (offset) { + munmap(result, offset); + result = (void *)((uintptr_t)result + offset); + } + munmap((void *)((uintptr_t)result + bytes), alignment_size - offset); + } + next_alloc = (void *)((uintptr_t)result + (bytes << 2)); + return result; +} + +/* + * Re-allocates memory using `mmap`, enforcing alignment. + */ +FIO_SFUNC void *FIO_MEM_SYS_REALLOC_def_func(void *mem, + size_t old_len, + size_t new_len, + uint8_t alignment_log) { + old_len = FIO_MEM_BYTES2PAGES(old_len); + new_len = FIO_MEM_BYTES2PAGES(new_len); + if (new_len > old_len) { + void *result; +#if defined(__linux__) + result = mremap(mem, old_len, new_len, 0); + if (result != MAP_FAILED) + return result; +#endif + result = mmap((void *)((uintptr_t)mem + old_len), + new_len - old_len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + if (result == (void *)((uintptr_t)mem + old_len)) { + result = mem; + } else { + /* copy and free */ + munmap(result, new_len - old_len); /* free the failed attempt */ + result = + FIO_MEM_SYS_ALLOC_def_func(new_len, + alignment_log); /* allocate new memory */ + if (!result) { + return (void *)NULL; + } + FIO_MEMCPY(result, mem, old_len); /* copy data */ + munmap(mem, old_len); /* free original memory */ + } + return result; + } + if (old_len != new_len) /* remove dangling pages */ + munmap((void *)((uintptr_t)mem + new_len), old_len - new_len); + return mem; +} + +/* frees memory using `munmap`. */ +FIO_IFUNC void FIO_MEM_SYS_FREE_def_func(void *mem, size_t bytes) { + bytes = FIO_MEM_BYTES2PAGES(bytes); + munmap(mem, bytes); +} + +/* ***************************************************************************** + + + +Windows Allocation + + + +***************************************************************************** */ +#elif FIO_OS_WIN +#include + +FIO_IFUNC void FIO_MEM_SYS_FREE_def_func(void *mem, size_t bytes) { + bytes = FIO_MEM_BYTES2PAGES(bytes); + if (!VirtualFree(mem, 0, MEM_RELEASE)) + FIO_LOG_ERROR("Memory address at %p couldn't be returned to the system", + mem); + (void)bytes; +} + +FIO_IFUNC void *FIO_MEM_SYS_ALLOC_def_func(size_t bytes, + uint8_t alignment_log) { + // return aligned_alloc((pages << 12), (1UL << alignment_log)); + void *result; + size_t attempts = 0; + static void *next_alloc = (void *)0x01; + const uintptr_t alignment_rounder = (1ULL << alignment_log) - 1; + const uintptr_t alignment_mask = ~alignment_rounder; + bytes = FIO_MEM_BYTES2PAGES(bytes); + do { + next_alloc = + (void *)(((uintptr_t)next_alloc + alignment_rounder) & alignment_mask); + FIO_ASSERT_DEBUG(!((uintptr_t)next_alloc & alignment_rounder), + "alignment allocation rounding error?"); + result = + VirtualAlloc(next_alloc, (bytes << 2), MEM_RESERVE, PAGE_READWRITE); + next_alloc = (void *)((uintptr_t)next_alloc + (bytes << 2)); + } while (!result && (attempts++) < 1024); + if (result) { + result = VirtualAlloc(result, bytes, MEM_COMMIT, PAGE_READWRITE); + FIO_ASSERT_DEBUG(result, "couldn't commit memory after reservation?!"); + + } else { + FIO_LOG_ERROR("Couldn't allocate memory from the system, error %zu." + "\n\t%zu attempts with final address %p", + GetLastError(), + attempts, + next_alloc); + } + return result; +} + +FIO_IFUNC void *FIO_MEM_SYS_REALLOC_def_func(void *mem, + size_t old_len, + size_t new_len, + uint8_t alignment_log) { + if (!new_len) + goto free_mem; + old_len = FIO_MEM_BYTES2PAGES(old_len); + new_len = FIO_MEM_BYTES2PAGES(new_len); + if (new_len > old_len) { + /* extend allocation */ + void *tmp = VirtualAlloc((void *)((uintptr_t)mem + old_len), + new_len - old_len, + MEM_COMMIT, + PAGE_READWRITE); + if (tmp) + return mem; + /* Alloc, Copy, Free... sorry... */ + tmp = FIO_MEM_SYS_ALLOC_def_func(new_len, alignment_log); + if (!tmp) { + FIO_LOG_ERROR("sysem realloc failed to allocate memory."); + return NULL; + } + FIO_MEMCPY(tmp, mem, old_len); + FIO_MEM_SYS_FREE_def_func(mem, old_len); + mem = tmp; + } else if (old_len > new_len) { + /* shrink allocation */ + if (!VirtualFree((void *)((uintptr_t)mem + new_len), + old_len - new_len, + MEM_DECOMMIT)) + FIO_LOG_ERROR("failed to decommit memory range @ %p.", mem); + } + return mem; +free_mem: + FIO_MEM_SYS_FREE_def_func(mem, old_len); + mem = NULL; + return NULL; +} + +/* ***************************************************************************** + + +Unknown OS... Unsupported? + + +***************************************************************************** */ +#else /* FIO_OS_POSIX / FIO_OS_WIN => unknown...? */ + +FIO_IFUNC void *FIO_MEM_SYS_ALLOC_def_func(size_t bytes, + uint8_t alignment_log) { + // return aligned_alloc((pages << 12), (1UL << alignment_log)); + exit(-1); + (void)bytes; + (void)alignment_log; +} + +FIO_IFUNC void *FIO_MEM_SYS_REALLOC_def_func(void *mem, + size_t old_len, + size_t new_len, + uint8_t alignment_log) { + (void)old_len; + (void)alignment_log; + new_len = FIO_MEM_BYTES2PAGES(new_len); + return realloc(mem, new_len); +} + +FIO_IFUNC void FIO_MEM_SYS_FREE_def_func(void *mem, size_t bytes) { + free(mem); + (void)bytes; +} + +#endif /* FIO_OS_POSIX / FIO_OS_WIN */ +/* ***************************************************************************** +Overridable system allocation macros +***************************************************************************** */ +#ifndef FIO_MEM_SYS_ALLOC +#define FIO_MEM_SYS_ALLOC(pages, alignment_log) \ + FIO_MEM_SYS_ALLOC_def_func((pages), (alignment_log)) +#define FIO_MEM_SYS_REALLOC(ptr, old_pages, new_pages, alignment_log) \ + FIO_MEM_SYS_REALLOC_def_func((ptr), (old_pages), (new_pages), (alignment_log)) +#define FIO_MEM_SYS_FREE(ptr, pages) FIO_MEM_SYS_FREE_def_func((ptr), (pages)) +#endif /* FIO_MEM_SYS_ALLOC */ + +#endif /* H___FIO_MEM_INCLUDE_ONCE___H */ + +/* ***************************************************************************** +FIO_MEMORY_DISABLE - use the system allocator +***************************************************************************** */ +#if defined(FIO_MALLOC_TMP_USE_SYSTEM) + +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size) { +#if FIO_MEMORY_INITIALIZE_ALLOCATIONS + return calloc(size, 1); +#elif defined(DEBUG) && DEBUG + void *ret = malloc(size); + if (ret) + FIO_MEMSET(ret, 0xFA, size); + return ret; +#else + return malloc(size); +#endif +} +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, + calloc)(size_t size_per_unit, + size_t unit_count) { + return calloc(size_per_unit, unit_count); +} +SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr) { free(ptr); } +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr, + size_t new_size) { + return realloc(ptr, new_size); +} +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr, + size_t new_size, + size_t copy_len) { + return realloc(ptr, new_size); + (void)copy_len; +} +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size) { + return calloc(size, 1); +} + +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void) {} +/** Prints the allocator's data structure. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void) {} +/** Prints the allocator's free block list. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void) {} +/** Prints the settings used to define the allocator. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void) {} +SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void) { return 0; } + +#ifdef FIO_TEST_ALL +SFUNC void FIO_NAME_TEST(FIO_NAME(stl, FIO_MEMORY_NAME), mem)(void) { + fprintf(stderr, "* Custom memory allocator bypassed.\n"); +} +#endif /* FIO_TEST_ALL */ + +#else /* FIO_MEMORY_DISABLE */ + +/* ***************************************************************************** + + + + + + Memory allocation implementation starts here + helper function and setup are complete + + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Lock type choice +***************************************************************************** */ +#if FIO_MEMORY_USE_THREAD_MUTEX +#define FIO_MEMORY_LOCK_TYPE fio_thread_mutex_t +#define FIO_MEMORY_LOCK_TYPE_INIT(lock) \ + ((lock) = (fio_thread_mutex_t)FIO_THREAD_MUTEX_INIT) +#define FIO_MEMORY_TRYLOCK(lock) fio_thread_mutex_trylock(&(lock)) +#define FIO_MEMORY_LOCK(lock) fio_thread_mutex_lock(&(lock)) +#define FIO_MEMORY_UNLOCK(lock) \ + do { \ + int tmp__ = fio_thread_mutex_unlock(&(lock)); \ + if (tmp__) { \ + FIO_LOG_ERROR("Couldn't free mutex! error (%d): %s", \ + tmp__, \ + strerror(tmp__)); \ + } \ + } while (0) + +#define FIO_MEMORY_LOCK_NAME "pthread_mutex" +#else +#define FIO_MEMORY_LOCK_TYPE fio_lock_i +#define FIO_MEMORY_LOCK_TYPE_INIT(lock) ((lock) = FIO_LOCK_INIT) +#define FIO_MEMORY_TRYLOCK(lock) fio_trylock(&(lock)) +#define FIO_MEMORY_LOCK(lock) fio_lock(&(lock)) +#define FIO_MEMORY_UNLOCK(lock) fio_unlock(&(lock)) +#define FIO_MEMORY_LOCK_NAME "facil.io spinlocks" +#endif + +/* ***************************************************************************** +Allocator debugging helpers +***************************************************************************** */ + +#if defined(DEBUG) || defined(FIO_LEAK_COUNTER) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MEMORY_NAME, __malloc_chunk)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MEMORY_NAME, malloc)) +static volatile size_t FIO_NAME(FIO_MEMORY_NAME, __malloc_total); +#define FIO_MEMORY_ON_CHUNK_ALLOC(ptr) \ + do { \ + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MEMORY_NAME, __malloc_chunk)); \ + FIO_LOG_DEBUG2("MEMORY CHUNK-ALLOC allocated %p", ptr); \ + } while (0); +#define FIO_MEMORY_ON_CHUNK_FREE(ptr) \ + do { \ + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MEMORY_NAME, __malloc_chunk)); \ + FIO_LOG_DEBUG2("MEMORY CHUNK-DEALLOC de-allocated %p", ptr); \ + } while (0); +#define FIO_MEMORY_ON_CHUNK_CACHE(ptr) \ + do { \ + FIO_LOG_DEBUG2("MEMORY CACHE-PUSH placed %p in cache", ptr); \ + } while (0); +#define FIO_MEMORY_ON_CHUNK_UNCACHE(ptr) \ + do { \ + FIO_LOG_DEBUG2("MEMORY CACHE-POP retrieved %p from cache", ptr); \ + } while (0); + +#define FIO_MEMORY_ON_BLOCK_RESET_IN_LOCK(ptr, blk) \ + do { \ + if (0) \ + FIO_LOG_DEBUG2("MEMORY chunk %p block %zu reset in lock", \ + ptr, \ + (size_t)blk); \ + } while (0); + +#define FIO_MEMORY_ON_BIG_BLOCK_SET(ptr) \ + do { \ + if (1) \ + FIO_LOG_DEBUG2("MEMORY chunk %p used as big-block", ptr); \ + } while (0); + +#define FIO_MEMORY_ON_BIG_BLOCK_UNSET(ptr) \ + do { \ + if (1) \ + FIO_LOG_DEBUG2("MEMORY chunk %p no longer used as big-block", ptr); \ + } while (0); +#define FIO_MEMORY_PRINT_STATS_END() \ + do { \ + FIO_LOG_DEBUG2( \ + "(" FIO_MACRO2STR( \ + FIO_NAME(FIO_MEMORY_NAME, malloc)) ") total allocations: %zu", \ + FIO_NAME(FIO_MEMORY_NAME, __malloc_total)); \ + } while (0) +#define FIO_MEMORY_ON_ALLOC_FUNC() \ + do { \ + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MEMORY_NAME, malloc)); \ + fio_atomic_add(&FIO_NAME(FIO_MEMORY_NAME, __malloc_total), 1); \ + } while (0) +#define FIO_MEMORY_ON_FREE_FUNC() \ + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MEMORY_NAME, malloc)) +#else /* defined(DEBUG) || defined(FIO_LEAK_COUNTER) */ +#define FIO_MEMORY_ON_CHUNK_ALLOC(ptr) ((void)0) +#define FIO_MEMORY_ON_CHUNK_FREE(ptr) ((void)0) +#define FIO_MEMORY_ON_CHUNK_CACHE(ptr) ((void)0) +#define FIO_MEMORY_ON_CHUNK_UNCACHE(ptr) ((void)0) +#define FIO_MEMORY_ON_BLOCK_RESET_IN_LOCK(ptr, blk) ((void)0) +#define FIO_MEMORY_ON_BIG_BLOCK_SET(ptr) ((void)0) +#define FIO_MEMORY_ON_BIG_BLOCK_UNSET(ptr) ((void)0) +#define FIO_MEMORY_PRINT_STATS_END() ((void)0) +#define FIO_MEMORY_ON_ALLOC_FUNC() ((void)0) +#define FIO_MEMORY_ON_FREE_FUNC() ((void)0) +#endif /* defined(DEBUG) || defined(FIO_LEAK_COUNTER) */ + +/* ***************************************************************************** + + + + + + +Memory chunk headers and block data (in chunk header) + + + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Chunk and Block data / header +***************************************************************************** */ + +typedef struct { + volatile int32_t ref; + volatile int32_t pos; +} FIO_NAME(FIO_MEMORY_NAME, __mem_block_s); + +typedef struct { + /* the head of the chunk... node->next says a lot */ + uint32_t marker; + volatile int32_t ref; + FIO_NAME(FIO_MEMORY_NAME, __mem_block_s) + blocks[FIO_MEMORY_BLOCKS_PER_ALLOCATION]; +} FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s); + +#if FIO_MEMORY_ENABLE_BIG_ALLOC +/* big-blocks consumes a chunk, sizeof header MUST be <= chunk header */ +typedef struct { + /* marker and ref MUST overlay chunk header */ + uint32_t marker; + volatile int32_t ref; + volatile int32_t pos; +} FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s); +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + +/* ***************************************************************************** +Arena type +***************************************************************************** */ +typedef struct { + void *block; + int32_t last_pos; + FIO_MEMORY_LOCK_TYPE lock; + uint8_t pad_for_cache___[115]; /* cache line padding */ +} FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s); + +/* ***************************************************************************** +Allocator State +***************************************************************************** */ + +typedef struct FIO_NAME(FIO_MEMORY_NAME, __mem_state_s) + FIO_NAME(FIO_MEMORY_NAME, __mem_state_s); + +static struct FIO_NAME(FIO_MEMORY_NAME, __mem_state_s) { +#if FIO_MEMORY_CACHE_SLOTS + /** cache array container for available memory chunks */ + struct { + /* chunk slot array */ + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * a[FIO_MEMORY_CACHE_SLOTS]; + size_t pos; + } cache; +#endif /* FIO_MEMORY_CACHE_SLOTS */ + +#if FIO_MEMORY_ENABLE_BIG_ALLOC + /** a block for big allocations, shared (no arena) */ + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) * big_block; + int32_t big_last_pos; + /** big allocation lock */ + FIO_MEMORY_LOCK_TYPE big_lock; + uint8_t pad_for_cache___[115]; /* cache line padding */ +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + /** main memory state lock */ + FIO_MEMORY_LOCK_TYPE lock; + /** free list for available blocks */ + FIO_LIST_HEAD blocks; + /** the arena count for the allocator */ + uint8_t pad_for_cache2___[111]; /* cache line padding */ + size_t arena_count; + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s) arena[]; +} * FIO_NAME(FIO_MEMORY_NAME, __mem_state); + +/* ***************************************************************************** +Arena assignment +***************************************************************************** */ + +/* SublimeText marker */ +void fio___mem_arena_unlock___(void); +/** Unlocks the thread's arena. */ +FIO_SFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_arena_unlock)( + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s) * a) { + FIO_ASSERT_DEBUG(a, "unlocking a NULL arena?!"); + FIO_MEMORY_UNLOCK(a->lock); +} + +/* SublimeText marker */ +void fio___mem_arena_lock___(void); + +/** Locks and returns the thread's arena. */ +FIO_SFUNC FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s) * + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_lock)(void) { +#if FIO_MEMORY_ARENA_COUNT == 1 + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[0].lock); + return FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena; + +#else /* FIO_MEMORY_ARENA_COUNT != 1 */ + +#if defined(DEBUG) && FIO_MEMORY_ARENA_COUNT > 0 && !defined(FIO_TEST_ALL) + static size_t warning_printed = 0; +#define FIO___MEMORY_ARENA_LOCK_WARNING() \ + do { \ + if (!warning_printed) \ + FIO_LOG_WARNING(FIO_MACRO2STR(FIO_NAME( \ + FIO_MEMORY_NAME, \ + malloc)) " high arena contention.\n" \ + " Consider recompiling with more arenas."); \ + warning_printed = 1; \ + } while (0) +#else /* !DEBUG || FIO_MEMORY_ARENA_COUNT <= 0 */ +#define FIO___MEMORY_ARENA_LOCK_WARNING() +#endif + /** thread arena value */ + size_t arena_index; + size_t loop_count = 0; + { + /* select the default arena selection using a thread ID. */ + union { + void *p; + fio_thread_t t; + } u = {.t = fio_thread_current()}; + arena_index = (fio_risky_ptr(u.p) & 127) % + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count; +#if (defined(DEBUG) && 0) + static void *pthread_last = NULL; + if (pthread_last != u.p) { + FIO_LOG_DEBUG( + "thread %p (%p) associated with arena %zu / %zu", + u.p, + (void *)fio_risky_ptr(u.p), + arena_index, + (size_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count); + pthread_last = u.p; + } +#endif + } + for (;;) { + /* rotate all arenas to find one that's available */ + if (!FIO_MEMORY_TRYLOCK( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[arena_index].lock)) + return (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena + arena_index); + FIO_LOG_DDEBUG("thread %p had to switch arena from %zu / %zu", + fio_thread_current(), + arena_index, + (size_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count); + ++arena_index; + if (arena_index == FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count) + arena_index = 0; + if (++loop_count < + (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count << 1)) + continue; + FIO___MEMORY_ARENA_LOCK_WARNING(); +#undef FIO___MEMORY_ARENA_LOCK_WARNING +#if FIO_MEMORY_USE_THREAD_MUTEX && FIO_OS_POSIX + /* slow wait for arena */ + FIO_MEMORY_LOCK( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[arena_index].lock); + return FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena + arena_index; +#else + // FIO_THREAD_RESCHEDULE(); +#endif /* FIO_MEMORY_USE_THREAD_MUTEX */ + } +#endif /* FIO_MEMORY_ARENA_COUNT != 1 */ +} + +/* ***************************************************************************** +Converting between chunk & block data to pointers (and back) +***************************************************************************** */ + +#define FIO_MEMORY_HEADER_SIZE \ + ((sizeof(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s)) + \ + (FIO_MEMORY_ALIGN_SIZE - 1)) & \ + (~(FIO_MEMORY_ALIGN_SIZE - 1))) + +#define FIO_MEMORY_BLOCK_SIZE \ + (((FIO_MEMORY_SYS_ALLOCATION_SIZE - FIO_MEMORY_HEADER_SIZE) / \ + FIO_MEMORY_BLOCKS_PER_ALLOCATION) & \ + (~(FIO_MEMORY_ALIGN_SIZE - 1))) + +#define FIO_MEMORY_UNITS_PER_BLOCK \ + (FIO_MEMORY_BLOCK_SIZE / FIO_MEMORY_ALIGN_SIZE) + +/* SublimeText marker */ +void fio___mem_chunk2ptr___(void); +/** returns a pointer within a chunk, given it's block and offset value. */ +FIO_IFUNC void *FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c, + size_t block, + size_t offset) { + return (void *)(((uintptr_t)(c) + FIO_MEMORY_HEADER_SIZE) + + (block * FIO_MEMORY_BLOCK_SIZE) + + (offset << FIO_MEMORY_ALIGN_LOG)); +} + +/* SublimeText marker */ +void fio___mem_ptr2chunk___(void); +/** returns a chunk given a pointer. */ +FIO_IFUNC FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(void *p) { + return FIO_PTR_MATH_RMASK(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s), + p, + FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG); +} + +/* SublimeText marker */ +void fio___mem_ptr2index___(void); +/** returns a pointer's block index within it's chunk. */ +FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c, + void *p) { + FIO_ASSERT_DEBUG(c == FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(p), + "chunk-pointer offset argument error"); + size_t i = + (size_t)FIO_PTR_MATH_LMASK(void, p, FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG); + i -= FIO_MEMORY_HEADER_SIZE; + i /= FIO_MEMORY_BLOCK_SIZE; + return i; + (void)c; +} + +/* ***************************************************************************** +Allocator State Initialization & Cleanup +***************************************************************************** */ +#define FIO_MEMORY_STATE_SIZE(arena_count) \ + FIO_MEM_BYTES2PAGES( \ + (sizeof(*FIO_NAME(FIO_MEMORY_NAME, __mem_state)) + \ + (sizeof(FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s)) * (arena_count)))) + +/* function declarations for functions called during cleanup */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_dealloc)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c); +FIO_IFUNC void *FIO_NAME(FIO_MEMORY_NAME, __mem_block_new)(void); +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_block_free)(void *ptr); +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_free)(void *ptr); +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_free)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c); + +/* IDE marker */ +void fio___mem_state_cleanup___(void); +FIO_SFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_state_cleanup)(void *ignr_) { + if (!FIO_NAME(FIO_MEMORY_NAME, __mem_state)) { + FIO_LOG_DEBUG2(FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, + __mem_state_cleanup)) " called more than once (NULL state)."); + return; + } + (void)ignr_; + FIO_LOG_DDEBUG2( + "starting facil.io memory allocator cleanup for " FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, malloc)) "."); + /* free arena blocks */ + for (size_t i = 0; i < FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count; + ++i) { + if (FIO_MEMORY_TRYLOCK( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].lock)) { + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].lock); + FIO_LOG_ERROR(FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, + malloc)) "cleanup called while some arenas are in use!"); + } + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].lock); + FIO_NAME(FIO_MEMORY_NAME, __mem_block_free) + (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block); + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block = NULL; + FIO_MEMORY_LOCK_TYPE_INIT( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].lock); + } + +#if FIO_MEMORY_ENABLE_BIG_ALLOC + /* cleanup big-alloc chunk */ + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block) { + if ((uint32_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block->ref > 1) { + FIO_LOG_WARNING("(" FIO_MACRO2STR(FIO_NAME( + FIO_MEMORY_NAME, + malloc)) ") active big-block reference count error at %p\n" + " Possible memory leaks for big-block allocation."); + } + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_free) + (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block); + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block = NULL; + FIO_MEMORY_LOCK_TYPE_INIT(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + } +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + +#if FIO_MEMORY_CACHE_SLOTS + /* deallocate all chunks in the cache */ + while (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos) { + const size_t pos = --FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos; + FIO_MEMORY_ON_CHUNK_UNCACHE( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.a[pos]); + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_dealloc) + (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.a[pos]); + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.a[pos] = NULL; + } +#endif /* FIO_MEMORY_CACHE_SLOTS */ + + /* report any blocks in the allocation list - even if not in DEBUG mode */ + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks.next != + &FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks) { + struct t_s { + FIO_LIST_NODE node; + }; + void *last_chunk = NULL; + FIO_LOG_WARNING("(" FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, + malloc)) ") blocks left after cleanup - memory leaks?"); + FIO_LIST_EACH(struct t_s, + node, + &FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks, + pos) { + if (last_chunk == (void *)FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(pos)) + continue; + last_chunk = (void *)FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(pos); + FIO_LOG_WARNING( + "(" FIO_MACRO2STR(FIO_NAME(FIO_MEMORY_NAME, + malloc)) ") leaked block(s) for chunk %p", + (void *)pos, + last_chunk); + } + } + + /* dealloc the state machine */ + const size_t s = FIO_MEMORY_STATE_SIZE( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count); + FIO_MEM_SYS_FREE(FIO_NAME(FIO_MEMORY_NAME, __mem_state), s); + FIO_NAME(FIO_MEMORY_NAME, __mem_state) = + (FIO_NAME(FIO_MEMORY_NAME, __mem_state_s *))NULL; + + FIO_MEMORY_PRINT_STATS_END(); + FIO_LOG_DDEBUG2( + "finished facil.io memory allocator cleanup for " FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, malloc)) "."); +} + +FIO_SFUNC void FIO_NAME(FIO_MEMORY_NAME, + __malloc_after_fork_task)(void *ignr_) { + (void)ignr_; + FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(); +} + +/* initializes (allocates) the arenas and state machine */ +FIO_CONSTRUCTOR(FIO_NAME(FIO_MEMORY_NAME, __mem_state_setup)) { + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)) + return; + fio_state_callback_add(FIO_CALL_IN_CHILD, + FIO_NAME(FIO_MEMORY_NAME, __malloc_after_fork_task), + NULL); + fio_state_callback_add(FIO_CALL_AT_EXIT, + FIO_NAME(FIO_MEMORY_NAME, __mem_state_cleanup), + NULL); + /* allocate the state machine */ + { +#if FIO_MEMORY_ARENA_COUNT > 0 + size_t const arena_count = FIO_MEMORY_ARENA_COUNT; +#else + size_t arena_count = FIO_MEMORY_ARENA_COUNT_FALLBACK; +#ifdef _SC_NPROCESSORS_ONLN + arena_count = sysconf(_SC_NPROCESSORS_ONLN); + if (arena_count == (size_t)-1UL) + arena_count = FIO_MEMORY_ARENA_COUNT_FALLBACK; + else /* arenas !> threads (birthday) */ + arena_count = (arena_count << 1) + 2; +#else /* FIO_MEMORY_ARENA_COUNT <= 0 */ +#if _MSC_VER || __MINGW32__ + /* https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + */ + SYSTEM_INFO win_system_info; + GetSystemInfo(&win_system_info); + arena_count = (size_t)win_system_info.dwNumberOfProcessors; +#else +#warning Dynamic CPU core count is unavailable - assuming FIO_MEMORY_ARENA_COUNT_FALLBACK cores. +#endif +#endif /* _SC_NPROCESSORS_ONLN */ +#if FIO_MEMORY_ARENA_COUNT < -1 + arena_count = arena_count / (0 - FIO_MEMORY_ARENA_COUNT); +#endif + if (arena_count >= FIO_MEMORY_ARENA_COUNT_MAX) + arena_count = FIO_MEMORY_ARENA_COUNT_MAX; + if (!arena_count) + arena_count = 1; + +#endif /* FIO_MEMORY_ARENA_COUNT <= 0 */ + + const size_t s = FIO_MEMORY_STATE_SIZE(arena_count); + FIO_NAME(FIO_MEMORY_NAME, __mem_state) = + (FIO_NAME(FIO_MEMORY_NAME, __mem_state_s *))FIO_MEM_SYS_ALLOC(s, 0); + FIO_ASSERT_ALLOC(FIO_NAME(FIO_MEMORY_NAME, __mem_state)); + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count = arena_count; + } + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks = + FIO_LIST_INIT(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks); + FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(); + +#if defined(FIO_MEMORY_WARMUP) && FIO_MEMORY_WARMUP + for (size_t i = 0; i < (size_t)FIO_MEMORY_WARMUP && + i < FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count; + ++i) { + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block = + FIO_NAME(FIO_MEMORY_NAME, __mem_block_new)(); + } +#endif +#ifdef DEBUG + FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(); +#endif /* DEBUG */ + (void)FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list); + (void)FIO_NAME(FIO_MEMORY_NAME, malloc_print_state); + (void)FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings); +} + +/* SublimeText marker */ +void fio_after_fork___(void); +/** + * When forking is called manually, call this function to reset the facil.io + * memory allocator's locks. + */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void) { + if (!FIO_NAME(FIO_MEMORY_NAME, __mem_state)) { + FIO_NAME(FIO_MEMORY_NAME, __mem_state_setup)(); + return; + } + FIO_LOG_DEBUG2("MEMORY reinitializing " FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, malloc)) " state"); + FIO_MEMORY_LOCK_TYPE_INIT(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); +#if FIO_MEMORY_ENABLE_BIG_ALLOC + FIO_MEMORY_LOCK_TYPE_INIT(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + for (size_t i = 0; i < FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count; + ++i) { + FIO_MEMORY_LOCK_TYPE_INIT( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].lock); + } +} + +/* ***************************************************************************** +Memory Allocation - state printing (debug helper) +***************************************************************************** */ + +/* SublimeText marker */ +void fio_malloc_print_state___(void); +/** Prints the allocator's data structure. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void) { + fprintf( + stderr, + FIO_MACRO2STR(FIO_NAME(FIO_MEMORY_NAME, malloc)) " allocator state:\n"); + for (size_t i = 0; i < FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count; + ++i) { + fprintf(stderr, + "\t* arena[%zu] block: %p\n", + i, + (void *)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block); + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block) { + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)( + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block); + size_t b = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)( + c, + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena[i].block); + fprintf(stderr, "\t\tchunk-ref: %zu (%p)\n", (size_t)c->ref, (void *)c); + fprintf(stderr, + "\t\t- block[%zu]-ref: %zu\n" + "\t\t- block[%zu]-pos: %zu\n", + b, + (size_t)c->blocks[b].ref, + b, + (size_t)c->blocks[b].pos); + } + } +#if FIO_MEMORY_ENABLE_BIG_ALLOC + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block) { + fprintf(stderr, "\t---big allocations---\n"); + fprintf(stderr, + "\t* big-block: %p\n" + "\t\t ref: %zu\n" + "\t\t pos: %zu\n", + (void *)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block, + (size_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block->ref, + (size_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block->pos); + } else { + fprintf(stderr, + "\t---big allocations---\n" + "\t* big-block: NULL\n"); + } + +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + +#if FIO_MEMORY_CACHE_SLOTS + fprintf(stderr, "\t---caches---\n"); + for (size_t i = 0; i < FIO_MEMORY_CACHE_SLOTS; ++i) { + fprintf(stderr, + "\t* cache[%zu] chunk: %p\n", + i, + (void *)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.a[i]); + } +#endif /* FIO_MEMORY_CACHE_SLOTS */ +} + +void fio_malloc_print_free_block_list___(void); +/** Prints the allocator's free block list. May be used for debugging. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void) { + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks.prev == + &FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks) + return; + fprintf(stderr, + FIO_MACRO2STR(FIO_NAME(FIO_MEMORY_NAME, + malloc)) " allocator free block list:\n"); + FIO_LIST_NODE *n = FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks.prev; + for (size_t i = 0; n != &FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks; + ++i) { + fprintf(stderr, "\t[%zu] %p\n", i, (void *)n); + n = n->prev; + } +} + +/* ***************************************************************************** +chunk allocation / deallocation +***************************************************************************** */ + +/* SublimeText marker */ +void fio___mem_chunk_dealloc___(void); +/* returns memory to system */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_dealloc)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c) { + if (!c) + return; + FIO_MEMORY_ON_CHUNK_FREE(c); + FIO_MEM_SYS_FREE(((void *)c), FIO_MEMORY_SYS_ALLOCATION_SIZE); +} + +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_cache_or_dealloc)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c) { +#if FIO_MEMORY_CACHE_SLOTS + /* place in cache...? */ + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos < + FIO_MEMORY_CACHE_SLOTS) { + FIO_MEMORY_ON_CHUNK_CACHE(c); + FIO_NAME(FIO_MEMORY_NAME, __mem_state) + ->cache.a[FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos++] = c; + c = NULL; + } +#endif /* FIO_MEMORY_CACHE_SLOTS */ + + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_dealloc)(c); +} + +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_free)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c) { + /* should we free the chunk? */ + if (!c || fio_atomic_sub_fetch(&c->ref, 1)) { + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + return; + } + + /* remove all blocks from the block allocation list */ + for (size_t b = 0; b < FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++b) { + FIO_LIST_NODE *n = + (FIO_LIST_NODE *)FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0); + if (n->prev && n->next) { + FIO_LIST_REMOVE(n); + n->prev = n->next = NULL; + } + } + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_cache_or_dealloc)(c); +} + +/* SublimeText marker */ +void fio___mem_chunk_new___(void); +/* UNSAFE! returns a clean chunk (cache / allocation). */ +FIO_IFUNC FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_new)(const size_t needs_lock) { + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = NULL; +#if FIO_MEMORY_CACHE_SLOTS + /* cache allocation */ + if (needs_lock) { + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + } + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos) { + c = FIO_NAME(FIO_MEMORY_NAME, __mem_state) + ->cache.a[--FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos]; + FIO_NAME(FIO_MEMORY_NAME, __mem_state) + ->cache.a[FIO_NAME(FIO_MEMORY_NAME, __mem_state)->cache.pos] = NULL; + } + if (needs_lock) { + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + } + if (c) { + FIO_MEMORY_ON_CHUNK_UNCACHE(c); + *c = (FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s)){.ref = 1}; + return c; + } +#endif /* FIO_MEMORY_CACHE_SLOTS */ + + /* system allocation */ + c = (FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *)FIO_MEM_SYS_ALLOC( + FIO_MEMORY_SYS_ALLOCATION_SIZE, + FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG); + + if (!c) + return c; + FIO_MEMORY_ON_CHUNK_ALLOC(c); + c->ref = 1; + return c; + (void)needs_lock; /* in case it isn't used */ +} + +/* ***************************************************************************** +block allocation / deallocation +***************************************************************************** */ + +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_block__reset_memory)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c, + size_t b) { +#if FIO_MEMORY_INITIALIZE_ALLOCATIONS + if (c->blocks[b].pos >= (int32_t)(FIO_MEMORY_UNITS_PER_BLOCK - 4)) { + /* zero out the whole block */ + FIO_MEMSET(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0), + 0, + FIO_MEMORY_BLOCK_SIZE); + } else { + /* zero out only the memory that was used */ + FIO_MEMSET(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0), + 0, + (((size_t)c->blocks[b].pos) << FIO_MEMORY_ALIGN_LOG)); + } +#elif defined(DEBUG) && DEBUG + /* set all bytes to 0xAF to better catch initialization bugs */ + FIO_MEMSET(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0), + 0xFA, + FIO_MEMORY_BLOCK_SIZE); +#else + /** only reset a block's free-list header */ + FIO_MEMSET(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0), + 0, + (((FIO_MEMORY_ALIGN_SIZE - 1) + sizeof(FIO_LIST_NODE)) & + (~(FIO_MEMORY_ALIGN_SIZE - 1)))); +#endif /*FIO_MEMORY_INITIALIZE_ALLOCATIONS*/ + c->blocks[b].pos = 0; +} + +/* SublimeText marker */ +void fio___mem_block_free___(void); +/** frees a block / decreases it's reference count */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_block_free)(void *p) { + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(p); + size_t b = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)(c, p); + if (!c) + return; + FIO_ASSERT_DEBUG( + (uint32_t)c->blocks[b].ref <= FIO_MEMORY_UNITS_PER_BLOCK + 1, + "block reference count corrupted, possible double free? (%zd)", + (size_t)c->blocks[b].ref); + FIO_ASSERT_DEBUG( + (uint32_t)c->blocks[b].pos <= FIO_MEMORY_UNITS_PER_BLOCK + 1, + "block allocation position corrupted, possible double free? (%zd)", + (size_t)c->blocks[b].pos); + if (fio_atomic_sub_fetch(&c->blocks[b].ref, 1)) + return; + + /* reset memory */ + FIO_NAME(FIO_MEMORY_NAME, __mem_block__reset_memory)(c, b); + + /* place in free list */ + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + FIO_LIST_NODE *n = + (FIO_LIST_NODE *)FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0); + FIO_LIST_PUSH(&FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks, n); + /* free chunk reference while in locked state */ + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_free)(c); +} + +/* SublimeText marker */ +void fio___mem_block_new___(void); +/** returns a new block with a reference count of 1 */ +FIO_IFUNC void *FIO_NAME(FIO_MEMORY_NAME, __mem_block_new)(void) { + void *p = NULL; + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = NULL; + size_t b; + + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + + /* try to collect from list */ + if (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks.prev != + &FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks) { + FIO_LIST_NODE *n = FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks.prev; + FIO_LIST_REMOVE(n); + n->next = n->prev = NULL; + c = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)((void *)n); + fio_atomic_add_fetch(&c->ref, 1); + p = (void *)n; + b = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)(c, p); + goto done; + } + + /* allocate from cache / system (sets chunk reference to 1) */ + c = FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_new)(0); + if (!c) + goto done; + + /* use the first block in the chunk as the new block */ + p = FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, 0, 0); + + /* place the rest of the blocks in the block allocation list */ + for (b = 1; b < FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++b) { + FIO_LIST_NODE *n = + (FIO_LIST_NODE *)FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0); + FIO_LIST_PUSH(&FIO_NAME(FIO_MEMORY_NAME, __mem_state)->blocks, n); + } + /* set block index to zero */ + b = 0; + +done: + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + if (!p) + return p; + /* update block reference and allocation position */ + c->blocks[b].ref = 1; + c->blocks[b].pos = 0; + return p; +} + +/* ***************************************************************************** +Small allocation internal API +***************************************************************************** */ + +/* SublimeText marker */ +void fio___mem_slice_new___(void); +/** slice a block to allocate a set number of bytes. */ +FIO_SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, + __mem_slice_new)(size_t bytes, + void *is_realloc) { + void *p = NULL; + bytes = (bytes + ((1UL << FIO_MEMORY_ALIGN_LOG) - 1)) >> FIO_MEMORY_ALIGN_LOG; + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_s) *a = + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_lock)(); + + if (!a->block) { + a->block = FIO_NAME(FIO_MEMORY_NAME, __mem_block_new)(); + a->last_pos = 0; + } + for (;;) { + if (!a->block) + goto no_mem; + void *const block = a->block; + + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *const c = + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(block); + const size_t b = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)(c, block); + + /* add allocation reference */ + /* if we are the only thread holding a reference to this block - reset. */ + if (fio_atomic_add(&c->blocks[b].ref, 1) == 1 && c->blocks[b].pos) { + FIO_NAME(FIO_MEMORY_NAME, __mem_block__reset_memory)(c, b); + FIO_MEMORY_ON_BLOCK_RESET_IN_LOCK(c, b); + a->last_pos = 0; + } + + /* enough space? allocate */ + if (c->blocks[b].pos + bytes < FIO_MEMORY_UNITS_PER_BLOCK) { + /* a lucky realloc? */ + if (is_realloc && + is_realloc == + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, a->last_pos)) { + c->blocks[b].pos += bytes; + fio_atomic_sub(&c->blocks[b].ref, 1); /* release reference added */ + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_unlock)(a); + return is_realloc; + } + p = FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, c->blocks[b].pos); + a->last_pos = c->blocks[b].pos; + c->blocks[b].pos += bytes; + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_unlock)(a); + return p; + } + is_realloc = NULL; + + /* + * allocate a new block before freeing the existing block + * this prevents the last chunk from de-allocating and reallocating + */ + a->block = FIO_NAME(FIO_MEMORY_NAME, __mem_block_new)(); + a->last_pos = 0; + + /* release allocation reference added */ + fio_atomic_sub(&c->blocks[b].ref, 1); + /* release the reference held by the arena (allocator) */ + FIO_NAME(FIO_MEMORY_NAME, __mem_block_free)(block); + } + +no_mem: + FIO_NAME(FIO_MEMORY_NAME, __mem_arena_unlock)(a); + errno = ENOMEM; + return p; +} + +/* SublimeText marker */ +void fio_____mem_slice_free___(void); +/** slice a block to allocate a set number of bytes. */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_slice_free)(void *p) { + FIO_NAME(FIO_MEMORY_NAME, __mem_block_free)(p); +} + +/* ***************************************************************************** +big block allocation / de-allocation +***************************************************************************** */ +#if FIO_MEMORY_ENABLE_BIG_ALLOC + +#define FIO_MEMORY_BIG_BLOCK_MARKER ((~(uint32_t)0) << 2) +#define FIO_MEMORY_BIG_BLOCK_HEADER_SIZE \ + (((sizeof(FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s)) + \ + ((FIO_MEMORY_ALIGN_SIZE - 1))) & \ + ((~(0UL)) << FIO_MEMORY_ALIGN_LOG))) + +#define FIO_MEMORY_BIG_BLOCK_SIZE \ + (FIO_MEMORY_SYS_ALLOCATION_SIZE - FIO_MEMORY_BIG_BLOCK_HEADER_SIZE) + +#define FIO_MEMORY_UNITS_PER_BIG_BLOCK \ + (FIO_MEMORY_BIG_BLOCK_SIZE / FIO_MEMORY_ALIGN_SIZE) + +/* SublimeText marker */ +void fio___mem_big_block__reset_memory___(void); +/** zeros out a big-block's memory, keeping it's reference count at 1. */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_big_block__reset_memory)( + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) * b) { + +#if FIO_MEMORY_INITIALIZE_ALLOCATIONS + /* zero out memory */ + if (b->pos >= (int32_t)(FIO_MEMORY_UNITS_PER_BIG_BLOCK - 10)) { + /* zero out everything */ + FIO_MEMSET((void *)b, 0, FIO_MEMORY_SYS_ALLOCATION_SIZE); + } else { + /* zero out only the used part of the memory */ + FIO_MEMSET((void *)b, + 0, + (((size_t)b->pos << FIO_MEMORY_ALIGN_LOG) + + FIO_MEMORY_BIG_BLOCK_HEADER_SIZE)); + } +#else +#if defined(DEBUG) && DEBUG + /* set all bytes to 0xAF to better catch initialization bugs */ + FIO_MEMSET((void *)b, 0xFA, FIO_MEMORY_SYS_ALLOCATION_SIZE); +#endif /* DEBUG */ + /* reset chunk header, which is always bigger than big_block header*/ + FIO_MEMSET((void *)b, 0, sizeof(FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s))); + /* zero out possible block memory (if required) */ + for (size_t i = 0; i < FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++i) { + FIO_NAME(FIO_MEMORY_NAME, __mem_block__reset_memory) + ((FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *)b, i); + } +#endif /* FIO_MEMORY_INITIALIZE_ALLOCATIONS */ + b->ref = 1; +} + +/* SublimeText marker */ +void fio___mem_big_block_free___(void); +/** frees a block / decreases it's reference count */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_free)(void *p) { + // FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) ; + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) *b = + (FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) *) + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(p); + /* should we free the block? */ + if (!b || fio_atomic_sub_fetch(&b->ref, 1)) + return; + FIO_MEMORY_ON_BIG_BLOCK_UNSET(b); + + /* zero out memory */ + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block__reset_memory)(b); +#if FIO_MEMORY_CACHE_SLOTS + /* lock for chunk de-allocation review () */ + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->lock); + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_cache_or_dealloc) + ((FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *)b); +#else + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_dealloc) + ((FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *)b); +#endif +} + +/* SublimeText marker */ +void fio___mem_big_block_new___(void); +/** returns a new block with a reference count of 1 */ +FIO_IFUNC FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) * + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_new)(void) { + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) *b = + (FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) *) + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_new)(1); + if (!b) + goto no_mem; + b->marker = FIO_MEMORY_BIG_BLOCK_MARKER; + b->ref = 1; + b->pos = 0; + FIO_MEMORY_ON_BIG_BLOCK_SET(b); + return b; +no_mem: + errno = ENOMEM; + return b; +} + +/* ***************************************************************************** +Big allocation internal API +***************************************************************************** */ + +/* SublimeText marker */ +void fio___mem_big2ptr___(void); +/** returns a pointer within a chunk, given it's block and offset value. */ +FIO_IFUNC void *FIO_NAME(FIO_MEMORY_NAME, __mem_big2ptr)( + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) * b, + size_t offset) { + return (void *)(((uintptr_t)(b) + FIO_MEMORY_BIG_BLOCK_HEADER_SIZE) + + (offset << FIO_MEMORY_ALIGN_LOG)); +} + +/* SublimeText marker */ +void fio___mem_big_slice_new___(void); +FIO_SFUNC void *FIO_MEM_ALIGN_NEW +FIO_NAME(FIO_MEMORY_NAME, __mem_big_slice_new)(size_t bytes, void *is_realloc) { + void *p = NULL; + bytes = (bytes + ((1UL << FIO_MEMORY_ALIGN_LOG) - 1)) >> FIO_MEMORY_ALIGN_LOG; + for (;;) { + FIO_MEMORY_LOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + if (!FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block) { + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block = + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_new)(); + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_last_pos = 0; + } + + if (!FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block) + goto done; + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_s) *b = + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block; + + /* are we the only thread holding a reference to this block... reset? */ + if (b->ref == 1 && b->pos) { + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block__reset_memory)(b); + FIO_MEMORY_ON_BLOCK_RESET_IN_LOCK(b, 0); + b->marker = FIO_MEMORY_BIG_BLOCK_MARKER; + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_last_pos = 0; + } + + /* enough space? */ + if (b->pos + bytes < FIO_MEMORY_UNITS_PER_BIG_BLOCK) { + /* a lucky realloc? */ + if (is_realloc && + is_realloc == + FIO_NAME(FIO_MEMORY_NAME, __mem_big2ptr)( + b, + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_last_pos)) { + b->pos += bytes; + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + return is_realloc; + } + + p = FIO_NAME(FIO_MEMORY_NAME, __mem_big2ptr)(b, b->pos); + fio_atomic_add(&b->ref, 1); /* keep inside lock to enable reset */ + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_last_pos = b->pos; + b->pos += bytes; + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + return p; + } + + is_realloc = NULL; + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_block = NULL; + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_last_pos = 0; + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_free)(b); + } +done: + FIO_MEMORY_UNLOCK(FIO_NAME(FIO_MEMORY_NAME, __mem_state)->big_lock); + return p; +} + +/* SublimeText marker */ +void fio_____mem_big_slice_free___(void); +/** slice a block to allocate a set number of bytes. */ +FIO_IFUNC void FIO_NAME(FIO_MEMORY_NAME, __mem_big_slice_free)(void *p) { + FIO_NAME(FIO_MEMORY_NAME, __mem_big_block_free)(p); +} + +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ +/* ***************************************************************************** +Memory Allocation - malloc(0) pointer +***************************************************************************** */ + +static long double FIO_NAME( + FIO_MEMORY_NAME, + malloc_zero)[((1UL << (FIO_MEMORY_ALIGN_LOG)) / sizeof(long double)) + 1]; + +#define FIO_MEMORY_MALLOC_ZERO_POINTER \ + ((void *)(((uintptr_t)FIO_NAME(FIO_MEMORY_NAME, malloc_zero) + \ + (FIO_MEMORY_ALIGN_SIZE - 1)) & \ + ((~(uintptr_t)0) << FIO_MEMORY_ALIGN_LOG))) + +/* ***************************************************************************** +Memory Allocation - API implementation - debugging and info +***************************************************************************** */ + +/* SublimeText marker */ +void fio_malloc_block_size___(void); +/* public API obligation */ +SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void) { + return FIO_MEMORY_BLOCK_SIZE; +} + +void fio_malloc_arenas___(void); +SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arenas)(void) { + return FIO_NAME(FIO_MEMORY_NAME, __mem_state) + ? FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count + : 0; +} + +SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void) { + // FIO_LOG_DEBUG2( + fprintf( + stderr, + "Custom memory allocator " FIO_MACRO2STR(FIO_NAME( + FIO_MEMORY_NAME, + malloc)) " initialized with:\n" + "\t* system allocation arenas: %zu arenas\n" + "\t* system allocation size: %zu bytes\n" + "\t* system allocation overhead (theoretical): %zu bytes\n" + "\t* system allocation overhead (actual): %zu bytes\n" + "\t* cached system allocations (max): %zu units\n" + "\t* memory block size: %zu bytes\n" + "\t* blocks per system allocation: %zu blocks\n" + "\t* allocation units per block: %zu units\n" + "\t* arena per-allocation limit: %zu bytes\n" + "\t* local per-allocation limit (before mmap): %zu bytes\n" + "\t* allocation alignment (non-zero): %zu bytes\n" + "\t* malloc(0) pointer: %p\n" + "\t* always initializes memory (zero-out): %s\n" + "\t* " FIO_MEMORY_LOCK_NAME " locking system\n", + (size_t)FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count, + (size_t)FIO_MEMORY_SYS_ALLOCATION_SIZE, + (size_t)FIO_MEMORY_HEADER_SIZE, + (size_t)FIO_MEMORY_SYS_ALLOCATION_SIZE % (size_t)FIO_MEMORY_BLOCK_SIZE, + (size_t)FIO_MEMORY_CACHE_SLOTS, + (size_t)FIO_MEMORY_BLOCK_SIZE, + (size_t)FIO_MEMORY_BLOCKS_PER_ALLOCATION, + (size_t)FIO_MEMORY_UNITS_PER_BLOCK, + (size_t)FIO_MEMORY_BLOCK_ALLOC_LIMIT, + (size_t)FIO_MEMORY_ALLOC_LIMIT, + (size_t)FIO_MEMORY_ALIGN_SIZE, + FIO_MEMORY_MALLOC_ZERO_POINTER, + (FIO_MEMORY_INITIALIZE_ALLOCATIONS ? "true" : "false")); +} + +/* ***************************************************************************** +Malloc implementation +***************************************************************************** */ + +/* SublimeText marker */ +void fio___malloc__(void); +/** + * Allocates memory using a per-CPU core block memory pool. + * Memory is zeroed out. + * + * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT will be redirected to `mmap`, + * as if `mempool_mmap` was called. + * + * `mempool_malloc` promises a best attempt at providing locality between + * consecutive calls, but locality can't be guaranteed. + */ +FIO_IFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, + ___malloc)(size_t size, + void *is_realloc) { + void *p = NULL; + if (!size) + goto malloc_zero; + +#if FIO_MEMORY_ENABLE_BIG_ALLOC + if ((is_realloc && size > (FIO_MEMORY_BIG_BLOCK_SIZE - + (FIO_MEMORY_BIG_BLOCK_HEADER_SIZE << 1))) || + (!is_realloc && size > FIO_MEMORY_ALLOC_LIMIT)) +#else + if (!is_realloc && size > FIO_MEMORY_ALLOC_LIMIT) +#endif + { +#ifdef DEBUG + FIO_LOG_WARNING( + "unintended " FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, mmap)) " allocation (slow): %zu bytes", + FIO_MEM_BYTES2PAGES(size)); +#endif + p = FIO_NAME(FIO_MEMORY_NAME, mmap)(size); + return p; + } + if (!FIO_NAME(FIO_MEMORY_NAME, __mem_state)) { + FIO_NAME(FIO_MEMORY_NAME, __mem_state_setup)(); + } +#if FIO_MEMORY_ENABLE_BIG_ALLOC + if ((is_realloc && + size > FIO_MEMORY_BLOCK_SIZE - (2 << FIO_MEMORY_ALIGN_LOG)) || + (!is_realloc && size > FIO_MEMORY_BLOCK_ALLOC_LIMIT)) { + p = FIO_NAME(FIO_MEMORY_NAME, __mem_big_slice_new)(size, is_realloc); + if (p && p != is_realloc) { + FIO_MEMORY_ON_ALLOC_FUNC(); + } + return p; + } +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + + p = FIO_NAME(FIO_MEMORY_NAME, __mem_slice_new)(size, is_realloc); + if (p && p != is_realloc) { + FIO_MEMORY_ON_ALLOC_FUNC(); + } + return p; +malloc_zero: + p = FIO_MEMORY_MALLOC_ZERO_POINTER; + return p; +} + +/* ***************************************************************************** +Memory Allocation - API implementation +***************************************************************************** */ + +/* SublimeText marker */ +void fio_malloc__(void); +/** + * Allocates memory using a per-CPU core block memory pool. + * Memory is zeroed out. + * + * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT will be redirected to `mmap`, + * as if `mempool_mmap` was called. + * + * `mempool_malloc` promises a best attempt at providing locality between + * consecutive calls, but locality can't be guaranteed. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size) { + void *p = FIO_NAME(FIO_MEMORY_NAME, ___malloc)(size, NULL); +#if !FIO_MEMORY_INITIALIZE_ALLOCATIONS && defined(DEBUG) && DEBUG + /* set all bytes to 0xAF to better catch initialization bugs */ + FIO_MEMSET(p, 0xFA, size); +#endif /* DEBUG dirtify */ + return p; +} + +/* SublimeText marker */ +void fio_calloc__(void); +/** + * same as calling `fio_malloc(size_per_unit * unit_count)`; + * + * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT will be redirected to `mmap`, + * as if `mempool_mmap` was called. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, + calloc)(size_t size_per_unit, + size_t unit_count) { +#if FIO_MEMORY_INITIALIZE_ALLOCATIONS + return FIO_NAME(FIO_MEMORY_NAME, malloc)(size_per_unit * unit_count); +#else + void *p; + /* round up to alignment size. */ + const size_t len = + ((size_per_unit * unit_count) + (FIO_MEMORY_ALIGN_SIZE - 1)) & + (~((size_t)FIO_MEMORY_ALIGN_SIZE - 1)); + p = FIO_NAME(FIO_MEMORY_NAME, malloc)(len); + /* initialize memory only when required */ + FIO_MEMSET(p, 0, len); + return p; +#endif /* FIO_MEMORY_INITIALIZE_ALLOCATIONS */ +} + +/* SublimeText marker */ +void fio_free__(void); +/** Frees memory that was allocated using this library. */ +SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr) { + if (!ptr || ptr == FIO_MEMORY_MALLOC_ZERO_POINTER) + return; + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(ptr); + if (!c) { + FIO_LOG_ERROR(FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, + free)) " attempting to free a pointer owned by a NULL chunk."); + return; + } + FIO_MEMORY_ON_FREE_FUNC(); + +#if FIO_MEMORY_ENABLE_BIG_ALLOC + if (c->marker == FIO_MEMORY_BIG_BLOCK_MARKER) { + FIO_NAME(FIO_MEMORY_NAME, __mem_big_slice_free)(ptr); + return; + } +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + + /* big mmap allocation? */ + if (((uintptr_t)c + FIO_MEMORY_ALIGN_SIZE) == (uintptr_t)ptr && c->marker) + goto mmap_free; + + FIO_NAME(FIO_MEMORY_NAME, __mem_slice_free)(ptr); + return; + +mmap_free: + /* zero out memory before returning it to the system */ + FIO_MEMSET(ptr, + 0, + ((size_t)c->marker << FIO_MEM_PAGE_SIZE_LOG) - + FIO_MEMORY_ALIGN_SIZE); + FIO_MEMORY_ON_CHUNK_FREE(c); + FIO_MEM_SYS_FREE(c, (size_t)c->marker << FIO_MEM_PAGE_SIZE_LOG); +} + +/* SublimeText marker */ +void fio_realloc__(void); +/** + * Re-allocates memory. An attempt to avoid copying the data is made only for + * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). + */ +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr, + size_t new_size) { + return FIO_NAME(FIO_MEMORY_NAME, realloc2)(ptr, new_size, new_size); +} + +/** + * Uses system page maps for reallocation. + */ +FIO_SFUNC void *FIO_NAME(FIO_MEMORY_NAME, __mem_realloc2_big)( + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) * c, + size_t new_size) { + const size_t new_len = FIO_MEM_BYTES2PAGES(new_size + FIO_MEMORY_ALIGN_SIZE); + c = (FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *)FIO_MEM_SYS_REALLOC( + c, + (size_t)c->marker << FIO_MEM_PAGE_SIZE_LOG, + new_len, + FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG); + if (!c) + return NULL; + c->marker = (uint32_t)(new_len >> FIO_MEM_PAGE_SIZE_LOG); + return (void *)((uintptr_t)c + FIO_MEMORY_ALIGN_SIZE); +} + +/* SublimeText marker */ +void fio_realloc2__(void); +/** + * Re-allocates memory. An attempt to avoid copying the data is made only for + * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). + * + * This variation is slightly faster as it might copy less data. + */ +SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr, + size_t new_size, + size_t copy_len) { + void *mem = NULL; + if (!new_size) + goto act_as_free; + if (!ptr || ptr == FIO_MEMORY_MALLOC_ZERO_POINTER) + goto act_as_malloc; + + { /* test for big-paged malloc and limit copy_len */ + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = + FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2chunk)(ptr); + size_t b = FIO_NAME(FIO_MEMORY_NAME, __mem_ptr2index)(c, ptr); + FIO_ASSERT(c, "cannot reallocate a pointer with a NULL system allocation"); + + register size_t max_len = + ((uintptr_t)FIO_NAME(FIO_MEMORY_NAME, __mem_chunk2ptr)(c, b, 0) + + FIO_MEMORY_BLOCK_SIZE) - + ((uintptr_t)ptr); +#if FIO_MEMORY_ENABLE_BIG_ALLOC + if (c->marker == FIO_MEMORY_BIG_BLOCK_MARKER) { + /* extend max_len to accommodate possible length */ + max_len = + ((uintptr_t)c + FIO_MEMORY_SYS_ALLOCATION_SIZE) - ((uintptr_t)ptr); + } else +#endif /* FIO_MEMORY_ENABLE_BIG_ALLOC */ + if ((uintptr_t)(c) + FIO_MEMORY_ALIGN_SIZE == (uintptr_t)ptr && + c->marker) { + if (new_size > FIO_MEMORY_ALLOC_LIMIT) + return ( + mem = FIO_NAME(FIO_MEMORY_NAME, __mem_realloc2_big)(c, new_size)); + max_len = new_size; /* shrinking from mmap to allocator */ + } + + if (copy_len > max_len) + copy_len = max_len; + if (copy_len > new_size) + copy_len = new_size; + } + + mem = FIO_NAME(FIO_MEMORY_NAME, ___malloc)(new_size, ptr); + if (!mem || mem == ptr) { + return mem; + } + + /* when allocated from the same block, the max length might be adjusted */ + if ((uintptr_t)mem > (uintptr_t)ptr && + (uintptr_t)ptr + copy_len >= (uintptr_t)mem) { + copy_len = (uintptr_t)mem - (uintptr_t)ptr; + } + + FIO_MEMCPY(mem, + ptr, + ((copy_len + (FIO_MEMORY_ALIGN_SIZE - 1)) & + ((~(size_t)0) << FIO_MEMORY_ALIGN_LOG))); + // zero out leftover bytes, if any. + while (copy_len & (FIO_MEMORY_ALIGN_SIZE - 1)) { + ((uint8_t *)mem)[copy_len++] = 0; + } + + FIO_NAME(FIO_MEMORY_NAME, free)(ptr); + + return mem; + +act_as_malloc: + mem = FIO_NAME(FIO_MEMORY_NAME, ___malloc)(new_size, NULL); + return mem; + +act_as_free: + FIO_NAME(FIO_MEMORY_NAME, free)(ptr); + mem = FIO_MEMORY_MALLOC_ZERO_POINTER; + return mem; +} + +/* SublimeText marker */ +void fio_mmap__(void); +/** + * Allocates memory directly using `mmap`, this is preferred for objects that + * both require almost a page of memory (or more) and expect a long lifetime. + * + * However, since this allocation will invoke the system call (`mmap`), it will + * be inherently slower. + * + * `mempoll_free` can be used for deallocating the memory. + */ +SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size) { + if (!size) + return FIO_NAME(FIO_MEMORY_NAME, malloc)(0); + size_t pages = FIO_MEM_BYTES2PAGES(size + FIO_MEMORY_ALIGN_SIZE); + if (((uint64_t)pages >> (31 + FIO_MEM_PAGE_SIZE_LOG))) + return NULL; + FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *c = + (FIO_NAME(FIO_MEMORY_NAME, __mem_chunk_s) *) + FIO_MEM_SYS_ALLOC(pages, FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG); + if (!c) + goto no_mem; + FIO_MEMORY_ON_ALLOC_FUNC(); + FIO_MEMORY_ON_CHUNK_ALLOC(c); + c->marker = (uint32_t)(pages >> FIO_MEM_PAGE_SIZE_LOG); + return (void *)((uintptr_t)c + FIO_MEMORY_ALIGN_SIZE); +no_mem: + errno = ENOMEM; + return NULL; +} + +/* ***************************************************************************** +Override the system's malloc functions if required +***************************************************************************** */ +#if defined(FIO_MALLOC_OVERRIDE_SYSTEM) && !defined(H___FIO_MALLOC_OVERRIDE___H) +#define H___FIO_MALLOC_OVERRIDE___H +void *malloc(size_t size) { return FIO_NAME(FIO_MEMORY_NAME, malloc)(size); } +void *calloc(size_t size, size_t count) { + return FIO_NAME(FIO_MEMORY_NAME, calloc)(size, count); +} +void free(void *ptr) { FIO_NAME(FIO_MEMORY_NAME, free)(ptr); } +void *realloc(void *ptr, size_t new_size) { + return FIO_NAME(FIO_MEMORY_NAME, realloc2)(ptr, new_size, new_size); +} +#endif /* FIO_MALLOC_OVERRIDE_SYSTEM */ +#undef FIO_MALLOC_OVERRIDE_SYSTEM + +/* ***************************************************************************** + + + + + +Memory Allocation - test - tests specific allocator settings + + + + + +***************************************************************************** */ +#ifdef FIO_TEST_ALL + +#ifndef H___FIO_TEST_MEMORY_HELPERS_H +#define H___FIO_TEST_MEMORY_HELPERS_H + +FIO_IFUNC void fio___memset_test_aligned(void *restrict dest_, + uint64_t data, + size_t bytes, + const char *msg) { + uint8_t *r = (uint8_t *)dest_; + uint8_t *e_group = r + (bytes & (~(size_t)63ULL)); + uint64_t d[8] = {data, data, data, data, data, data, data, data}; + while (r < e_group) { + fio_memcpy64(d, r); + FIO_ASSERT(d[0] == data && d[1] == data && d[2] == data && d[3] == data && + d[4] == data && d[5] == data && d[6] == data && d[7] == data, + "%s memory data was overwritten", + msg); + r += 64; + } + fio_memcpy63x(d, r, bytes); + FIO_ASSERT(d[0] == data && d[1] == data && d[2] == data && d[3] == data && + d[4] == data && d[5] == data && d[6] == data && d[7] == data, + "%s memory data was overwritten", + msg); + (void)msg; /* in case FIO_ASSERT is disabled */ +} +#endif /* H___FIO_TEST_MEMORY_HELPERS_H */ + +#ifndef FIO_TEST_MULTI_THREADED +#define FIO_TEST_MULTI_THREADED 0 +#endif + +/* contention testing (multi-threaded) */ +FIO_IFUNC void *FIO_NAME_TEST(FIO_NAME(FIO_MEMORY_NAME, fio), + mem_tsk)(void *i_) { + uintptr_t cycles = (uintptr_t)i_; + const size_t test_byte_count = + FIO_MEMORY_SYS_ALLOCATION_SIZE + (FIO_MEMORY_SYS_ALLOCATION_SIZE >> 1); + uint64_t marker[2]; + do { + marker[0] = fio_rand64(); + marker[1] = fio_rand64(); + } while (!marker[0] || !marker[1] || marker[0] == marker[1]); + + const size_t limit = (test_byte_count / cycles); + char **ary = (char **)FIO_NAME(FIO_MEMORY_NAME, calloc)(sizeof(*ary), limit); + const uintptr_t alignment_mask = (FIO_MEMORY_ALIGN_SIZE - 1); + FIO_ASSERT(ary, "allocation failed for test container"); + for (size_t i = 0; i < limit; ++i) { + if (1) { + /* add some fragmentation */ + char *tmp = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(16); + FIO_NAME(FIO_MEMORY_NAME, free)(tmp); + FIO_ASSERT(tmp, "small allocation failed!"); + FIO_ASSERT(!((uintptr_t)tmp & alignment_mask), + "allocation alignment error!"); + } + ary[i] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_ASSERT(ary[i], "allocation failed!"); + FIO_ASSERT(!((uintptr_t)ary[i] & alignment_mask), + "allocation alignment error!"); + FIO_ASSERT(!FIO_MEMORY_INITIALIZE_ALLOCATIONS || !ary[i][(cycles - 1)], + "allocated memory not zero (end): %p", + (void *)ary[i]); + FIO_ASSERT(!FIO_MEMORY_INITIALIZE_ALLOCATIONS || !ary[i][0], + "allocated memory not zero (start): %p", + (void *)ary[i]); + FIO_MEMSET(ary[i], 0, cycles); + fio_xmask(ary[i], cycles, marker[i & 1]); + } + for (size_t i = 0; i < limit; ++i) { + char *tmp = (char *)FIO_NAME(FIO_MEMORY_NAME, + realloc2)(ary[i], (cycles << 1), (cycles)); + FIO_ASSERT(tmp, "re-allocation failed!"); + ary[i] = tmp; + FIO_ASSERT(!((uintptr_t)ary[i] & alignment_mask), + "allocation alignment error!"); + FIO_ASSERT(!FIO_MEMORY_INITIALIZE_ALLOCATIONS || !ary[i][(cycles)], + "realloc2 copy overflow!"); + fio___memset_test_aligned(ary[i], marker[i & 1], (cycles), "realloc grow"); + tmp = + (char *)FIO_NAME(FIO_MEMORY_NAME, realloc2)(ary[i], (cycles), (cycles)); + FIO_ASSERT(tmp, "re-allocation (shrinking) failed!"); + ary[i] = tmp; + fio___memset_test_aligned(ary[i], + marker[i & 1], + (cycles), + "realloc shrink"); + } + for (size_t i = 0; i < limit; ++i) { + fio___memset_test_aligned(ary[i], marker[i & 1], (cycles), "mem review"); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i]); + ary[i] = NULL; + } + + uint64_t mark; + void *old = &mark; + mark = fio_risky_hash(&old, sizeof(mark), 0); + + for (int repeat_cycle_test = 0; repeat_cycle_test < 4; ++repeat_cycle_test) { + for (size_t i = 0; i < limit - 4; i += 4) { + if (ary[i]) + fio___memset_test_aligned(ary[i], mark, 16, "mark missing at ary[0]"); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i]); + if (ary[i + 1]) + fio___memset_test_aligned(ary[i + 1], + mark, + cycles, + "mark missing at ary[1]"); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + 1]); + if (ary[i + 2]) + fio___memset_test_aligned(ary[i + 2], + mark, + cycles, + "mark missing at ary[2]"); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + 2]); + if (ary[i + 3]) + fio___memset_test_aligned(ary[i + 3], + mark, + cycles, + "mark missing at ary[3]"); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + 3]); + + ary[i] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_MEMSET(ary[i], 0, cycles); + fio_xmask(ary[i], cycles, mark); + + ary[i + 1] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + 1]); + ary[i + 1] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_MEMSET(ary[i + 1], 0, cycles); + fio_xmask(ary[i + 1], cycles, mark); + + ary[i + 2] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_MEMSET(ary[i + 2], 0, cycles); + fio_xmask(ary[i + 2], cycles, mark); + ary[i + 2] = (char *)FIO_NAME(FIO_MEMORY_NAME, + realloc2)(ary[i + 2], cycles * 2, cycles); + + ary[i + 3] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + 3]); + ary[i + 3] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_MEMSET(ary[i + 3], 0, cycles); + fio_xmask(ary[i + 3], cycles, mark); + ary[i + 3] = (char *)FIO_NAME(FIO_MEMORY_NAME, + realloc2)(ary[i + 3], cycles * 2, cycles); + + for (int b = 0; b < 4; ++b) { + for (size_t pos = 0; pos < (cycles / sizeof(uint64_t)); ++pos) { + FIO_ASSERT(((uint64_t *)(ary[i + b]))[pos] == mark, + "memory mark corrupted at test ptr %zu", + i + b); + } + } + for (int b = 1; b < 4; ++b) { + FIO_NAME(FIO_MEMORY_NAME, free)(ary[b]); + ary[b] = NULL; + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i + b]); + } + for (int b = 1; b < 4; ++b) { + ary[i + b] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + if (i) { + ary[b] = (char *)FIO_NAME(FIO_MEMORY_NAME, malloc)(cycles); + FIO_MEMSET(ary[b], 0, cycles); + fio_xmask(ary[b], cycles, mark); + } + FIO_MEMSET(ary[i + b], 0, cycles); + fio_xmask(ary[i + b], cycles, mark); + } + + for (int b = 0; b < 4; ++b) { + for (size_t pos = 0; pos < (cycles / sizeof(uint64_t)); ++pos) { + FIO_ASSERT(((uint64_t *)(ary[b]))[pos] == mark, + "memory mark corrupted at test ptr %zu", + i + b); + FIO_ASSERT(((uint64_t *)(ary[i + b]))[pos] == mark, + "memory mark corrupted at test ptr %zu", + i + b); + } + } + } + } + for (size_t i = 0; i < limit; ++i) { + FIO_NAME(FIO_MEMORY_NAME, free)(ary[i]); + ary[i] = NULL; + } + + FIO_NAME(FIO_MEMORY_NAME, free)(ary); + return NULL; +} + +/* main test function */ +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME(stl, FIO_MEMORY_NAME), mem)(void) { + fprintf(stderr, + "* Testing core memory allocator " FIO_MACRO2STR( + FIO_NAME(FIO_MEMORY_NAME, malloc)) ".\n"); + + const uintptr_t alignment_mask = (FIO_MEMORY_ALIGN_SIZE - 1); + fprintf(stderr, + "* Validating allocation alignment on %zu byte border.\n", + (size_t)(FIO_MEMORY_ALIGN_SIZE)); + for (size_t i = 0; i < alignment_mask; ++i) { + void *p = FIO_NAME(FIO_MEMORY_NAME, malloc)(i); + FIO_ASSERT(!((uintptr_t)p & alignment_mask), + "allocation alignment error allocating %zu bytes!", + i); + FIO_NAME(FIO_MEMORY_NAME, free)(p); + } + const size_t thread_count = + FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count + + (FIO_NAME(FIO_MEMORY_NAME, __mem_state)->arena_count >> 1); + + for (uintptr_t cycles = 16; cycles <= (FIO_MEMORY_ALLOC_LIMIT); cycles *= 2) { + fprintf(stderr, + "* Testing %zu byte allocation blocks, single threaded.\n", + (size_t)(cycles)); + FIO_NAME_TEST(FIO_NAME(FIO_MEMORY_NAME, fio), mem_tsk)((void *)cycles); + } + + if (FIO_TEST_MULTI_THREADED) { + + for (uintptr_t cycles = 16; cycles <= (FIO_MEMORY_ALLOC_LIMIT); + cycles *= 2) { +#if _MSC_VER + fio_thread_t threads[(FIO_MEMORY_ARENA_COUNT_MAX + 1) * 2]; + FIO_ASSERT(((FIO_MEMORY_ARENA_COUNT_MAX + 1) * 2) >= thread_count, + "Please use CLang or GCC to test this memory allocator"); +#else + fio_thread_t threads[thread_count]; +#endif + + fprintf(stderr, + "* Testing %zu byte allocation blocks, using %zu threads.\n", + (size_t)(cycles), + (thread_count + 1)); + for (size_t i = 0; i < thread_count; ++i) { + if (fio_thread_create( + threads + i, + FIO_NAME_TEST(FIO_NAME(FIO_MEMORY_NAME, fio), mem_tsk), + (void *)cycles)) { + abort(); + } + } + FIO_NAME_TEST(FIO_NAME(FIO_MEMORY_NAME, fio), mem_tsk)((void *)cycles); + for (size_t i = 0; i < thread_count; ++i) { + fio_thread_join(threads + i); + } + } + } + fprintf(stderr, + "* Re-validating allocation alignment on %zu byte border.\n", + (size_t)(FIO_MEMORY_ALIGN_SIZE)); + for (size_t i = 0; i < alignment_mask; ++i) { + void *p = FIO_NAME(FIO_MEMORY_NAME, malloc)(i); + FIO_ASSERT(!((uintptr_t)p & alignment_mask), + "allocation alignment error allocating %zu bytes!", + i); + FIO_NAME(FIO_MEMORY_NAME, free)(p); + } + +#if DEBUG + FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(); + FIO_NAME(FIO_MEMORY_NAME, __mem_state_cleanup)(NULL); +#endif /* DEBUG */ +} +#endif /* FIO_TEST_ALL */ + +/* ***************************************************************************** +Memory pool cleanup +***************************************************************************** */ +#undef FIO_MEM_ALIGN +#undef FIO_MEM_ALIGN_NEW +#undef FIO_MEMORY_MALLOC_ZERO_POINTER + +#endif /* FIO_MEMORY_DISABLE */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_MEMORY_NAME */ + +#undef FIO_MEMORY_ON_CHUNK_ALLOC +#undef FIO_MEMORY_ON_CHUNK_FREE +#undef FIO_MEMORY_ON_CHUNK_CACHE +#undef FIO_MEMORY_ON_CHUNK_UNCACHE +#undef FIO_MEMORY_ON_BLOCK_RESET_IN_LOCK +#undef FIO_MEMORY_ON_BIG_BLOCK_SET +#undef FIO_MEMORY_ON_BIG_BLOCK_UNSET +#undef FIO_MEMORY_ON_ALLOC_FUNC +#undef FIO_MEMORY_ON_FREE_FUNC +#undef FIO_MEMORY_PRINT_STATS_END + +#undef FIO_MEMORY_ARENA_COUNT +#undef FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG +#undef FIO_MEMORY_CACHE_SLOTS +#undef FIO_MEMORY_ALIGN_LOG +#undef FIO_MEMORY_INITIALIZE_ALLOCATIONS +#undef FIO_MEMORY_USE_THREAD_MUTEX +#undef FIO_MEMORY_BLOCK_SIZE +#undef FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG +#undef FIO_MEMORY_BLOCKS_PER_ALLOCATION +#undef FIO_MEMORY_ENABLE_BIG_ALLOC +// #undef FIO_MEMORY_ARENA_COUNT_FALLBACK +// #undef FIO_MEMORY_ARENA_COUNT_MAX +#undef FIO_MEMORY_WARMUP + +#undef FIO_MEMORY_LOCK_NAME +#undef FIO_MEMORY_LOCK_TYPE +#undef FIO_MEMORY_LOCK_TYPE_INIT +#undef FIO_MEMORY_TRYLOCK +#undef FIO_MEMORY_LOCK +#undef FIO_MEMORY_UNLOCK + +/* don't undefine FIO_MEMORY_NAME due to possible use in allocation macros */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_POLL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + POSIX Portable Polling + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_POLL) && !defined(H___FIO_POLL___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) + +#ifndef FIO_POLL_POSSIBLE_FLAGS +/** The user flags IO events recognize */ +#define FIO_POLL_POSSIBLE_FLAGS (POLLIN | POLLOUT | POLLPRI) +#endif + +#ifndef FIO_POLL_MAX_EVENTS +#if UINTPTR_MAX <= 0xFFFFFFFF +/** relevant only for epoll and kqueue - maximum number of events per review */ +#define FIO_POLL_MAX_EVENTS 256 +#else +/** relevant only for epoll and kqueue - maximum number of events per review */ +#define FIO_POLL_MAX_EVENTS 128 +#endif +#endif + +/* ***************************************************************************** +Possible polling engine (system call) selection +***************************************************************************** */ + +#ifndef FIO_POLL_ENGINE_POLL +/** define `FIO_POLL_ENGINE` as `FIO_POLL_ENGINE_POLL` to use `poll` */ +#define FIO_POLL_ENGINE_POLL 1 +#endif +#ifndef FIO_POLL_ENGINE_EPOLL +/** define `FIO_POLL_ENGINE` as `FIO_POLL_ENGINE_EPOLL` to use `epoll` */ +#define FIO_POLL_ENGINE_EPOLL 2 +#endif +#ifndef FIO_POLL_ENGINE_KQUEUE +/** define `FIO_POLL_ENGINE` as `FIO_POLL_ENGINE_KQUEUE` to use `kqueue` */ +#define FIO_POLL_ENGINE_KQUEUE 3 +#endif + +/* if `FIO_POLL_ENGINE` wasn't define, detect automatically. */ +#if !defined(FIO_POLL_ENGINE) +#if defined(HAVE_EPOLL) || __has_include("sys/epoll.h") +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_EPOLL +#elif (defined(HAVE_KQUEUE) || __has_include("sys/event.h")) +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_KQUEUE +#else +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_POLL +#endif +#endif /* FIO_POLL_ENGINE */ + +#if FIO_POLL_ENGINE == FIO_POLL_ENGINE_POLL +#ifndef FIO_POLL_ENGINE_STR +#define FIO_POLL_ENGINE_STR "poll" +#endif +#elif FIO_POLL_ENGINE == FIO_POLL_ENGINE_EPOLL +#ifndef FIO_POLL_ENGINE_STR +#define FIO_POLL_ENGINE_STR "epoll" +#endif +#elif FIO_POLL_ENGINE == FIO_POLL_ENGINE_KQUEUE +#ifndef FIO_POLL_ENGINE_STR +#define FIO_POLL_ENGINE_STR "kqueue" +#endif +#endif +/* ***************************************************************************** +Polling API +***************************************************************************** */ + +/** the `fio_poll_s` type should be considered opaque. */ +typedef struct fio_poll_s fio_poll_s; + +typedef struct { + /** callback for when data is available in the incoming buffer. */ + void (*on_data)(void *udata); + /** callback for when the outgoing buffer allows a call to `write`. */ + void (*on_ready)(void *udata); + /** callback for closed connections and / or connections with errors. */ + void (*on_close)(void *udata); +} fio_poll_settings_s; + +/** Initializes the polling object, allocating its resources. */ +FIO_IFUNC void fio_poll_init(fio_poll_s *p, fio_poll_settings_s); +/** Initializes the polling object, allocating its resources. */ +#define fio_poll_init(p, ...) \ + fio_poll_init((p), (fio_poll_settings_s){__VA_ARGS__}) + +/** Destroys the polling object, freeing its resources. */ +FIO_IFUNC void fio_poll_destroy(fio_poll_s *p); + +/** returns the system call used for polling as a constant string. */ +FIO_IFUNC const char *fio_poll_engine(void); + +/** + * Adds a file descriptor to be monitored, adds events to be monitored or + * updates the monitored file's `udata`. + * + * Possible flags are: `POLLIN` and `POLLOUT`. Other flags may be set but might + * be ignored. + * + * Monitoring mode is always one-shot. If an event if fired, it is removed from + * the monitoring state. + * + * Returns -1 on error. + */ +SFUNC int fio_poll_monitor(fio_poll_s *p, + int fd, + void *udata, + unsigned short flags); + +/** + * Reviews if any of the monitored file descriptors has any events. + * + * `timeout` is in milliseconds. + * + * Returns the number of events called. + * + * Polling is thread safe, but has different effects on different threads. + * + * Adding a new file descriptor from one thread while polling in a different + * thread will not poll that IO until `fio_poll_review` is called again. + */ +SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout); + +/** Stops monitoring the specified file descriptor (if monitoring). */ +SFUNC int fio_poll_forget(fio_poll_s *p, int fd); + +/* ***************************************************************************** +Implementation Helpers +***************************************************************************** */ + +/** returns the system call used for polling as a constant string. */ +FIO_IFUNC const char *fio_poll_engine(void) { return FIO_POLL_ENGINE_STR; } + +/* validate settings */ +#define FIO_POLL_VALIDATE(settings_dest) \ + if (!(settings_dest).on_data) \ + (settings_dest).on_data = fio___poll_ev_mock; \ + if (!(settings_dest).on_ready) \ + (settings_dest).on_ready = fio___poll_ev_mock; \ + if (!(settings_dest).on_close) \ + (settings_dest).on_close = fio___poll_ev_mock; + +SFUNC void fio___poll_ev_mock(void *udata); + +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/* mock event */ +SFUNC void fio___poll_ev_mock(void *udata) { (void)udata; } +#endif /* defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) */ +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_POLL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_EPOLL /* Dev */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_POLL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ************************************************************************* */ +#if defined(FIO_POLL) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) && \ + FIO_POLL_ENGINE == FIO_POLL_ENGINE_EPOLL && \ + !defined(H___FIO_POLL_EGN___H) && !defined(H___FIO_POLL___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_POLL_EGN___H +/* ***************************************************************************** + + + + + POSIX Portable Polling with `epoll` + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#include + +/* ***************************************************************************** +Polling API +***************************************************************************** */ + +/** the `fio_poll_s` type should be considered opaque. */ +struct fio_poll_s { + fio_poll_settings_s settings; + struct pollfd fds[2]; + int fd[2]; +}; + +FIO_SFUNC void fio___epoll_after_fork(void *p_) { + fio_poll_s *p = (fio_poll_s *)p_; + fio_poll_destroy(p); + fio_poll_init FIO_NOOP(p, p->settings); +} + +/** Initializes the polling object, allocating its resources. */ +FIO_IFUNC void fio_poll_init FIO_NOOP(fio_poll_s *p, fio_poll_settings_s args) { + *p = (fio_poll_s){ + .settings = args, + .fds = + { + {.fd = epoll_create1(0), .events = (POLLIN | POLLOUT)}, + {.fd = epoll_create1(0), .events = (POLLIN | POLLOUT)}, + }, + }; + FIO_POLL_VALIDATE(p->settings); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___epoll_after_fork, p); +} + +/** Destroys the polling object, freeing its resources. */ +FIO_IFUNC void fio_poll_destroy(fio_poll_s *p) { + for (int i = 0; i < 2; ++i) { + if (p->fds[i].fd != -1) + close(p->fds[i].fd); + p->fds[i].fd = -1; + } + fio_state_callback_remove(FIO_CALL_IN_CHILD, fio___epoll_after_fork, p); +} + +/* ***************************************************************************** +Poll Monitoring Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_IFUNC int fio___epoll_add2(int fd, + void *udata, + uint32_t events, + int ep_fd) { + int ret = 0; + struct epoll_event chevent; + do { + errno = 0; + chevent = (struct epoll_event){ + .events = events, + .data.ptr = udata, + }; + ret = epoll_ctl(ep_fd, EPOLL_CTL_MOD, fd, &chevent); + if (ret == -1 && errno == ENOENT) { + errno = 0; + chevent = (struct epoll_event){ + .events = events, + .data.ptr = udata, + }; + ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, fd, &chevent); + } + } while (errno == EINTR); + + return ret; +} + +/** + * Adds a file descriptor to be monitored, adds events to be monitored or + * updates the monitored file's `udata`. + * + * Possible flags are: `POLLIN` and `POLLOUT`. Other flags may be set but might + * be ignored. + * + * Monitoring mode is always one-shot. If an event if fired, it is removed from + * the monitoring state. + * + * Returns -1 on error. + */ +SFUNC int fio_poll_monitor(fio_poll_s *p, + int fd, + void *udata, + unsigned short flags) { + int r = 0; + if ((flags & POLLOUT)) + r |= fio___epoll_add2(fd, + udata, + (EPOLLOUT | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), + p->fds[0].fd); + if ((flags & POLLIN)) + r |= fio___epoll_add2(fd, + udata, + (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), + p->fds[1].fd); + return r; +} + +/** + * Stops monitoring the specified file descriptor, returning its udata (if any). + */ +SFUNC int fio_poll_forget(fio_poll_s *p, int fd) { + int r = 0; + struct epoll_event chevent = {.events = (EPOLLOUT | EPOLLIN)}; + r |= epoll_ctl(p->fds[0].fd, EPOLL_CTL_DEL, fd, &chevent); + r |= epoll_ctl(p->fds[1].fd, EPOLL_CTL_DEL, fd, &chevent); + return r; +} + +/** + * Reviews if any of the monitored file descriptors has any events. + * + * `timeout` is in milliseconds. + * + * Returns the number of events called. + * + * Polling is thread safe, but has different effects on different threads. + * + * Adding a new file descriptor from one thread while polling in a different + * thread will not poll that IO until `fio_poll_review` is called again. + */ +SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout) { + int total = 0; + struct epoll_event events[FIO_POLL_MAX_EVENTS]; + /* wait for events and handle them */ + int internal_count = poll(p->fds, 2, timeout); + if (internal_count <= 0) + return total; + int active_count = epoll_wait(p->fds[0].fd, events, FIO_POLL_MAX_EVENTS, 0); + if (active_count > 0) { + for (int i = 0; i < active_count; i++) { + // errors are handled as disconnections (on_close) in the EPOLLIN queue + // if no error, try an active event(s) + if (events[i].events & EPOLLOUT) + p->settings.on_ready(events[i].data.ptr); + } // end for loop + total += active_count; + } + active_count = epoll_wait(p->fds[1].fd, events, FIO_POLL_MAX_EVENTS, 0); + if (active_count > 0) { + for (int i = 0; i < active_count; i++) { + // errors are handled as disconnections (on_close), but only once... + if (events[i].events & (~(EPOLLIN | EPOLLOUT))) + p->settings.on_close(events[i].data.ptr); + // no error, then it's an active event(s) + else if (events[i].events & EPOLLIN) + p->settings.on_data(events[i].data.ptr); + } // end for loop + total += active_count; + } + return total; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_POLL_ENGINE == FIO_POLL_ENGINE_EPOLL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_KQUEUE /* Dev */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_POLL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ************************************************************************* */ +#if defined(FIO_POLL) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) && \ + FIO_POLL_ENGINE == FIO_POLL_ENGINE_KQUEUE && \ + !defined(H___FIO_POLL_EGN___H) && !defined(H___FIO_POLL___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_POLL_EGN___H +/* ***************************************************************************** + + + + + POSIX Portable Polling with `kqueue` + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#include +/* ***************************************************************************** +Polling API +***************************************************************************** */ + +/** the `fio_poll_s` type should be considered opaque. */ +struct fio_poll_s { + fio_poll_settings_s settings; + int fd; +}; + +FIO_SFUNC void fio___kqueue_after_fork(void *p_) { + fio_poll_s *p = (fio_poll_s *)p_; + fio_poll_destroy(p); + fio_poll_init FIO_NOOP(p, p->settings); +} + +/** Initializes the polling object, allocating its resources. */ +FIO_IFUNC void fio_poll_init FIO_NOOP(fio_poll_s *p, fio_poll_settings_s args) { + *p = (fio_poll_s){ + .settings = args, + .fd = kqueue(), + }; + if (p->fd == -1) { + FIO_LOG_FATAL("couldn't open kqueue.\n"); + exit(errno); + } + FIO_POLL_VALIDATE(p->settings); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___kqueue_after_fork, p); +} + +/** Destroys the polling object, freeing its resources. */ +FIO_IFUNC void fio_poll_destroy(fio_poll_s *p) { + if (p->fd != -1) + close(p->fd); + p->fd = -1; + fio_state_callback_remove(FIO_CALL_IN_CHILD, fio___kqueue_after_fork, p); +} + +/* ***************************************************************************** +Poll Monitoring Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/** + * Adds a file descriptor to be monitored, adds events to be monitored or + * updates the monitored file's `udata`. + * + * Possible flags are: `POLLIN` and `POLLOUT`. Other flags may be set but might + * be ignored. + * + * Monitoring mode is always one-shot. If an event if fired, it is removed from + * the monitoring state. + * + * Returns -1 on error. + */ +SFUNC int fio_poll_monitor(fio_poll_s *p, + int fd, + void *udata, + unsigned short flags) { + int r = -1; + struct kevent chevent[2]; + int i = 0; + if ((flags & POLLIN)) { + EV_SET(chevent, + fd, + EVFILT_READ, + EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, + 0, + 0, + udata); + ++i; + } + if ((flags & POLLOUT)) { + EV_SET(chevent + i, + fd, + EVFILT_WRITE, + EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, + 0, + 0, + udata); + ++i; + } + do { + errno = 0; + } while ((r = kevent(p->fd, chevent, i, NULL, 0, NULL)) == -1 && + errno == EINTR); + return r; +} + +/** + * Reviews if any of the monitored file descriptors has any events. + * + * `timeout` is in milliseconds. + * + * Returns the number of events called. + * + * Polling is thread safe, but has different effects on different threads. + * + * Adding a new file descriptor from one thread while polling in a different + * thread will not poll that IO until `fio_poll_review` is called again. + */ +SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout_) { + if (p->fd < 0) + return -1; + struct kevent events[FIO_POLL_MAX_EVENTS] = {{0}}; + + const struct timespec timeout = { + .tv_sec = (time_t)(timeout_ / 1000), + .tv_nsec = (suseconds_t)((timeout_ % 1000) * 1000000)}; + /* wait for events and handle them */ + int active_count = + kevent(p->fd, NULL, 0, events, FIO_POLL_MAX_EVENTS, &timeout); + + if (active_count > 0) { + for (int i = 0; i < active_count; i++) { + // test for event(s) type + if (events[i].filter == EVFILT_WRITE) { + p->settings.on_ready(events[i].udata); + } else if (events[i].filter == EVFILT_READ) { + p->settings.on_data(events[i].udata); + } + if (events[i].flags & (EV_EOF | EV_ERROR)) { + p->settings.on_close(events[i].udata); + } + } + } else if (active_count < 0) { + if (errno == EINTR) + return 0; + return -1; + } + return active_count; +} + +/** Stops monitoring the specified file descriptor (if monitoring). */ +SFUNC int fio_poll_forget(fio_poll_s *p, int fd) { + int r = 0; + if (p->fd == -1) + return (r = -1); + struct kevent chevent[2]; + EV_SET(chevent, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + EV_SET(chevent + 1, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + do { + errno = 0; + r = kevent(p->fd, chevent, 2, NULL, 0, NULL); + } while (errno == EINTR); + return r; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_POLL_ENGINE == FIO_POLL_ENGINE_KQUEUE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO_POLL_ENGINE FIO_POLL_ENGINE_POLL /* Dev */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_POLL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ************************************************************************* */ +#if defined(FIO_POLL) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) && \ + FIO_POLL_ENGINE == FIO_POLL_ENGINE_POLL && \ + !defined(H___FIO_POLL_EGN___H) && !defined(H___FIO_POLL___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_POLL_EGN___H +/* ***************************************************************************** + + + + POSIX Portable Polling with `poll` + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef POLLRDHUP +#define FIO_POLL_EX_FLAGS POLLRDHUP +#else +#define FIO_POLL_EX_FLAGS 0 +#endif + +typedef struct { + void *udata; + int fd; + unsigned short flags; +} fio___poll_i_s; + +#define FIO___POLL_IMAP_CMP(a, b) ((a)->fd == (b)->fd) +#define FIO___POLL_IMAP_HASH(o) (fio_risky_ptr((void *)((uintptr_t)((o)->fd)))) +FIO_TYPEDEF_IMAP_ARRAY(fio___poll_map, + fio___poll_i_s, + uint32_t, + FIO___POLL_IMAP_HASH, + FIO___POLL_IMAP_CMP, + FIO_IMAP_ALWAYS_VALID) +#undef FIO___POLL_IMAP_CMP +#undef FIO___POLL_IMAP_VALID +#undef FIO___POLL_IMAP_HASH + +struct fio_poll_s { + fio_poll_settings_s settings; + fio___poll_map_s map; + FIO___LOCK_TYPE lock; +}; + +/* ***************************************************************************** +Poll Monitoring Implementation - inline static functions +***************************************************************************** */ + +/** Initializes the polling object, allocating its resources. */ +FIO_IFUNC void fio_poll_init FIO_NOOP(fio_poll_s *p, fio_poll_settings_s args) { + if (p) { + *p = (fio_poll_s){ + .settings = args, + .map = {0}, + .lock = FIO___LOCK_INIT, + }; + FIO_POLL_VALIDATE(p->settings); + } +} + +/** Destroys the polling object, freeing its resources. */ +FIO_IFUNC void fio_poll_destroy(fio_poll_s *p) { + if (!p) + return; + fio___poll_map_destroy(&p->map); + FIO___LOCK_DESTROY(p->lock); +} + +/* ***************************************************************************** +Poll Monitoring Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* handle events, return a mask for possible remaining flags. */ +FIO_IFUNC unsigned short fio___poll_handle_events(fio_poll_s *p, + void *udata, + unsigned short flags) { + if ((flags & POLLOUT)) + p->settings.on_ready(udata); + if ((flags & (POLLIN | POLLPRI))) + p->settings.on_data(udata); + if ((flags & (POLLHUP | POLLERR | POLLNVAL | FIO_POLL_EX_FLAGS))) { + p->settings.on_close(udata); + return 0; + } + return ~flags; +} + +/** + * Adds a file descriptor to be monitored, adds events to be monitored or + * updates the monitored file's `udata`. + * + * Possible flags are: `POLLIN` and `POLLOUT`. Other flags may be set but might + * be ignored. + * + * Monitoring mode is always one-shot. If an event if fired, it is removed from + * the monitoring state. + * + * Returns -1 on error. + */ +SFUNC int fio_poll_monitor(fio_poll_s *p, + int fd, + void *udata, + unsigned short flags) { + int r = -1; + if (!p || fd == -1) + return r; + r = 0; + flags &= FIO_POLL_POSSIBLE_FLAGS; + flags |= FIO_POLL_EX_FLAGS; + fio___poll_i_s i = {.udata = udata, .fd = fd, .flags = flags}; + FIO___LOCK_LOCK(p->lock); + fio___poll_i_s *ptr = fio___poll_map_set(&p->map, i, 0); + ptr->flags |= flags; + FIO___LOCK_UNLOCK(p->lock); + return r; +} + +/** + * Reviews if any of the monitored file descriptors has any events. + * + * `timeout` is in milliseconds. + * + * Returns the number of events called. + * + * Polling is thread safe, but has different effects on different threads. + * + * Adding a new file descriptor from one thread while polling in a different + * thread will not poll that IO until `fio_poll_review` is called again. + */ +SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout) { + int events = -1; + int handled = -1; + if (!p || !(p->map.count)) { + if (timeout) { + FIO_THREAD_WAIT((timeout * 1000000)); + } + return 0; + } + /* handle events in a copy, allowing events / threads to mutate it */ + FIO___LOCK_LOCK(p->lock); + fio_poll_s cpy = *p; + p->map = (fio___poll_map_s){0}; + FIO___LOCK_UNLOCK(p->lock); + + const size_t max = cpy.map.count; + const unsigned short flag_mask = FIO_POLL_POSSIBLE_FLAGS | FIO_POLL_EX_FLAGS; + + int w = 0, r = 0, i = 0; + struct pollfd *pfd = (struct pollfd *)FIO_MEM_REALLOC_( + NULL, + 0, + ((max * sizeof(void *)) + (max * sizeof(struct pollfd))), + 0); + void **uary = (void **)(pfd + max); + + FIO_IMAP_EACH(fio___poll_map, (&cpy.map), pos) { + if (!(cpy.map.ary[pos].flags & flag_mask)) + continue; + pfd[r] = (struct pollfd){.fd = cpy.map.ary[pos].fd, + .events = (short)cpy.map.ary[pos].flags}; + uary[r] = cpy.map.ary[pos].udata; + ++r; + } + +#if FIO_OS_WIN + events = WSAPoll(pfd, r, (int)timeout); +#else + events = poll(pfd, r, (int)timeout); +#endif + + if (events > 0) { + /* handle events and remove consumed entries */ + for (i = 0; i < r && handled < events; ++i) { + if (pfd[i].revents) { + ++handled; + pfd[i].events &= + fio___poll_handle_events(&cpy, uary[i], pfd[i].revents); + } + if ((pfd[i].events & (~(FIO_POLL_EX_FLAGS)))) { + if (i != w) { + pfd[w] = pfd[i]; + uary[w] = uary[i]; + } + ++w; + } + } + if (i < r && i != w) { + FIO_MEMMOVE(pfd + w, pfd + i, ((r - i) * sizeof(*pfd))); + FIO_MEMMOVE(uary + w, uary + i, ((r - i) * sizeof(*uary))); + } + } + w += r - i; + i = 0; + + FIO___LOCK_LOCK(p->lock); + if (!p->map.count && events <= 0) { + p->map = cpy.map; + i = 1; + goto finish; + } + if (w) { + fio___poll_map_reserve(&p->map, w + p->map.count); + for (i = 0; i < w; ++i) { + fio___poll_i_s *existing = + fio___poll_map_get(&p->map, (fio___poll_i_s){.fd = pfd[i].fd}); + if (existing) { + existing->flags |= existing->flags ? pfd[i].events : 0; + continue; + } + fio___poll_map_set(&p->map, + (fio___poll_i_s){ + .fd = pfd[i].fd, + .flags = (unsigned short)pfd[i].events, + .udata = uary[i], + }, + 1); + } + } + i = 0; + +finish: + FIO___LOCK_UNLOCK(p->lock); + FIO_MEM_FREE(pfd, ((max * sizeof(void *)) + (max * sizeof(struct pollfd)))); + if (!i) + fio___poll_map_destroy(&cpy.map); + return events; +} + +/** Stops monitoring the specified file descriptor, returning -1 on error. */ +SFUNC int fio_poll_forget(fio_poll_s *p, int fd) { + int r = 0; + fio___poll_i_s i = {.fd = fd}; + FIO___LOCK_LOCK(p->lock); + fio___poll_i_s *ptr = fio___poll_map_set(&p->map, i, 0); + if (!ptr->flags) + r = -1; + ptr->flags = 0; + FIO___LOCK_UNLOCK(p->lock); + return r; +} + +/** Closes all sockets, calling the `on_close`. */ +SFUNC void fio_poll_close_all(fio_poll_s *p) { + FIO___LOCK_LOCK(p->lock); + fio_poll_s cpy = *p; + p->map = (fio___poll_map_s){0}; + FIO___LOCK_UNLOCK(p->lock); + const unsigned short flag_mask = FIO_POLL_POSSIBLE_FLAGS | FIO_POLL_EX_FLAGS; + FIO_IMAP_EACH(fio___poll_map, (&cpy.map), pos) { + if ((cpy.map.ary[pos].flags & flag_mask)) { + cpy.settings.on_close(cpy.map.ary[pos].udata); + fio_sock_close(cpy.map.ary[pos].fd); + } + } + fio___poll_map_destroy(&cpy.map); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#undef FIO_POLL_EX_FLAGS +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_POLL_ENGINE == FIO_POLL_ENGINE_POLL */ + +#if defined(FIO_POLL) && !defined(H___FIO_POLL___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_POLL___H +#undef FIO_POLL +#endif /* FIO_POLL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_QUEUE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Task / Timer Queues + (Event Loop Engine) + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_QUEUE) && !defined(H___FIO_QUEUE___H) +#define H___FIO_QUEUE___H + +/* ***************************************************************************** +Queue Type(s) +***************************************************************************** */ + +/* Note: FIO_QUEUE_TASKS_PER_ALLOC can't be more than 65535 */ +#ifndef FIO_QUEUE_TASKS_PER_ALLOC +#if UINTPTR_MAX <= 0xFFFFFFFF +/* fits fio_queue_s in one page on most 32 bit machines */ +#define FIO_QUEUE_TASKS_PER_ALLOC 338 +#else +/* fits fio_queue_s in one page on most 64 bit machines */ +#define FIO_QUEUE_TASKS_PER_ALLOC 168 +#endif +#endif + +/** Task information */ +typedef struct { + /** The function to call */ + void (*fn)(void *, void *); + /** User opaque data */ + void *udata1; + /** User opaque data */ + void *udata2; +} fio_queue_task_s; + +/* internal use */ +typedef struct fio___task_ring_s { + uint16_t r; /* reader position */ + uint16_t w; /* writer position */ + uint16_t dir; /* direction */ + struct fio___task_ring_s *next; + fio_queue_task_s buf[FIO_QUEUE_TASKS_PER_ALLOC]; +} fio___task_ring_s; + +/** The queue object - should be considered opaque (or, at least, read only). */ +typedef struct { + /** task read pointer. */ + fio___task_ring_s *r; + /** task write pointer. */ + fio___task_ring_s *w; + /** the number of tasks waiting to be performed. */ + uint32_t count; + /** global queue lock. */ + FIO___LOCK_TYPE lock; + /** linked lists of consumer threads. */ + FIO_LIST_NODE consumers; + /** main ring buffer associated with the queue. */ + fio___task_ring_s mem; +} fio_queue_s; + +typedef struct { + FIO_LIST_NODE node; + fio_queue_s *queue; + fio_thread_t thread; + fio_thread_mutex_t mutex; + fio_thread_cond_t cond; + size_t workers; + volatile int stop; +} fio___thread_group_s; + +/* ***************************************************************************** +Queue API +***************************************************************************** */ + +#if FIO_USE_THREAD_MUTEX_TMP +/** May be used to initialize global, static memory, queues. */ +#define FIO_QUEUE_STATIC_INIT(queue) \ + { \ + .r = &(queue).mem, .w = &(queue).mem, \ + .lock = (fio_thread_mutex_t)FIO_THREAD_MUTEX_INIT, \ + .consumers = FIO_LIST_INIT((queue).consumers), \ + } +#else +/** May be used to initialize global, static memory, queues. */ +#define FIO_QUEUE_STATIC_INIT(queue) \ + { \ + .r = &(queue).mem, .w = &(queue).mem, .lock = FIO_LOCK_INIT, \ + .consumers = FIO_LIST_INIT((queue).consumers), \ + } +#endif + +/** Initializes a fio_queue_s object. */ +FIO_IFUNC void fio_queue_init(fio_queue_s *q); + +/** Destroys a queue and re-initializes it, after freeing any used resources. */ +SFUNC void fio_queue_destroy(fio_queue_s *q); + +/** Creates a new queue object (allocated on the heap). */ +SFUNC fio_queue_s *fio_queue_new(void); + +/** Frees a queue object after calling fio_queue_destroy. */ +SFUNC void fio_queue_free(fio_queue_s *q); + +/** Pushes a task to the queue. Returns -1 on error. */ +SFUNC int fio_queue_push(fio_queue_s *q, fio_queue_task_s task); + +/** + * Pushes a task to the queue, offering named arguments for the task. + * Returns -1 on error. + */ +#define fio_queue_push(q, ...) \ + fio_queue_push((q), (fio_queue_task_s){__VA_ARGS__}) + +/** Pushes a task to the head of the queue. Returns -1 on error (no memory). */ +SFUNC int fio_queue_push_urgent(fio_queue_s *q, fio_queue_task_s task); + +/** + * Pushes a task to the queue, offering named arguments for the task. + * Returns -1 on error. + */ +#define fio_queue_push_urgent(q, ...) \ + fio_queue_push_urgent((q), (fio_queue_task_s){__VA_ARGS__}) + +/** Pops a task from the queue (FIFO). Returns a NULL task on error. */ +SFUNC fio_queue_task_s fio_queue_pop(fio_queue_s *q); + +/** Performs a task from the queue. Returns -1 on error (queue empty). */ +SFUNC int fio_queue_perform(fio_queue_s *q); + +/** Performs all tasks in the queue. */ +SFUNC void fio_queue_perform_all(fio_queue_s *q); + +/** returns the number of tasks in the queue. */ +FIO_IFUNC uint32_t fio_queue_count(fio_queue_s *q); + +/** Adds worker / consumer threads to perform the jobs in the queue. */ +SFUNC int fio_queue_workers_add(fio_queue_s *q, size_t count); + +/** Signals all worker threads to stop performing tasks and terminate. */ +SFUNC void fio_queue_workers_stop(fio_queue_s *q); + +/** Signals all worker threads to stop, waiting for them to complete. */ +SFUNC void fio_queue_workers_join(fio_queue_s *q); + +/** Signals all worker threads to go back to work (new tasks added). */ +SFUNC void fio_queue_workers_wake(fio_queue_s *q); + +/* ***************************************************************************** +Timer Queue Types and API +***************************************************************************** */ + +typedef struct fio___timer_event_s fio___timer_event_s; + +typedef struct { + fio___timer_event_s *next; + FIO___LOCK_TYPE lock; +} fio_timer_queue_s; + +#if FIO_USE_THREAD_MUTEX_TMP +#define FIO_TIMER_QUEUE_INIT \ + { .lock = ((fio_thread_mutex_t)FIO_THREAD_MUTEX_INIT) } +#else +#define FIO_TIMER_QUEUE_INIT \ + { .lock = FIO_LOCK_INIT } +#endif + +typedef struct { + /** The timer function. If it returns a non-zero value, the timer stops. */ + int (*fn)(void *, void *); + /** Opaque user data. */ + void *udata1; + /** Opaque user data. */ + void *udata2; + /** Called when the timer is done (finished). */ + void (*on_finish)(void *, void *); + /** Timer interval, in milliseconds. */ + uint32_t every; + /** The number of times the timer should be performed. -1 == infinity. */ + int32_t repetitions; + /** Millisecond at which to start. If missing, filled automatically. */ + int64_t start_at; +} fio_timer_schedule_args_s; + +/** Adds a time-bound event to the timer queue. */ +SFUNC void fio_timer_schedule(fio_timer_queue_s *timer_queue, + fio_timer_schedule_args_s args); + +/** A MACRO allowing named arguments to be used. See fio_timer_schedule_args_s. + */ +#define fio_timer_schedule(timer_queue, ...) \ + fio_timer_schedule((timer_queue), (fio_timer_schedule_args_s){__VA_ARGS__}) + +/** Pushes due events from the timer queue to an event queue. */ +SFUNC size_t fio_timer_push2queue(fio_queue_s *queue, + fio_timer_queue_s *timer_queue, + int64_t now_in_milliseconds); + +/* + * Returns the millisecond at which the next event should occur. + * + * If no timer is due (list is empty), returns `(uint64_t)-1`. + * + * NOTE: unless manually specified, millisecond timers are relative to + * `fio_time_milli()`. + */ +FIO_IFUNC int64_t fio_timer_next_at(fio_timer_queue_s *timer_queue); + +/** + * Clears any waiting timer bound tasks. + * + * NOTE: + * + * The timer queue must NEVER be freed when there's a chance that timer tasks + * are waiting to be performed in a `fio_queue_s`. + * + * This is due to the fact that the tasks may try to reschedule themselves (if + * they repeat). + */ +SFUNC void fio_timer_destroy(fio_timer_queue_s *timer_queue); + +/* ***************************************************************************** +Queue Inline Helpers +***************************************************************************** */ + +/** returns the number of tasks in the queue. */ +FIO_IFUNC uint32_t fio_queue_count(fio_queue_s *q) { return q->count; } + +/** Initializes a fio_queue_s object. */ +FIO_IFUNC void fio_queue_init(fio_queue_s *q) { + /* do this manually, we don't want to reset a whole page */ + q->r = &q->mem; + q->w = &q->mem; + q->count = 0; + q->consumers = FIO_LIST_INIT(q->consumers); + q->lock = FIO___LOCK_INIT; + q->mem.next = NULL; + q->mem.r = q->mem.w = q->mem.dir = 0; +} + +/* ***************************************************************************** +Timer Queue Inline Helpers +***************************************************************************** */ + +struct fio___timer_event_s { + int (*fn)(void *, void *); + void *udata1; + void *udata2; + void (*on_finish)(void *udata1, void *udata2); + int64_t due; + uint32_t every; + int32_t repetitions; + struct fio___timer_event_s *next; +}; + +/* + * Returns the millisecond at which the next event should occur. + * + * If no timer is due (list is empty), returns `-1`. + * + * NOTE: unless manually specified, millisecond timers are relative to + * `fio_time_milli()`. + */ +FIO_IFUNC int64_t fio_timer_next_at(fio_timer_queue_s *tq) { + int64_t v = -1; + if (!tq) + goto missing_tq; + if (!tq || !tq->next) + return v; + FIO___LOCK_LOCK(tq->lock); + if (tq->next) + v = tq->next->due; + FIO___LOCK_UNLOCK(tq->lock); + return v; + +missing_tq: + FIO_LOG_ERROR("`fio_timer_next_at` called with a NULL timer queue!"); + return v; +} + +/* ***************************************************************************** +Queue Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* task queue leak detection */ +FIO_LEAK_COUNTER_DEF(fio_queue) +FIO_LEAK_COUNTER_DEF(fio_queue_task_rings) +/** Destroys a queue and re-initializes it, after freeing any used resources. */ +SFUNC void fio_queue_destroy(fio_queue_s *q) { + for (;;) { + FIO___LOCK_LOCK(q->lock); + while (q->r) { + fio___task_ring_s *tmp = q->r; + q->r = q->r->next; + if (tmp != &q->mem) + FIO_MEM_FREE_(tmp, sizeof(*tmp)); + } + if (FIO_LIST_IS_EMPTY(&q->consumers)) { + FIO___LOCK_UNLOCK(q->lock); + break; + } + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + fio_atomic_or(&pos->stop, 1); + for (size_t i = 0; i < pos->workers; ++i) + fio_thread_cond_signal(&pos->cond); + } + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + FIO___LOCK_UNLOCK(q->lock); + fio_thread_join(&pos->thread); + FIO___LOCK_LOCK(q->lock); + } + FIO___LOCK_UNLOCK(q->lock); + if (FIO_LIST_IS_EMPTY(&q->consumers)) + break; + FIO_THREAD_RESCHEDULE(); + } + FIO___LOCK_DESTROY(q->lock); + fio_queue_init(q); +} + +/** Creates a new queue object (allocated on the heap). */ +SFUNC fio_queue_s *fio_queue_new(void) { + fio_queue_s *q = (fio_queue_s *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*q), 0); + if (!q) + return NULL; + fio_queue_init(q); + FIO_LEAK_COUNTER_ON_ALLOC(fio_queue); + return q; +} + +/** Frees a queue object after calling fio_queue_destroy. */ +SFUNC void fio_queue_free(fio_queue_s *q) { + fio_queue_destroy(q); + if (q) { + FIO_LEAK_COUNTER_ON_FREE(fio_queue); + FIO_MEM_FREE_(q, sizeof(*q)); + } +} + +FIO_IFUNC int fio___task_ring_push(fio___task_ring_s *r, + fio_queue_task_s task) { + if (r->dir && r->r == r->w) + return -1; + r->buf[r->w] = task; + ++(r->w); + if (r->w == FIO_QUEUE_TASKS_PER_ALLOC) { + r->w = 0; + r->dir = ~r->dir; + } + return 0; +} + +FIO_IFUNC int fio___task_ring_unpop(fio___task_ring_s *r, + fio_queue_task_s task) { + if (r->dir && r->r == r->w) + return -1; + if (!r->r) { + r->r = FIO_QUEUE_TASKS_PER_ALLOC; + r->dir = ~r->dir; + } + --r->r; + r->buf[r->r] = task; + return 0; +} + +FIO_IFUNC fio_queue_task_s fio___task_ring_pop(fio___task_ring_s *r) { + fio_queue_task_s t = {.fn = NULL}; + if (!r->dir && r->r == r->w) { + return t; + } + t = r->buf[r->r]; + r->buf[r->r] = (fio_queue_task_s){.fn = NULL}; + ++r->r; + if (r->r == FIO_QUEUE_TASKS_PER_ALLOC) { + r->r = 0; + r->dir = ~r->dir; + } + return t; +} + +int fio_queue_push___(void); /* sublime text marker */ +/** Pushes a task to the queue. Returns -1 on error. */ +SFUNC int fio_queue_push FIO_NOOP(fio_queue_s *q, fio_queue_task_s task) { + if (!task.fn) + return 0; + FIO___LOCK_LOCK(q->lock); + if (fio___task_ring_push(q->w, task)) { + if (q->w != &q->mem && q->mem.next == NULL) { + q->w->next = &q->mem; + q->mem.w = q->mem.r = q->mem.dir = 0; + } else { + void *tmp = (fio___task_ring_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*q->w->next), 0); + if (!tmp) + goto no_mem; + FIO_LEAK_COUNTER_ON_ALLOC(fio_queue_task_rings); + q->w->next = (fio___task_ring_s *)tmp; + if (!FIO_MEM_REALLOC_IS_SAFE_) { + q->w->next->r = q->w->next->w = q->w->next->dir = 0; + + q->w->next->next = NULL; + } + } + q->w = q->w->next; + fio___task_ring_push(q->w, task); + } + ++q->count; + if (!FIO_LIST_IS_EMPTY(&q->consumers)) { + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + fio_thread_cond_signal(&pos->cond); + } + } + FIO___LOCK_UNLOCK(q->lock); + return 0; +no_mem: + FIO___LOCK_UNLOCK(q->lock); + FIO_LOG_ERROR("No memory for Queue %p to increase task ring buffer.", + (void *)q); + return -1; +} + +int fio_queue_push_urgent___(void); /* IDE marker */ +/** Pushes a task to the head of the queue. Returns -1 on error (no memory). */ +SFUNC int fio_queue_push_urgent FIO_NOOP(fio_queue_s *q, + fio_queue_task_s task) { + if (!task.fn) + return 0; + FIO___LOCK_LOCK(q->lock); + if (fio___task_ring_unpop(q->r, task)) { + /* such a shame... but we must allocate a while task block for one task */ + fio___task_ring_s *tmp = + (fio___task_ring_s *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*q->w->next), 0); + if (!tmp) + goto no_mem; + FIO_LEAK_COUNTER_ON_ALLOC(fio_queue_task_rings); + tmp->next = q->r; + q->r = tmp; + tmp->w = 1; + tmp->dir = tmp->r = 0; + tmp->buf[0] = task; + } + ++q->count; + if (!FIO_LIST_IS_EMPTY(&q->consumers)) { + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + fio_thread_cond_signal(&pos->cond); + } + } + FIO___LOCK_UNLOCK(q->lock); + return 0; +no_mem: + FIO___LOCK_UNLOCK(q->lock); + FIO_LOG_ERROR("No memory for Queue %p to increase task ring buffer.", + (void *)q); + return -1; +} + +/** Pops a task from the queue (FIFO). Returns a NULL task on error. */ +SFUNC fio_queue_task_s fio_queue_pop(fio_queue_s *q) { + fio_queue_task_s t = {.fn = NULL}; + fio___task_ring_s *to_free = NULL; + fio___task_ring_s *to_free_tst = NULL; + if (!q->count) + return t; + FIO___LOCK_LOCK(q->lock); + if (!q->count) + goto finish; + if (!(t = fio___task_ring_pop(q->r)).fn) { + to_free = q->r; + q->r = to_free->next; + to_free->next = NULL; + t = fio___task_ring_pop(q->r); + } + if (t.fn && !(--q->count) && q->r != &q->mem) { + if (to_free && to_free != &q->mem) { // edge case + FIO_LEAK_COUNTER_ON_FREE(fio_queue_task_rings); + FIO_MEM_FREE_(to_free, sizeof(*to_free)); + } + to_free = q->r; + q->r = q->w = &q->mem; + q->mem.w = q->mem.r = q->mem.dir = 0; + } + to_free_tst = &q->mem; +finish: + FIO___LOCK_UNLOCK(q->lock); + if (to_free && to_free != to_free_tst) { + FIO_LEAK_COUNTER_ON_FREE(fio_queue_task_rings); + FIO_MEM_FREE_(to_free, sizeof(*to_free)); + } + return t; +} + +/** Performs a task from the queue. Returns -1 on error (queue empty). */ +SFUNC int fio_queue_perform(fio_queue_s *q) { + fio_queue_task_s t = fio_queue_pop(q); + if (!t.fn) + return -1; + t.fn(t.udata1, t.udata2); + return 0; +} + +/** Performs all tasks in the queue. */ +SFUNC void fio_queue_perform_all(fio_queue_s *q) { + fio_queue_task_s t; + while ((t = fio_queue_pop(q)).fn) + t.fn(t.udata1, t.udata2); +} + +/* ***************************************************************************** +Queue Consumer Threads +***************************************************************************** */ + +FIO_SFUNC void *fio___queue_worker_task(void *g_) { + fio___thread_group_s *grp = (fio___thread_group_s *)g_; + fio_state_callback_force(FIO_CALL_ON_WORKER_THREAD_START); + while (!grp->stop) { + fio_queue_perform_all(grp->queue); + fio_thread_mutex_lock(&grp->mutex); + if (!grp->stop) + fio_thread_cond_wait(&grp->cond, &grp->mutex); + fio_thread_mutex_unlock(&grp->mutex); + fio_queue_perform_all(grp->queue); + } + fio_state_callback_force(FIO_CALL_ON_WORKER_THREAD_END); + return NULL; +} +FIO_SFUNC void *fio___queue_worker_manager(void *g_) { + fio_thread_t threads_buf[256]; + fio___thread_group_s grp = *(fio___thread_group_s *)g_; + FIO_LIST_PUSH(&grp.queue->consumers, &grp.node); + grp.stop = 0; + fio_thread_t *threads = + grp.workers > 256 + ? ((fio_thread_t *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*threads) * grp.workers, 0)) + : threads_buf; + fio_thread_mutex_init(&grp.mutex); + fio_thread_cond_init(&grp.cond); + for (size_t i = 0; i < grp.workers; ++i) { + fio_thread_create(threads + i, fio___queue_worker_task, (void *)&grp); + } + fio_atomic_and(&((fio___thread_group_s *)g_)->stop, 0); + /* from this point on, g_ is invalid! */ + for (size_t i = 0; i < grp.workers; ++i) { + fio_thread_join(threads + i); + } + if (threads != threads_buf) + FIO_MEM_FREE_(threads, sizeof(*threads) * grp.workers); + FIO___LOCK_LOCK(grp.queue->lock); + FIO_LIST_REMOVE(&grp.node); + FIO___LOCK_UNLOCK(grp.queue->lock); + fio_queue_perform_all(grp.queue); + return NULL; +} + +SFUNC int fio_queue_workers_add(fio_queue_s *q, size_t workers) { + FIO___LOCK_LOCK(q->lock); + if (!q->consumers.next || !q->consumers.prev) { + q->consumers = FIO_LIST_INIT(q->consumers); + } + fio___thread_group_s grp = {.queue = q, .workers = workers, .stop = 1}; + if (fio_thread_create(&grp.thread, fio___queue_worker_manager, &grp)) { + FIO___LOCK_UNLOCK(q->lock); + return -1; + } + while (grp.stop) + FIO_THREAD_RESCHEDULE(); + FIO___LOCK_UNLOCK(q->lock); + return 0; +} + +SFUNC void fio_queue_workers_stop(fio_queue_s *q) { + if (FIO_LIST_IS_EMPTY(&q->consumers)) + return; + FIO___LOCK_LOCK(q->lock); + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + fio_atomic_or(&pos->stop, 1); + for (size_t i = 0; i < pos->workers * 2; ++i) + fio_thread_cond_signal(&pos->cond); + } + FIO___LOCK_UNLOCK(q->lock); +} + +/** Signals all worker threads to go back to work (new tasks were). */ +SFUNC void fio_queue_workers_wake(fio_queue_s *q) { + if (FIO_LIST_IS_EMPTY(&q->consumers)) + return; + FIO___LOCK_LOCK(q->lock); + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + fio_thread_cond_signal(&pos->cond); + } + FIO___LOCK_UNLOCK(q->lock); +} + +/** Signals all worker threads to stop, waiting for them to complete. */ +SFUNC void fio_queue_workers_join(fio_queue_s *q) { + fio_queue_workers_stop(q); + FIO___LOCK_LOCK(q->lock); + FIO_LIST_EACH(fio___thread_group_s, node, &q->consumers, pos) { + FIO___LOCK_UNLOCK(q->lock); + fio_thread_join(&pos->thread); + FIO___LOCK_LOCK(q->lock); + } + FIO___LOCK_UNLOCK(q->lock); +} + +/* ***************************************************************************** +Timer Queue Implementation +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio___timer_event_s) + +FIO_IFUNC void fio___timer_insert(fio___timer_event_s **pos, + fio___timer_event_s *e) { + while (*pos && e->due >= (*pos)->due) + pos = &((*pos)->next); + e->next = *pos; + *pos = e; +} + +FIO_IFUNC fio___timer_event_s *fio___timer_pop(fio___timer_event_s **pos, + int64_t due) { + if (!*pos || (*pos)->due > due) + return NULL; + fio___timer_event_s *t = *pos; + *pos = t->next; + return t; +} + +FIO_IFUNC fio___timer_event_s *fio___timer_event_new( + fio_timer_schedule_args_s args) { + fio___timer_event_s *t = NULL; + t = (fio___timer_event_s *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*t), 0); + if (!t) + goto init_error; + FIO_LEAK_COUNTER_ON_ALLOC(fio___timer_event_s); + if (!args.repetitions) + args.repetitions = 1; + *t = (fio___timer_event_s){ + .fn = args.fn, + .udata1 = args.udata1, + .udata2 = args.udata2, + .on_finish = args.on_finish, + .due = args.start_at + args.every, + .every = args.every, + .repetitions = args.repetitions, + }; + return t; +init_error: + if (args.on_finish) + args.on_finish(args.udata1, args.udata2); + return NULL; +} + +FIO_IFUNC void fio___timer_event_free(fio_timer_queue_s *tq, + fio___timer_event_s *t) { + if (tq && (t->repetitions < 0 || fio_atomic_sub_fetch(&t->repetitions, 1))) { + FIO___LOCK_LOCK(tq->lock); + fio___timer_insert(&tq->next, t); + FIO___LOCK_UNLOCK(tq->lock); + return; + } + if (t->on_finish) + t->on_finish(t->udata1, t->udata2); + FIO_LEAK_COUNTER_ON_FREE(fio___timer_event_s); + FIO_MEM_FREE_(t, sizeof(*t)); +} + +FIO_SFUNC void fio___timer_perform(void *timer_, void *t_) { + fio_timer_queue_s *tq = (fio_timer_queue_s *)timer_; + fio___timer_event_s *t = (fio___timer_event_s *)t_; + if (t->fn(t->udata1, t->udata2)) + tq = NULL; + t->due += t->every; + fio___timer_event_free(tq, t); +} + +/** Pushes due events from the timer queue to an event queue. */ +SFUNC size_t fio_timer_push2queue(fio_queue_s *queue, + fio_timer_queue_s *timer, + int64_t start_at) { + size_t r = 0; + if (!start_at) + start_at = fio_time_milli(); + if (FIO___LOCK_TRYLOCK(timer->lock)) + return 0; + fio___timer_event_s *t; + while ((t = fio___timer_pop(&timer->next, start_at))) { + fio_queue_push(queue, + .fn = fio___timer_perform, + .udata1 = timer, + .udata2 = t); + ++r; + } + FIO___LOCK_UNLOCK(timer->lock); + return r; +} + +void fio_timer_schedule___(void); /* IDE marker */ +/** Adds a time-bound event to the timer queue. */ +SFUNC void fio_timer_schedule FIO_NOOP(fio_timer_queue_s *timer, + fio_timer_schedule_args_s args) { + fio___timer_event_s *t = NULL; + if (!timer || !args.fn || !args.every) + goto no_timer_queue; + if (!args.start_at) + args.start_at = fio_time_milli(); + t = fio___timer_event_new(args); + if (!t) + return; + FIO___LOCK_LOCK(timer->lock); + fio___timer_insert(&timer->next, t); + FIO___LOCK_UNLOCK(timer->lock); + return; +no_timer_queue: + if (args.on_finish) + args.on_finish(args.udata1, args.udata2); + FIO_LOG_ERROR("fio_timer_schedule called with illegal arguments."); +} + +/** + * Clears any waiting timer bound tasks. + * + * NOTE: + * + * The timer queue must NEVER be freed when there's a chance that timer tasks + * are waiting to be performed in a `fio_queue_s`. + * + * This is due to the fact that the tasks may try to reschedule themselves (if + * they repeat). + */ +SFUNC void fio_timer_destroy(fio_timer_queue_s *tq) { + fio___timer_event_s *next; + FIO___LOCK_LOCK(tq->lock); + next = tq->next; + tq->next = NULL; + FIO___LOCK_UNLOCK(tq->lock); + FIO___LOCK_DESTROY(tq->lock); + while (next) { + fio___timer_event_s *tmp = next; + + next = next->next; + fio___timer_event_free(NULL, tmp); + } +} +/* ***************************************************************************** +Queue/Timer Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_QUEUE +#endif /* FIO_QUEUE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_STREAM /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + A packet based data stream for storing / buffering endless data. + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_STREAM) && !defined(H___FIO_STREAM___H) +#define H___FIO_STREAM___H +#include + +#ifndef FIO_STREAM_COPY_PER_PACKET +/** Break apart large memory blocks into smaller pieces. by default 96Kb */ +#define FIO_STREAM_COPY_PER_PACKET 98304 +#endif + +#ifndef FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN +/** If the data added is less than said bytes, copy is preferred (locality). */ +#define FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN 116 +#ifdef DEBUG +#undef FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN +#define FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN 8 +#endif +#endif + +/* ***************************************************************************** +Stream API - types, constructor / destructor +***************************************************************************** */ + +typedef struct fio_stream_packet_s fio_stream_packet_s; + +typedef struct { + /* do not directly access! */ + fio_stream_packet_s *next; + fio_stream_packet_s **pos; + size_t consumed; + size_t length; +} fio_stream_s; + +/* at this point publish (declare only) the public API */ + +#ifndef FIO_STREAM_INIT +/* Initialization macro. */ +#define FIO_STREAM_INIT(s) \ + { .next = NULL, .pos = &(s).next } +#endif + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/* Allocates a new object on the heap and initializes it's memory. */ +FIO_IFUNC fio_stream_s *fio_stream_new(void); + +/* Frees any internal data AND the object's container! */ +FIO_IFUNC int fio_stream_free(fio_stream_s *stream); + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** Destroys the object, re-initializing its container. */ +SFUNC void fio_stream_destroy(fio_stream_s *stream); + +/* ***************************************************************************** +Stream API - packing data into packets and adding it to the stream +***************************************************************************** */ + +/** Packs data into a fio_stream_packet_s container. */ +SFUNC fio_stream_packet_s *fio_stream_pack_data(void *buf, + size_t len, + size_t offset, + uint8_t copy_buffer, + void (*dealloc_func)(void *)); + +/** Packs a file descriptor into a fio_stream_packet_s container. */ +SFUNC fio_stream_packet_s *fio_stream_pack_fd(int fd, + size_t len, + size_t offset, + uint8_t keep_open); + +/** Adds a packet to the stream. This isn't thread safe.*/ +SFUNC void fio_stream_add(fio_stream_s *stream, fio_stream_packet_s *packet); + +/** Destroys the fio_stream_packet_s - call this ONLY if unused. */ +SFUNC void fio_stream_pack_free(fio_stream_packet_s *packet); + +/* ***************************************************************************** +Stream API - Consuming the stream +***************************************************************************** */ + +/** + * Reads data from the stream (if any), leaving it in the stream. + * + * `buf` MUST point to a buffer with - at least - `len` bytes. This is required + * in case the packed data is fragmented or references a file and needs to be + * copied to an available buffer. + * + * On error, or if the stream is empty, `buf` will be set to NULL and `len` will + * be set to zero. + * + * Otherwise, `buf` may retain the same value or it may point directly to a + * memory address within the stream's buffer (the original value may be lost) + * and `len` will be updated to the largest possible value for valid data that + * can be read from `buf`. + * + * Note: this isn't thread safe. + */ +SFUNC void fio_stream_read(fio_stream_s *stream, char **buf, size_t *len); + +/** + * Advances the Stream, so the first `len` bytes are marked as consumed. + * + * Note: this isn't thread safe. + */ +SFUNC void fio_stream_advance(fio_stream_s *stream, size_t len); + +/** + * Returns true if there's any data in the stream. + * + * Note: this isn't truly thread safe. + */ +FIO_IFUNC uint8_t fio_stream_any(fio_stream_s *stream); + +/** + * Returns the number of bytes waiting in the stream. + * + * Note: this isn't truly thread safe. + */ +FIO_IFUNC size_t fio_stream_length(fio_stream_s *stream); + +/* ***************************************************************************** + + + + + + + + + Stream Implementation + + + + + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Stream Implementation - inlined static functions +***************************************************************************** */ + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +FIO_LEAK_COUNTER_DEF(fio_stream) +/* Allocates a new object on the heap and initializes it's memory. */ +FIO_IFUNC fio_stream_s *fio_stream_new(void) { + fio_stream_s *s = (fio_stream_s *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*s), 0); + if (s) { + *s = (fio_stream_s)FIO_STREAM_INIT(s[0]); + } + FIO_LEAK_COUNTER_ON_ALLOC(fio_stream); + return s; +} +/* Frees any internal data AND the object's container! */ +FIO_IFUNC int fio_stream_free(fio_stream_s *s) { + fio_stream_destroy(s); + FIO_MEM_FREE_(s, sizeof(*s)); + FIO_LEAK_COUNTER_ON_FREE(fio_stream); + return 0; +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* Returns true if there's any data in the stream */ +FIO_IFUNC uint8_t fio_stream_any(fio_stream_s *s) { return s && s->next; } + +/* Returns the number of bytes waiting in the stream */ +FIO_IFUNC size_t fio_stream_length(fio_stream_s *s) { return s->length; } + +/* ***************************************************************************** +Stream Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_IFUNC void fio_stream_packet_free_all(fio_stream_packet_s *p); +/* Frees any internal data AND the object's container! */ +SFUNC void fio_stream_destroy(fio_stream_s *s) { + if (!s) + return; + fio_stream_packet_free_all(s->next); + *s = (fio_stream_s)FIO_STREAM_INIT(s[0]); + return; +} + +FIO_LEAK_COUNTER_DEF(fio_stream_packet_s) + +/* ***************************************************************************** +Stream API - packing data into packets and adding it to the stream +***************************************************************************** */ + +struct fio_stream_packet_s { + fio_stream_packet_s *next; +}; + +typedef enum { + FIO_PACKET_TYPE_EMBEDDED = 0, + FIO_PACKET_TYPE_EXTERNAL = 1, + FIO_PACKET_TYPE_FILE = 2, + FIO_PACKET_TYPE_FILE_NO_CLOSE = 3, +} fio_stream_packet_type_e; +#define FIO_STREAM___TYPE_BITS 2 + +typedef struct fio_stream_packet_embd_s { + fio_stream_packet_type_e type; + uint32_t length; + char buf[]; +} fio_stream_packet_embd_s; + +typedef struct fio_stream_packet_extrn_s { + fio_stream_packet_type_e type; + size_t length; + char *buf; + uintptr_t offset; + void (*dealloc)(void *buf); +} fio_stream_packet_extrn_s; + +/** User-space socket buffer data */ +typedef struct { + fio_stream_packet_type_e type; + size_t length; + size_t offset; + int fd; +} fio_stream_packet_fd_s; + +FIO_SFUNC void fio_stream_packet_free(fio_stream_packet_s *p) { + if (!p) + return; + FIO_LEAK_COUNTER_ON_FREE(fio_stream_packet_s); + union { + fio_stream_packet_embd_s *em; + fio_stream_packet_extrn_s *ext; + fio_stream_packet_fd_s *f; + } const u = {.em = (fio_stream_packet_embd_s *)(p + 1)}; + switch (u.em->type) { + case FIO_PACKET_TYPE_EMBEDDED: + FIO_MEM_FREE_(p, sizeof(*p) + sizeof(*u.em) + u.em->length); + break; + case FIO_PACKET_TYPE_EXTERNAL: + if (u.ext->dealloc) + u.ext->dealloc(u.ext->buf); + FIO_MEM_FREE_(p, sizeof(*p) + sizeof(*u.ext)); + break; + case FIO_PACKET_TYPE_FILE: close(u.f->fd); +#ifdef DEBUG + FIO_LOG_DEBUG2("fio_stream_packet_free closed file fd %d", u.f->fd); +#endif + /* fall through */ + case FIO_PACKET_TYPE_FILE_NO_CLOSE: + FIO_MEM_FREE_(p, sizeof(*p) + sizeof(*u.f)); + break; + } +} + +FIO_IFUNC void fio_stream_packet_free_all(fio_stream_packet_s *p) { + while (p) { + register fio_stream_packet_s *t = p; + p = p->next; + fio_stream_packet_free(t); + } +} + +FIO_IFUNC size_t fio___stream_p2len(fio_stream_packet_s *p) { + size_t len = 0; + if (!p) + return len; + union { + fio_stream_packet_embd_s *em; + fio_stream_packet_extrn_s *ext; + fio_stream_packet_fd_s *f; + } const u = {.em = (fio_stream_packet_embd_s *)(p + 1)}; + + switch ((fio_stream_packet_type_e)(u.em->type & + ((1UL << FIO_STREAM___TYPE_BITS) - 1))) { + case FIO_PACKET_TYPE_EMBEDDED: return len = u.em->length; return len; + case FIO_PACKET_TYPE_EXTERNAL: len = u.ext->length; return len; + case FIO_PACKET_TYPE_FILE: /* fall through */ + case FIO_PACKET_TYPE_FILE_NO_CLOSE: len = u.f->length; return len; + } + return len; +} + +/** Packs data into a fio_stream_packet_s container. */ +SFUNC fio_stream_packet_s *fio_stream_pack_data(void *buf, + size_t len, + size_t offset, + uint8_t copy_buffer, + void (*dealloc_func)(void *)) { + fio_stream_packet_s *p = NULL; + if (!len || !buf || (len & ((~(0UL)) << (32 - FIO_STREAM___TYPE_BITS)))) + goto error; + if (copy_buffer || len < FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN) { + while (len) { + /* break apart large memory blocks into smaller pieces */ + const size_t slice = + (len > FIO_STREAM_COPY_PER_PACKET) ? FIO_STREAM_COPY_PER_PACKET : len; + fio_stream_packet_embd_s *em; + fio_stream_packet_s *tmp = (fio_stream_packet_s *)FIO_MEM_REALLOC_( + NULL, + 0, + sizeof(*p) + sizeof(*em) + (sizeof(char) * slice), + 0); + if (!tmp) + goto error; + FIO_LEAK_COUNTER_ON_ALLOC(fio_stream_packet_s); + tmp->next = p; + em = (fio_stream_packet_embd_s *)(tmp + 1); + em->type = FIO_PACKET_TYPE_EMBEDDED; + em->length = (uint32_t)slice; + FIO_MEMCPY(em->buf, (char *)buf + offset + (len - slice), slice); + p = tmp; + len -= slice; + } + if (dealloc_func) + dealloc_func(buf); + } else { + fio_stream_packet_extrn_s *ext; + p = (fio_stream_packet_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*p) + sizeof(*ext), 0); + if (!p) + goto error; + FIO_LEAK_COUNTER_ON_ALLOC(fio_stream_packet_s); + p->next = NULL; + ext = (fio_stream_packet_extrn_s *)(p + 1); + *ext = (fio_stream_packet_extrn_s){ + .type = FIO_PACKET_TYPE_EXTERNAL, + .length = (uint32_t)len, + .buf = (char *)buf, + .offset = offset, + .dealloc = dealloc_func, + }; + } + return p; + +error: + if (dealloc_func) + dealloc_func(buf); + fio_stream_packet_free_all(p); + return p; +} + +/** Packs a file descriptor into a fio_stream_packet_s container. */ +SFUNC fio_stream_packet_s *fio_stream_pack_fd(int fd, + size_t len, + size_t offset, + uint8_t keep_open) { + fio_stream_packet_s *p = NULL; + fio_stream_packet_fd_s *f; + if ((unsigned)(fd + 1) < 2) + goto no_file; + + if (!len) { + /* review file total length and auto-calculate */ + len = fio_fd_size(fd); + if (!len || offset >= len || len >= 0x7FFFFFFF) + goto error; + len -= offset; + } + + p = (fio_stream_packet_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*p) + sizeof(*f), 0); + if (!p) + goto error; + FIO_LEAK_COUNTER_ON_ALLOC(fio_stream_packet_s); + p->next = NULL; + f = (fio_stream_packet_fd_s *)(p + 1); + *f = (fio_stream_packet_fd_s){ + .type = + (keep_open ? FIO_PACKET_TYPE_FILE_NO_CLOSE : FIO_PACKET_TYPE_FILE), + .length = len, + .offset = offset, + .fd = fd, + }; +#ifdef DEBUG + FIO_LOG_DEBUG2("fio_stream_pack_fd wrapping file fd %d", fd); +#endif + return p; +error: + if (!keep_open) + close(fd); +no_file: + return p; +} + +/** Adds a packet to the stream. This isn't thread safe.*/ +SFUNC void fio_stream_add(fio_stream_s *s, fio_stream_packet_s *p) { + fio_stream_packet_s *last = p; + size_t len = 0; + + if (!s || !p) + goto error; + len = fio___stream_p2len(p); + + while (last->next) { + last = last->next; + len += fio___stream_p2len(last); + } + if (!s->pos) + s->pos = &s->next; + *s->pos = p; + s->pos = &last->next; + s->length += len; + return; +error: + fio_stream_pack_free(p); +} + +/** Destroys the fio_stream_packet_s - call this ONLY if unused. */ +SFUNC void fio_stream_pack_free(fio_stream_packet_s *p) { + fio_stream_packet_free_all(p); +} + +/* ***************************************************************************** +Stream API - Consuming the stream +***************************************************************************** */ + +FIO_SFUNC void fio___stream_read_internal(fio_stream_packet_s *p, + char **buf, + size_t *len, + size_t buf_offset, + size_t offset, + size_t must_copy) { + if (!p || !len[0]) { + len[0] = 0; + return; + } + union { + fio_stream_packet_embd_s *em; + fio_stream_packet_extrn_s *ext; + fio_stream_packet_fd_s *f; + } const u = {.em = (fio_stream_packet_embd_s *)(p + 1)}; + size_t written = 0; + + switch (u.em->type) { + case FIO_PACKET_TYPE_EMBEDDED: + if (!buf[0] || !len[0] || + (!must_copy && (!p->next || u.em->length >= len[0] + offset))) { + buf[0] = u.em->buf + offset; + len[0] = (size_t)u.em->length - offset; + return; + } + written = u.em->length - offset; + if (written > len[0]) + written = len[0]; + if (written) { + FIO_MEMCPY(buf[0] + buf_offset, u.em->buf + offset, written); + len[0] -= written; + } + if (len[0]) { + fio___stream_read_internal(p->next, buf, len, written + buf_offset, 0, 1); + } + len[0] += written; + return; + case FIO_PACKET_TYPE_EXTERNAL: + if (!buf[0] || !len[0] || + (!must_copy && (!p->next || u.ext->length >= len[0] + offset))) { + buf[0] = u.ext->buf + u.ext->offset + offset; + len[0] = (size_t)(u.ext->length) - offset; + return; + } + written = u.ext->length - offset; + if (written > len[0]) + written = len[0]; + if (written) { + FIO_MEMCPY(buf[0] + buf_offset, + u.ext->buf + u.ext->offset + offset, + written); + len[0] -= written; + } + if (len[0]) { + fio___stream_read_internal(p->next, buf, len, written + buf_offset, 0, 1); + } + len[0] += written; + return; + break; + case FIO_PACKET_TYPE_FILE: /* fall through */ + case FIO_PACKET_TYPE_FILE_NO_CLOSE: + if (!buf[0] || !len[0]) { + len[0] = 0; + return; + } + { + uint8_t possible_eof_surprise = 0; + written = u.f->length - offset; /* written here == to be read & written */ + if (written > len[0]) + written = len[0]; + if (written) { + ssize_t act; + retry_on_signal: + act = fio_fd_read(u.f->fd, + buf[0] + buf_offset, + written, + u.f->offset + offset); + if (act <= 0) { + /* no more data in the file? */ + FIO_LOG_DEBUG("file read error for %d: %s", u.f->fd, strerror(errno)); + if (errno == EINTR) + goto retry_on_signal; + // u.f->length = offset; /* mark EOF */ + } else if ((size_t)act != written) { + /* a surprising EOF? */ + written = act; + possible_eof_surprise = 1; + // u.f->length = offset + act; /* mark EOF? */ + } + len[0] -= written; + } + if (!possible_eof_surprise && len[0]) { + fio___stream_read_internal(p->next, + buf, + len, + written + buf_offset, + 0, + 1); + } + len[0] += written; + } + return; + } +} + +/** + * Reads data from the stream (if any), leaving it in the stream. + * + * `buf` MUST point to a buffer with - at least - `len` bytes. This is required + * in case the packed data is fragmented or references a file and needs to be + * copied to an available buffer. + * + * On error, or if the stream is empty, `buf` will be set to NULL and `len` will + * be set to zero. + * + * Otherwise, `buf` may retain the same value or it may point directly to a + * memory address wiithin the stream's buffer (the original value may be lost) + * and `len` will be updated to the largest possible value for valid data that + * can be read from `buf`. + * + * Note: this isn't thread safe. + */ +SFUNC void fio_stream_read(fio_stream_s *s, char **buf, size_t *len) { + if (!s || !s->next) + goto none; + fio___stream_read_internal(s->next, buf, len, 0, s->consumed, 0); + return; +none: + *buf = NULL; + *len = 0; +} + +/** + * Advances the Stream, so the first `len` bytes are marked as consumed. + * + * Note: this isn't thread safe. + */ +SFUNC void fio_stream_advance(fio_stream_s *s, size_t len) { + if (!s || !s->next) + return; + s->length -= len; + len += s->consumed; + while (len) { + size_t p_len = fio___stream_p2len(s->next); + if (len >= p_len) { + fio_stream_packet_s *p = s->next; + s->next = p->next; + fio_stream_packet_free(p); + len -= p_len; + if (!s->next) { + s->pos = &s->next; + s->consumed = 0; + s->length = 0; + return; + } + } else { + s->consumed = len; + return; + } + } + s->consumed = len; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_STREAM___TYPE_BITS +#endif /* FIO_STREAM */ +#undef FIO_STREAM +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_STR /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Binary Safe String Core Helpers + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_STR) && !defined(H___FIO_STR___H) +#define H___FIO_STR___H +/* ***************************************************************************** +String Authorship Helpers (`fio_string_write` functions) +***************************************************************************** */ + +/** + * A reallocation callback type for buffers in a `fio_str_info_s`. + * + * The callback MUST allocate at least `len + 1` bytes, setting the new capacity + * in `dest->capa`. + * */ +typedef int (*fio_string_realloc_fn)(fio_str_info_s *dest, size_t len); +/** + * Writes data to the end of the string in the `fio_string_s` struct, + * returning an updated `fio_string_s` struct. + * + * The returned string is NUL terminated if edited. + * + * * `dest` an `fio_string_s` struct containing the destination string. + * + * * `reallocate` is a callback that attempts to reallocate more memory (i.e., + * using `realloc`) and returns an updated `fio_string_s` struct containing the + * updated capacity and buffer pointer (as well as the original length). + * + * On failure the original `fio_string_s` should be returned. if + * `reallocate` is NULL or fails, the data copied will be truncated. + * + * * `src` is the data to be written to the end of `dest`. + * + * * `len` is the length of the data to be written to the end of `dest`. + * + * Note: this function performs only minimal checks and assumes that `dest` is + * fully valid - i.e., that `dest.capa >= dest.len`, that `dest.buf` is + * valid, etc'. + * + * An example for a `reallocate` callback using the system's `realloc` function: + * + * int fio_string_realloc_system(fio_str_info_s *dest, size_t len_no_nul) { + * const size_t new_capa = fio_string_capa4len(len_pre_nul); + * void *tmp = realloc(dest.buf, new_capa); + * if (!tmp) + * return -1; + * dest.capa = new_capa; + * dest.buf = (char *)tmp; + * return 0; + * } + * + * An example for using the function: + * + * void example(void) { + * char buf[32]; + * fio_str_info_s str = FIO_STR_INFO3(buf, 0, 32); + * fio_string_write(&str, NULL, "The answer is: 0x", 17); + * str.len += fio_ltoa(str.buf + str.len, 42, 16); + * fio_string_write(&str, NULL, "!\n", 2); + * printf("%s", str.buf); + * } + */ +FIO_SFUNC int fio_string_write(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *restrict src, + size_t len); + +/** + * Similar to `fio_string_write`, only replacing/inserting a sub-string in a + * specific location. + * + * Negative `start_pos` values are calculated backwards, `-1` == end of String. + * + * When `overwrite_len` is zero, the function will insert the data at + * `start_pos`, pushing existing data until after the inserted data. + * + * If `overwrite_len` is non-zero, than `overwrite_len` bytes will be + * overwritten (or deleted). + * + * If `len == 0` than `src` will be ignored and the data marked for replacement + * will be erased. + */ +SFUNC int fio_string_replace(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + intptr_t start_pos, + size_t overwrite_len, + const void *src, + size_t len); + +/** Argument type used by fio_string_write2. */ +typedef struct { + size_t klass; + union { + struct { + size_t len; + const char *buf; + } str; + double f; + int64_t i; + uint64_t u; + } info; +} fio_string_write_s; + +/** + * Writes a group of objects (strings, numbers, etc') to `dest`. + * + * `dest` and `reallocate` are similar to `fio_string_write`. + * + * `src` is an array of `fio_string_write_s` structs, ending with a struct + * that's all set to 0. + * + * Use the `fio_string_write2` macro for ease, i.e.: + * + * fio_str_info_s str = {0}; + * fio_string_write2(&str, my_reallocate, + * FIO_STRING_WRITE_STR1("The answer is: "), + * FIO_STRING_WRITE_NUM(42), + * FIO_STRING_WRITE_STR2("(0x", 3), + * FIO_STRING_WRITE_HEX(42), + * FIO_STRING_WRITE_STR2(")", 1)); + * + * Note: this function might end up allocating more memory than absolutely + * required as it favors fast performance over memory savings. It performs only + * a single allocation (if any) and computes numeral string length only when + * writing the numbers to the string. + */ +SFUNC int fio_string_write2(fio_str_info_s *restrict dest, + fio_string_realloc_fn reallocate, + const fio_string_write_s srcs[]); + +/* Helper macro for fio_string_write2 */ +#define fio_string_write2(dest, reallocate, ...) \ + fio_string_write2((dest), \ + (reallocate), \ + (fio_string_write_s[]){__VA_ARGS__, {0}}) + +/** A macro to add a String to `fio_string_write2`. */ +#define FIO_STRING_WRITE_STR1(str_) \ + ((fio_string_write_s){ \ + .klass = 1, \ + .info.str = {.len = (size_t)FIO_STRLEN((str_)), .buf = (str_)}}) + +/** A macro to add a String with known length to `fio_string_write2`. */ +#define FIO_STRING_WRITE_STR2(str_, len_) \ + ((fio_string_write_s){.klass = 1, .info.str = {.len = (len_), .buf = (str_)}}) + +/** A macro to add a String with known length to `fio_string_write2`. */ +#define FIO_STRING_WRITE_STR_INFO(str_) \ + ((fio_string_write_s){.klass = 1, \ + .info.str = {.len = (str_).len, .buf = (str_).buf}}) + +/** A macro to add a signed number to `fio_string_write2`. */ +#define FIO_STRING_WRITE_NUM(num) \ + ((fio_string_write_s){.klass = 2, .info.i = (int64_t)(num)}) + +/** A macro to add an unsigned number to `fio_string_write2`. */ +#define FIO_STRING_WRITE_UNUM(num) \ + ((fio_string_write_s){.klass = 3, .info.u = (uint64_t)(num)}) + +/** A macro to add a hex representation to `fio_string_write2`. */ +#define FIO_STRING_WRITE_HEX(num) \ + ((fio_string_write_s){.klass = 4, .info.u = (uint64_t)(num)}) + +/** A macro to add a binary representation to `fio_string_write2`. */ +#define FIO_STRING_WRITE_BIN(num) \ + ((fio_string_write_s){.klass = 5, .info.u = (uint64_t)(num)}) + +/** A macro to add a float (double) to `fio_string_write2`. */ +#define FIO_STRING_WRITE_FLOAT(num) \ + ((fio_string_write_s){.klass = 6, .info.f = (double)(num)}) + +/* ***************************************************************************** +String Numerals support +***************************************************************************** */ + +/* Writes a signed number `i` to the String */ +SFUNC int fio_string_write_i(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int64_t i); +/* Writes an unsigned number `i` to the String */ +SFUNC int fio_string_write_u(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i); +/* Writes a hex representation of `i` to the String */ +SFUNC int fio_string_write_hex(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i); +/* Writes a binary representation of `i` to the String */ +SFUNC int fio_string_write_bin(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i); + +/* ***************************************************************************** +String printf style support +***************************************************************************** */ + +/** Similar to fio_string_write, only using printf semantics. */ +SFUNC FIO___PRINTF_STYLE(3, 0) int fio_string_printf( + fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *format, + ...); + +/** Similar to fio_string_write, only using vprintf semantics. */ +SFUNC FIO___PRINTF_STYLE(3, 0) int fio_string_vprintf( + fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *format, + va_list argv); + +/* ***************************************************************************** +String C / JSON escaping +***************************************************************************** */ + +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +SFUNC int fio_string_write_escape(fio_str_info_s *restrict dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len); + +/** Writes an escaped data into the string after un-escaping the data. */ +SFUNC int fio_string_write_unescape(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *enscaped, + size_t enscaped_len); + +/* ***************************************************************************** +String Base32 support +***************************************************************************** */ + +/** Writes data to String using base64 encoding. */ +SFUNC int fio_string_write_base32enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len); + +/** Writes decoded base64 data to String. */ +SFUNC int fio_string_write_base32dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len); + +/* ***************************************************************************** +String Base64 support +***************************************************************************** */ + +/** Writes data to String using base64 encoding. */ +SFUNC int fio_string_write_base64enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len, + uint8_t url_encoded); + +/** Writes decoded base64 data to String. */ +SFUNC int fio_string_write_base64dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len); + +/* ***************************************************************************** +String URL Encoding support +***************************************************************************** */ + +/** Writes data to String using URL encoding (a.k.a., percent encoding). */ +SFUNC int fio_string_write_url_enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len); + +/** Writes decoded URL data to String, decoding + to spaces. */ +SFUNC int fio_string_write_url_dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len); + +/** Writes decoded URL data to String, without decoding + to spaces. */ +SFUNC int fio_string_write_path_dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len); + +/* ***************************************************************************** +String HTML escaping support +***************************************************************************** */ + +/** Writes HTML escaped data to a String. */ +SFUNC int fio_string_write_html_escape(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len); + +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +SFUNC int fio_string_write_html_unescape(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *enscaped, + size_t enscaped_len); + +/* ***************************************************************************** +String File Reading support +***************************************************************************** */ + +/** + * Writes up to `limit` bytes from `fd` into `dest`, starting at `start_at`. + * + * If `limit` is 0 (or less than 0) data will be written until EOF. + * + * If `start_at` is negative, position will be calculated from the end of the + * file where `-1 == EOF`. + * + * Note: this will fail unless used on actual files (not sockets, not pipes). + * */ +SFUNC int fio_string_readfd(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int fd, + intptr_t start_at, + size_t limit); + +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. + * + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. + */ +SFUNC int fio_string_readfile(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *filename, + intptr_t start_at, + size_t limit); + +/** + * Writes up to `limit` bytes from `fd` into `dest`, starting at `start_at` and + * ending either at the first occurrence of `delim` or at EOF. + * + * If `limit` is 0 (or less than 0) as much data as may be required will be + * written. + * + * If `start_at` is negative, position will be calculated from the end of the + * file where `-1 == EOF`. + * + * Note: this will fail unless used on actual seekable files (not sockets, not + * pipes). + * */ +SFUNC int fio_string_getdelim_fd(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int fd, + intptr_t start_at, + char delim, + size_t limit); + +/** + * Opens the file `filename`, calls `fio_string_getdelim_fd` and closes the + * file. + */ +SFUNC int fio_string_getdelim_file(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *filename, + intptr_t start_at, + char delim, + size_t limit); + +/* ***************************************************************************** +Memory Helpers (for Authorship) +***************************************************************************** */ + +/* calculates a 16 bytes boundary aligned capacity for `new_len`. */ +FIO_IFUNC size_t fio_string_capa4len(size_t new_len); + +/** Default reallocation callback implementation using libc `realloc`. */ +#define FIO_STRING_SYS_REALLOC fio_string_sys_reallocate +/** Default reallocation callback implementation using the default allocator */ +#define FIO_STRING_REALLOC fio_string_default_reallocate +/** Default reallocation callback for memory that mustn't be freed. */ +#define FIO_STRING_ALLOC_COPY fio_string_default_allocate_copy +/** default allocator for the fio_keystr_s string data.. */ +#define FIO_STRING_ALLOC_KEY fio_string_default_key_alloc +/** Frees memory that was allocated with the default callbacks. */ +#define FIO_STRING_FREE fio_string_default_free +/** Frees memory that was allocated with the default callbacks. */ +#define FIO_STRING_FREE2 fio_string_default_free2 +/** Frees memory that was allocated for a key string. */ +#define FIO_STRING_FREE_KEY fio_string_default_free_key +/** Does nothing. */ +#define FIO_STRING_FREE_NOOP fio_string_default_free_noop +/** Does nothing. */ +#define FIO_STRING_FREE_NOOP2 fio_string_default_free_noop2 + +/** default reallocation callback implementation. */ +SFUNC int fio_string_default_reallocate(fio_str_info_s *dst, size_t len); +/** default reallocation callback for memory that mustn't be freed. */ +SFUNC int fio_string_default_allocate_copy(fio_str_info_s *dest, + size_t new_capa); +/** frees memory that was allocated with the default callbacks. */ +SFUNC void fio_string_default_free(void *); +/** frees memory that was allocated with the default callbacks. */ +SFUNC void fio_string_default_free2(fio_str_info_s str); +/** does nothing. */ +SFUNC void fio_string_default_free_noop(void *); +/** does nothing. */ +SFUNC void fio_string_default_free_noop2(fio_str_info_s str); + +/** default allocator for the fio_keystr_s string data.. */ +SFUNC void *fio_string_default_key_alloc(size_t len); +/** frees a fio_keystr_s memory that was allocated with the default callback. */ +SFUNC void fio_string_default_free_key(void *, size_t); + +/* ***************************************************************************** +UTF-8 Support +***************************************************************************** */ + +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC bool fio_string_utf8_valid(fio_str_info_s str); + +/** Returns the String's length in UTF-8 characters or 0 if invalid. */ +SFUNC size_t fio_string_utf8_len(fio_str_info_s str); + +/** Returns 0 if non-UTF-8 or returns 1-4 (UTF-8 if a valid char). */ +SFUNC size_t fio_string_utf8_valid_code_point(const void *u8c, size_t buf_len); + +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int fio_string_utf8_select(fio_str_info_s str, + intptr_t *pos, + size_t *len); + +/* ***************************************************************************** +Sorting / Comparison Helpers +***************************************************************************** */ + +/** + * Compares two `fio_buf_info_s`, returning 1 if data in a is bigger than b. + * + * Note: returns 0 if data in b is bigger than or equal(!). + */ +SFUNC int fio_string_is_greater_buf(fio_buf_info_s a, fio_buf_info_s b); + +/** + * Compares two strings, returning 1 if string a is bigger than string b. + * + * Note: returns 0 if string b is bigger than string a or if strings are equal. + */ +FIO_IFUNC int fio_string_is_greater(fio_str_info_s a, fio_str_info_s b); + +/* ***************************************************************************** +Binary String Type - Embedded Strings optimized for mutability and locality +***************************************************************************** */ + +/* for internal use only */ +typedef struct { + uint32_t len; + uint32_t capa; + uint32_t ref; +} fio___bstr_meta_s; + +/* for internal use only */ +typedef struct { + fio___bstr_meta_s meta; + char *ptr; +} fio___bstr_const_s; + +/** Reserves `len` for future `write` operations (used to minimize realloc). */ +FIO_IFUNC char *fio_bstr_reserve(char *bstr, size_t len); + +/** Copies a `fio_bstr` using "copy on write". */ +FIO_IFUNC char *fio_bstr_copy(char *bstr); +/** Frees a binary string allocated by a `fio_bstr` function. Returns NULL.*/ +FIO_IFUNC void fio_bstr_free(char *bstr); + +/** Returns information about the fio_bstr. */ +FIO_IFUNC fio_str_info_s fio_bstr_info(const char *bstr); +/** Returns information about the fio_bstr. */ +FIO_IFUNC fio_buf_info_s fio_bstr_buf(const char *bstr); +/** Gets the length of the fio_bstr. `bstr` MUST NOT be NULL. */ +FIO_IFUNC size_t fio_bstr_len(const char *bstr); +/** Sets the length of the fio_bstr. `bstr` MUST NOT be NULL. */ +FIO_IFUNC char *fio_bstr_len_set(char *bstr, size_t len); + +/** Compares to see if fio_bstr a is greater than fio_bstr b (for FIO_SORT). */ +FIO_SFUNC int fio_bstr_is_greater(const char *a, const char *b); +/** Compares to see if fio_bstr a is equal to another String. */ +FIO_SFUNC int fio_bstr_is_eq2info(const char *a_, fio_str_info_s b); +/** Compares to see if fio_bstr a is equal to another String. */ +FIO_SFUNC int fio_bstr_is_eq2buf(const char *a_, fio_buf_info_s b); + +/** Writes data to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write(char *bstr, + const void *restrict src, + size_t len); +/** Replaces data in a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_replace(char *bstr, + intptr_t start_pos, + size_t overwrite_len, + const void *src, + size_t len); +/** Writes data to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write2(char *bstr, const fio_string_write_s srcs[]); +/** Writes data to a fio_bstr, returning the address of the new fio_bstr. */ +#define fio_bstr_write2(bstr, ...) \ + fio_bstr_write2(bstr, (fio_string_write_s[]){__VA_ARGS__, {0}}) + +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_i(char *bstr, int64_t num); +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_u(char *bstr, uint64_t num); +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_hex(char *bstr, uint64_t num); +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_bin(char *bstr, uint64_t num); + +/** Writes escaped data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_escape(char *bstr, const void *src, size_t len); +/** Un-escapes and writes data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_unescape(char *bstr, + const void *src, + size_t len); + +/** Writes base64 encoded data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_base64enc(char *bstr, + const void *src, + size_t len, + uint8_t url_encoded); +/** Decodes base64 data and writes to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_base64dec(char *bstr, + const void *src, + size_t len); + +/** Writes data to String using URL encoding (a.k.a., percent encoding). */ +FIO_IFUNC char *fio_bstr_write_url_enc(char *bstr, + const void *data, + size_t len); +/** Writes decoded URL data to String. */ +FIO_IFUNC char *fio_bstr_write_url_dec(char *bstr, + const void *encoded, + size_t len); + +/** Writes HTML escaped data to a String. */ +FIO_IFUNC char *fio_bstr_write_html_escape(char *bstr, + const void *raw, + size_t len); +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +FIO_IFUNC char *fio_bstr_write_html_unescape(char *bstr, + const void *escaped, + size_t len); + +/** Writes to the String from a regular file `fd`. */ +FIO_IFUNC char *fio_bstr_readfd(char *bstr, + int fd, + intptr_t start_at, + intptr_t limit); +/** Writes to the String from a regular file named `filename`. */ +FIO_IFUNC char *fio_bstr_readfile(char *bstr, + const char *filename, + intptr_t start_at, + intptr_t limit); +/** Writes to the String from a regular file named `filename`. */ +FIO_IFUNC char *fio_bstr_getdelim_file(char *bstr, + const char *filename, + intptr_t start_at, + char delim, + size_t limit); +/** Writes to the String from a regular file `fd`. */ +FIO_IFUNC char *fio_bstr_getdelim_fd(char *bstr, + int fd, + intptr_t start_at, + char delim, + size_t limit); + +/** Writes a `fio_bstr` in `printf` style. */ +FIO_IFUNC FIO___PRINTF_STYLE(2, 0) char *fio_bstr_printf(char *bstr, + const char *format, + ...); + +/** default reallocation callback implementation - mostly for internal use. */ +SFUNC int fio_bstr_reallocate(fio_str_info_s *dest, size_t len); + +/* ***************************************************************************** +Key String Type - binary String container for Hash Maps and Arrays +***************************************************************************** */ + +/** a semi-opaque type used for the `fio_keystr` functions */ +typedef struct fio_keystr_s fio_keystr_s; + +/** returns the Key String. NOTE: Key Strings are NOT NUL TERMINATED! */ +FIO_IFUNC fio_buf_info_s fio_keystr_buf(fio_keystr_s *str); +/** returns the Key String. NOTE: Key Strings are NOT NUL TERMINATED! */ +FIO_IFUNC fio_str_info_s fio_keystr_info(fio_keystr_s *str); + +/** Returns a TEMPORARY `fio_keystr_s`. */ +FIO_IFUNC fio_keystr_s fio_keystr_tmp(const char *buf, uint32_t len); +/** Returns an initialized `fio_keystr_s` containing a copy of `str`. */ +FIO_SFUNC fio_keystr_s fio_keystr_init(fio_str_info_s str, + void *(*alloc_func)(size_t len)); +/** Destroys an initialized `fio_keystr_s`. */ +FIO_SFUNC void fio_keystr_destroy(fio_keystr_s *key, + void (*free_func)(void *, size_t)); +/** Compares two Key Strings. */ +FIO_IFUNC int fio_keystr_is_eq(fio_keystr_s a, fio_keystr_s b); +/** Compares a Key String to any String - used internally by the hash map. */ +FIO_IFUNC int fio_keystr_is_eq2(fio_keystr_s a_, fio_str_info_s b); +/** Compares a Key String to any String - used internally by the hash map. */ +FIO_IFUNC int fio_keystr_is_eq3(fio_keystr_s a_, fio_buf_info_s b); +/** Returns a good-enough `fio_keystr_s` risky hash. */ +FIO_IFUNC uint64_t fio_keystr_hash(fio_keystr_s a); + +#define FIO_KEYSTR_CONST ((size_t)-1LL) + +/* ***************************************************************************** + + + String Implementation + + IMPLEMENTATION - INLINED + + +***************************************************************************** */ + +/* ***************************************************************************** +String Authorship Helpers - (inlined) implementation +***************************************************************************** */ + +/* calculates a 16 bytes boundary aligned capacity for `new_len`. */ +FIO_IFUNC size_t fio_string_capa4len(size_t new_len) { + return sizeof(char) * + ((new_len + 15LL + (!(new_len & 15ULL))) & (~((size_t)15ULL))); +} + +/* + * performs `reallocate` if necessary, `capa` rounded up to 16 byte units. + * updates `len` if reallocation fails (or is unavailable). + */ +FIO_IFUNC int fio_string___write_validate_len(fio_str_info_s *restrict dest, + fio_string_realloc_fn reallocate, + size_t *restrict len) { + size_t l = len[0]; + if ((dest->capa > dest->len + l)) + return 0; + if (reallocate && l < (dest->capa >> 2) && + ((dest->capa >> 2) + (dest->capa) < 0x7FFFFFFFULL)) + l = (dest->capa >> 2); + l += dest->len; + if (l < 0x7FFFFFFFULL && reallocate && !reallocate(dest, l)) + return 0; + if (dest->capa > dest->len + 1) + len[0] = dest->capa - (dest->len + 1); + else + len[0] = 0; + return -1; +} + +/* fio_string_write */ +FIO_SFUNC int fio_string_write(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *restrict src, + size_t len) { + int r = 0; + if (!len) + return r; + r = fio_string___write_validate_len(dest, reallocate, &len); + if (FIO_LIKELY(len && src)) + FIO_MEMCPY(dest->buf + dest->len, src, len); + dest->len += len; + dest->buf[dest->len] = 0; + return r; +} + +/** + * Compares two strings, returning 1 if string a is bigger than string b. + * + * Note: returns 0 if string b is bigger than string a or if strings are equal. + */ +FIO_IFUNC int fio_string_is_greater(fio_str_info_s a, fio_str_info_s b) { + return fio_string_is_greater_buf(FIO_STR2BUF_INFO(a), FIO_STR2BUF_INFO(b)); +} + +/* ***************************************************************************** +Binary String Type - Embedded Strings +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio_bstr_s) + +#ifndef FIO___BSTR_META +#define FIO___BSTR_META(bstr) \ + FIO_PTR_MATH_SUB(fio___bstr_meta_s, bstr, sizeof(fio___bstr_meta_s)) +#endif + +/** Duplicates a `fio_bstr` using copy on write. */ +FIO_IFUNC char *fio_bstr_copy(char *bstr) { + if (!bstr) + return bstr; + fio___bstr_meta_s *meta = FIO___BSTR_META(bstr); + if (fio_atomic_add(&meta->ref, 1) > ((uint32_t)1UL << 31)) + goto copy_anyway; + return bstr; +copy_anyway: + bstr = fio_bstr_write(NULL, bstr, meta->len); + fio_bstr_free((char *)(meta + 1)); + return bstr; +} + +/** Frees a binary string allocated by a `fio_bstr` function. */ +FIO_IFUNC void fio_bstr_free(char *bstr) { + if (!bstr) + return; + fio___bstr_meta_s *meta = FIO___BSTR_META(bstr); + if (fio_atomic_sub(&meta->ref, 1)) + return; + FIO_LEAK_COUNTER_ON_FREE(fio_bstr_s); + FIO_MEM_FREE_(meta, (meta->capa + sizeof(*meta))); +} + +/** internal helper - sets the length of the fio_bstr. */ +FIO_IFUNC char *fio_bstr___len_set(char *bstr, size_t len) { + if (FIO_UNLIKELY(!bstr)) + return bstr; + // if (FIO_UNLIKELY(len >= 0xFFFFFFFFULL)) + // return bstr; + bstr[(FIO___BSTR_META(bstr)->len = (uint32_t)len)] = 0; + return bstr; +} + +/** Reserves `len` for future `write` operations (used to minimize realloc). */ +FIO_IFUNC char *fio_bstr_reserve(char *bstr, size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + if (i.len + len < i.capa) + return bstr; + fio_bstr_reallocate(&i, (i.len + len)); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Returns information about the fio_bstr. */ +FIO_IFUNC fio_str_info_s fio_bstr_info(const char *bstr) { + fio_str_info_s r = {0}; + r.buf = (char *)bstr; + /* please emit conditional mov and not an if branches */ + if (bstr) + r.len = FIO___BSTR_META(bstr)->len; + if (bstr) + r.capa = FIO___BSTR_META(bstr)->capa; + if (bstr && FIO___BSTR_META(bstr)->ref) + r.capa = 1; + return r; +} + +/** Returns information about the fio_bstr. */ +FIO_IFUNC fio_buf_info_s fio_bstr_buf(const char *bstr) { + fio___bstr_meta_s mem[1] = {{0}}; + fio___bstr_meta_s *meta_map[2] = {FIO___BSTR_META(bstr), mem}; + fio___bstr_meta_s *meta = meta_map[!bstr]; + return FIO_BUF_INFO2((char *)bstr, meta->len); +} + +/** Gets the length of the fio_bstr. `bstr` MUST NOT be NULL. */ +FIO_IFUNC size_t fio_bstr_len(const char *bstr) { + if (!bstr) + return 0; + fio___bstr_meta_s *meta = FIO___BSTR_META(bstr); + return meta->len; +} + +/** Sets the length of the fio_bstr. `bstr` MUST NOT be NULL. */ +FIO_IFUNC char *fio_bstr_len_set(char *bstr, size_t len) { + fio___bstr_meta_s m[2] = {0}; + fio___bstr_meta_s *meta = FIO___BSTR_META(bstr); + if (!bstr) + meta = m; + if (FIO_UNLIKELY(meta->ref || meta->capa <= len)) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_bstr_reallocate(&i, len); + bstr = i.buf; + } + return fio_bstr___len_set(bstr, len); +} + +/** Writes data to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write(char *bstr, + const void *restrict src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Replaces data in a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_replace(char *bstr, + intptr_t start_pos, + size_t overwrite_len, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_replace(&i, + fio_bstr_reallocate, + start_pos, + overwrite_len, + src, + len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes data to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write2 FIO_NOOP(char *bstr, + const fio_string_write_s srcs[]) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write2 FIO_NOOP(&i, fio_bstr_reallocate, srcs); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_i(char *bstr, int64_t num) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_i(&i, fio_bstr_reallocate, num); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_u(char *bstr, uint64_t num) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_u(&i, fio_bstr_reallocate, num); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_hex(char *bstr, uint64_t num) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_hex(&i, fio_bstr_reallocate, num); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes number to a fio_bstr, returning the address of the new fio_bstr. */ +FIO_IFUNC char *fio_bstr_write_bin(char *bstr, uint64_t num) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_bin(&i, fio_bstr_reallocate, num); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes escaped data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_escape(char *bstr, const void *src, size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_escape(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Un-escapes and writes data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_unescape(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_unescape(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes base64 encoded data to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_base64enc(char *bstr, + const void *src, + size_t len, + uint8_t url_encoded) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_base64enc(&i, fio_bstr_reallocate, src, len, url_encoded); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Decodes base64 data and writes to a fio_bstr, returning its new address. */ +FIO_IFUNC char *fio_bstr_write_base64dec(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_base64dec(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes data to String using URL encoding (a.k.a., percent encoding). */ +FIO_IFUNC char *fio_bstr_write_url_enc(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_url_enc(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes decoded URL data to String. */ +FIO_IFUNC char *fio_bstr_write_url_dec(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_url_dec(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes HTML escaped data to a String. */ +FIO_IFUNC char *fio_bstr_write_html_escape(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_html_escape(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +FIO_IFUNC char *fio_bstr_write_html_unescape(char *bstr, + const void *src, + size_t len) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_write_html_unescape(&i, fio_bstr_reallocate, src, len); + return fio_bstr___len_set(i.buf, i.len); +} + +FIO_IFUNC FIO___PRINTF_STYLE(2, 0) char *fio_bstr_printf(char *bstr, + const char *format, + ...) { + va_list argv; + va_start(argv, format); + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_vprintf(&i, fio_bstr_reallocate, format, argv); + va_end(argv); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes to the String from a regular file `fd`. */ +FIO_IFUNC char *fio_bstr_readfd(char *bstr, + int fd, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_readfd(&i, fio_bstr_reallocate, fd, start_at, limit); + return fio_bstr___len_set(i.buf, i.len); +} +/** Writes to the String from a regular file named `filename`. */ +FIO_IFUNC char *fio_bstr_readfile(char *bstr, + const char *filename, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_readfile(&i, fio_bstr_reallocate, filename, start_at, limit); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes to the String from a regular file named `filename`. */ +FIO_IFUNC char *fio_bstr_getdelim_file(char *bstr, + const char *filename, + intptr_t start_at, + char delim, + size_t limit) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_getdelim_file(&i, + fio_bstr_reallocate, + filename, + start_at, + delim, + limit); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Writes to the String from a regular file `fd`. */ +FIO_IFUNC char *fio_bstr_getdelim_fd(char *bstr, + int fd, + intptr_t start_at, + char delim, + size_t limit) { + fio_str_info_s i = fio_bstr_info(bstr); + fio_string_getdelim_fd(&i, fio_bstr_reallocate, fd, start_at, delim, limit); + return fio_bstr___len_set(i.buf, i.len); +} + +/** Compares to see if fio_bstr a is greater than fio_bstr b (for FIO_SORT). */ +FIO_SFUNC int fio_bstr_is_greater(const char *a, const char *b) { + return fio_string_is_greater_buf(fio_bstr_buf(a), fio_bstr_buf(b)); +} + +/** Compares to see if fio_bstr a is equal to another String. */ +FIO_SFUNC int fio_bstr_is_eq2info(const char *a_, fio_str_info_s b) { + fio_str_info_s a = fio_bstr_info(a_); + return FIO_STR_INFO_IS_EQ(a, b); +} +/** Compares to see if fio_bstr a is equal to another String. */ +FIO_SFUNC int fio_bstr_is_eq2buf(const char *a_, fio_buf_info_s b) { + fio_buf_info_s a = fio_bstr_buf(a_); + return FIO_BUF_INFO_IS_EQ(a, b); +} + +/* ***************************************************************************** +Key String Type - binary String container for Hash Maps and Arrays +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(fio_keystr_s) + +/* key string type implementation */ +struct fio_keystr_s { + uint8_t info; + uint8_t embd[3]; + uint32_t len; + const char *buf; +}; + +/** returns the Key String. */ +FIO_IFUNC fio_buf_info_s fio_keystr_buf(fio_keystr_s *str) { + fio_buf_info_s r; + if ((str->info + 1) > 1) { + r = (fio_buf_info_s){.len = str->info, .buf = (char *)str->embd}; + return r; + } + r = (fio_buf_info_s){.len = str->len, .buf = (char *)str->buf}; + return r; +} +/** returns the Key String. */ +FIO_IFUNC fio_str_info_s fio_keystr_info(fio_keystr_s *str) { + fio_str_info_s r; + if ((str->info + 1) > 1) { + r = (fio_str_info_s){.len = str->info, .buf = (char *)str->embd}; + return r; + } + r = (fio_str_info_s){.len = str->len, .buf = (char *)str->buf}; + return r; +} + +/** Returns a TEMPORARY `fio_keystr_s` to be used as a key for a hash map. */ +FIO_IFUNC fio_keystr_s fio_keystr_tmp(const char *buf, uint32_t len) { + fio_keystr_s r = {0}; + if (len + 1 < sizeof(r)) { /* always embed small strings in container! */ + r.info = (uint8_t)len; + FIO_MEMCPY(r.embd, buf, len); + return r; + } + r.info = 0xFF; + r.len = len; + r.buf = buf; + return r; +} + +/** Returns a copy of `fio_keystr_s`. */ +FIO_SFUNC fio_keystr_s fio_keystr_init(fio_str_info_s str, + void *(*alloc_func)(size_t len)) { + fio_keystr_s r = {0}; + if (!str.buf || !str.len || (str.len & (~(size_t)0xFFFFFFFF))) + return r; + if (str.len + 1 < sizeof(r)) { + r.info = (uint8_t)str.len; + FIO_MEMCPY(r.embd, str.buf, str.len); + return r; + } + if (str.capa == FIO_KEYSTR_CONST) { + r.info = 0xFF; + r.len = (uint32_t)str.len; + r.buf = str.buf; + return r; + } + char *buf; + r.len = (uint32_t)str.len; + r.buf = buf = (char *)alloc_func(str.len + 1); + if (!buf) + goto no_mem; + FIO_LEAK_COUNTER_ON_ALLOC(fio_keystr_s); + FIO_MEMCPY(buf, str.buf, str.len); + buf[str.len] = 0; + return r; +no_mem: + FIO_LOG_FATAL("fio_keystr_init allocation failed - results undefined!!!"); + r = fio_keystr_tmp(str.buf, (uint32_t)str.len); + return r; +} +/** Destroys a copy of `fio_keystr_s` - used internally by the hash map. */ +FIO_SFUNC void fio_keystr_destroy(fio_keystr_s *key, + void (*free_func)(void *, size_t)) { + if (key->info || !key->buf) + return; + FIO_LEAK_COUNTER_ON_FREE(fio_keystr_s); + free_func((void *)key->buf, key->len); +} + +/** Compares two Key Strings. */ +FIO_IFUNC int fio_keystr_is_eq(fio_keystr_s a_, fio_keystr_s b_) { + fio_buf_info_s a = fio_keystr_buf(&a_); + fio_buf_info_s b = fio_keystr_buf(&b_); + return FIO_BUF_INFO_IS_EQ(a, b); +} + +/** Compares a Key String to any String - used internally by the hash map. */ +FIO_IFUNC int fio_keystr_is_eq2(fio_keystr_s a_, fio_str_info_s b) { + fio_str_info_s a = fio_keystr_info(&a_); + return FIO_STR_INFO_IS_EQ(a, b); +} +/** Compares a Key String to any String - used internally by the hash map. */ +FIO_IFUNC int fio_keystr_is_eq3(fio_keystr_s a_, fio_buf_info_s b) { + fio_buf_info_s a = fio_keystr_buf(&a_); + return FIO_BUF_INFO_IS_EQ(a, b); +} + +/** Returns a good-enough `fio_keystr_s` risky hash. */ +FIO_IFUNC uint64_t fio_keystr_hash(fio_keystr_s a_) { + fio_buf_info_s a = fio_keystr_buf(&a_); + return fio_risky_hash(a.buf, a.len, (uint64_t)(uintptr_t)fio_string_write2); +} + +/* ***************************************************************************** +Extern-ed functions +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(fio_string_default_allocations) +FIO_LEAK_COUNTER_DEF(fio_string_default_key_allocations) +/* ***************************************************************************** +Allocation Helpers +***************************************************************************** */ + +SFUNC int fio_string_sys_reallocate(fio_str_info_s *dest, size_t len) { + len = fio_string_capa4len(len); + void *tmp = realloc(dest->buf, dest->capa); + if (!tmp) + return -1; + dest->capa = len; + dest->buf = (char *)tmp; + return 0; +} + +SFUNC int fio_string_default_reallocate(fio_str_info_s *dest, size_t len) { + len = fio_string_capa4len(len); + void *tmp = FIO_MEM_REALLOC_(dest->buf, dest->capa, len, dest->len); + if (!tmp) + return -1; + if (!dest->buf) + FIO_LEAK_COUNTER_ON_ALLOC(fio_string_default_allocations); + dest->capa = len; + dest->buf = (char *)tmp; + return 0; +} + +SFUNC int fio_string_default_allocate_copy(fio_str_info_s *dest, size_t len) { + len = fio_string_capa4len(len); + void *tmp = FIO_MEM_REALLOC_(NULL, 0, len, 0); + if (!tmp) + return -1; + FIO_LEAK_COUNTER_ON_ALLOC(fio_string_default_allocations); + dest->capa = len; + dest->buf = (char *)tmp; + if (dest->len) + FIO_MEMCPY(tmp, dest->buf, dest->len); + return 0; +} + +SFUNC void *fio_string_default_key_alloc(size_t len) { + return FIO_MEM_REALLOC_(NULL, 0, len, 0); +} + +SFUNC void fio_string_default_free(void *ptr) { + if (ptr) { + FIO_LEAK_COUNTER_ON_FREE(fio_string_default_allocations); + FIO_MEM_FREE_(ptr, 0); + } +} +SFUNC void fio_string_default_free2(fio_str_info_s str) { + if (str.buf) { + FIO_LEAK_COUNTER_ON_FREE(fio_string_default_allocations); + FIO_MEM_FREE_(str.buf, str.capa); + } +} + +/** frees a fio_keystr_s memory that was allocated with the default callback. */ +SFUNC void fio_string_default_free_key(void *buf, size_t capa) { + FIO_MEM_FREE_(buf, capa); + (void)capa; /* if unused */ +} + +SFUNC void fio_string_default_free_noop(void *str) { (void)str; } +SFUNC void fio_string_default_free_noop2(fio_str_info_s str) { (void)str; } + +/* ***************************************************************************** +Numeral Support +***************************************************************************** */ + +/* fio_string_write_i */ +SFUNC int fio_string_write_i(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int64_t i) { + int r = -1; + size_t len = 0; + len = fio_digits10(i); + if (fio_string___write_validate_len(dest, reallocate, &len)) + return r; /* no writing of partial numbers. */ + r = 0; + fio_ltoa10(dest->buf + dest->len, i, len); + dest->len += len; + return r; +} + +/* fio_string_write_u */ +SFUNC int fio_string_write_u(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i) { + int r = -1; + size_t len = fio_digits10u(i); + if (fio_string___write_validate_len(dest, reallocate, &len)) + return r; /* no writing of partial numbers. */ + r = 0; + fio_ltoa10u(dest->buf + dest->len, i, len); + dest->len += len; + return r; +} + +/* fio_string_write_hex */ +SFUNC int fio_string_write_hex(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i) { + int r = 0; + size_t len = fio_digits16u(i); + if (fio_string___write_validate_len(dest, reallocate, &len)) + return (r = -1); /* no writing of partial numbers. */ + fio_ltoa16u(dest->buf + dest->len, i, len); + dest->len += len; + return r; +} + +/* fio_string_write_bin */ +SFUNC int fio_string_write_bin(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + uint64_t i) { + int r = 0; + size_t len = fio_digits_bin(i); + if (fio_string___write_validate_len(dest, reallocate, &len)) + return (r = -1); /* no writing of partial numbers. */ + fio_ltoa_bin(dest->buf + dest->len, i, len); + dest->len += len; + return r; +} + +/* ***************************************************************************** +`printf` Style Support +***************************************************************************** */ + +/* Similar to fio_string_write, only using vprintf semantics. */ +SFUNC int FIO___PRINTF_STYLE(3, 0) + fio_string_vprintf(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *format, + va_list argv) { + int r = 0; + va_list argv_cpy; + va_copy(argv_cpy, argv); + int len_i = vsnprintf(NULL, 0, format, argv_cpy); + va_end(argv_cpy); + if (len_i <= 0) + return -1; + size_t len = (size_t)len_i; + r = fio_string___write_validate_len(dest, reallocate, &len); + if (FIO_UNLIKELY(dest->capa < dest->len + 2)) + return -1; + if (len) + vsnprintf(dest->buf + dest->len, len + 1, format, argv); + dest->len += len; + dest->buf[dest->len] = 0; + return r; +} + +/** Similar to fio_string_write, only using printf semantics. */ +SFUNC int FIO___PRINTF_STYLE(3, 4) + fio_string_printf(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *format, + ...) { + int r = 0; + va_list argv; + va_start(argv, format); + r = fio_string_vprintf(dest, reallocate, format, argv); + va_end(argv); + return r; +} + +/* ***************************************************************************** +UTF-8 Support +***************************************************************************** */ + +/** Returns 0 if non-UTF-8 or returns 1-4 (UTF-8 if a valid char). */ +SFUNC size_t fio_string_utf8_valid_code_point(const void *c, size_t buf_len) { + size_t l = fio_utf8_char_len((uint8_t *)c); + l &= 0U - (buf_len >= l); + return l; +} + +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC bool fio_string_utf8_valid(fio_str_info_s str) { + if (!str.len) + return 1; + char *const end = str.buf + str.len; + size_t tmp; + while ((tmp = fio_utf8_char_len(str.buf)) && ((str.buf += tmp) < end)) + ; + return str.buf == end; +} + +/** Returns the String's length in UTF-8 characters. */ +SFUNC size_t fio_string_utf8_len(fio_str_info_s str) { + if (!str.len) + return 0; + char *end = str.buf + str.len; + size_t utf8len = 0, tmp; + do { + tmp = fio_utf8_char_len(str.buf); + str.buf += tmp; + ++utf8len; + } while (tmp && str.buf < end); + utf8len &= 0U - (str.buf == end); + return utf8len; +} + +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int fio_string_utf8_select(fio_str_info_s str, + intptr_t *pos, + size_t *len) { + if (!pos || !len) + return -1; + const uint8_t *p = (uint8_t *)str.buf; + const uint8_t *const end = p + str.len; + size_t start, clen; + if (!str.len) + goto at_end; + if ((*pos) > 0) { + start = *pos; + do { + clen = fio_utf8_char_len(p); + p += clen; + --start; + } while (clen && start && p < end); + if (!clen || p > end) + goto error; + if (p == end) + goto at_end; + } else if (*pos < 0) { /* walk backwards */ + p += str.len; + start = 0 - *pos; + do { + const uint8_t *was = p; + --p; + while ((*p & 0xC0U) == 0x80U && p > (uint8_t *)str.buf) + --p; + if ((size_t)fio_utf8_char_len_unsafe(*p) != (size_t)(was - p)) + goto error; + } while (--start && p > (uint8_t *)str.buf); + } + *pos = p - (uint8_t *)str.buf; + + /* find end */ + start = *len; + clen = 1; + while (start && p < end && (clen = fio_utf8_char_len(p))) { + p += clen; + --start; + } + if (!clen || p > end) + goto error; + *len = p - ((uint8_t *)str.buf + (*pos)); + return 0; + +at_end: + *pos = str.len; + *len = 0; + return 0; +error: + *pos = -1; + *len = 0; + return -1; +} + +/* ***************************************************************************** +fio_string_is_greater +***************************************************************************** */ + +/** + * Compares two `fio_buf_info_s`, returning 1 if data in a is bigger than b. + * + * Note: returns 0 if data in b is bigger than or equal(!). + */ +SFUNC int fio_string_is_greater_buf(fio_buf_info_s a, fio_buf_info_s b) { + const int a_len_is_bigger = a.len > b.len; + size_t len = a_len_is_bigger ? b.len : a.len; /* shared length */ + if (a.buf == b.buf) + return a_len_is_bigger; + uint64_t ua[4] FIO_ALIGN(16) = {0}; + uint64_t ub[4] FIO_ALIGN(16) = {0}; + uint64_t flag = 0; + if (len < 32) + goto mini_cmp; + + len -= 32; + for (;;) { + for (size_t i = 0; i < 4; ++i) { + fio_memcpy8(ua + i, a.buf); + fio_memcpy8(ub + i, b.buf); + flag |= (ua[i] ^ ub[i]); + a.buf += 8; + b.buf += 8; + } + if (flag) + goto review_diff; + if (len > 31) { + len -= 32; + continue; + } + if (!len) + return a_len_is_bigger; + a.buf -= 32; + b.buf -= 32; + a.buf += len & 31; + b.buf += len & 31; + len = 0; + } + +review_diff: + if (ua[2] != ub[2]) { + ua[3] = ua[2]; + ub[3] = ub[2]; + } + if (ua[1] != ub[1]) { + ua[3] = ua[1]; + ub[3] = ub[1]; + } + if (ua[0] != ub[0]) { + ua[3] = ua[0]; + ub[3] = ub[0]; + } +review_diff8: + ua[3] = fio_lton64(ua[3]); /* comparison requires network byte order */ + ub[3] = fio_lton64(ub[3]); + return ua[3] > ub[3]; + +mini_cmp: + if (len > 7) { + len -= 8; + for (;;) { + fio_memcpy8(ua + 3, a.buf); + fio_memcpy8(ub + 3, b.buf); + if (ua[3] != ub[3]) + goto review_diff8; + if (len > 7) { + a.buf += 8; + b.buf += 8; + len -= 8; + continue; + } + if (!len) + return a_len_is_bigger; + a.buf += len & 7; + b.buf += len & 7; + len = 0; + } + } + while (len--) { + if (a.buf[0] != b.buf[0]) + return a.buf[0] > b.buf[0]; + ++a.buf; + ++b.buf; + } + return a_len_is_bigger; +} + +/* ***************************************************************************** +Insert / Write2 +***************************************************************************** */ + +/* fio_string_replace */ +SFUNC int fio_string_replace(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + intptr_t start_pos, + size_t overwrite_len, + const void *src, + size_t len) { + int r = 0; + if (start_pos < 0) { + start_pos = dest->len + start_pos + 1; + if (start_pos < 0) + start_pos = 0; + } + if (dest->len < (size_t)start_pos + overwrite_len + 1) { + if ((size_t)start_pos < dest->len) + dest->len = start_pos; + return fio_string_write(dest, reallocate, src, len); + } + + size_t move_start = start_pos + overwrite_len; + size_t move_len = dest->len - (start_pos + overwrite_len); + if (overwrite_len < len) { + /* adjust for possible memory expansion */ + const size_t extra = len - overwrite_len; + if (dest->capa < dest->len + extra + 1) { + r = -1; /* in case reallocate is NULL */ + if (!reallocate || + FIO_UNLIKELY( + (r = reallocate(dest, fio_string_capa4len(dest->len + extra))))) { + move_len -= (dest->len + extra + 1) - dest->capa; + if (dest->capa < start_pos + len + 1) { + move_len = 0; + len = dest->capa - start_pos - 1; + } + } + } + } + if (move_len) + FIO_MEMMOVE(dest->buf + start_pos + len, dest->buf + move_start, move_len); + if (len) + FIO_MEMCPY(dest->buf + start_pos, src, len); + dest->len = start_pos + len + move_len; + dest->buf[dest->len] = 0; + return r; +} + +/* IDE marker */ +void fio_string_write2____(void); +/* the fio_string_write2 is a printf alternative. */ +SFUNC int fio_string_write2 FIO_NOOP(fio_str_info_s *restrict dest, + fio_string_realloc_fn reallocate, + const fio_string_write_s srcs[]) { + int r = 0; + const fio_string_write_s *pos = srcs; + size_t len = 0; + + while (pos->klass) { + switch (pos->klass) { /* use more memory rather then calculate twice. */ + case 2: /* number */ len += fio_digits10(pos->info.i); break; + case 3: /* unsigned */ len += fio_digits10u(pos->info.u); break; + case 4: /* hex */ len += fio_digits16u(pos->info.u); break; + case 5: /* binary */ len += fio_digits_bin(pos->info.u); break; + case 6: /* float */ len += 18; break; + default: len += pos->info.str.len; + } + ++pos; + } + if (!len) + return r; + pos = srcs; + if (fio_string___write_validate_len(dest, reallocate, &len)) + goto truncate; + while (pos->klass) { + switch (pos->klass) { + case 2: fio_string_write_i(dest, NULL, pos->info.i); break; /* number */ + case 3: fio_string_write_u(dest, NULL, pos->info.u); break; /* unsigned */ + case 4: fio_string_write_hex(dest, NULL, pos->info.u); break; /* hex */ + case 5: fio_string_write_bin(dest, NULL, pos->info.u); break; /* binary */ + case 6: /* float */ + dest->len += snprintf(dest->buf + dest->len, 19, "%.15g", pos->info.f); + break; + default: + FIO_MEMCPY(&dest->buf[dest->len], pos->info.str.buf, pos->info.str.len); + dest->len += pos->info.str.len; + } + ++pos; + } +finish: + dest->buf[dest->len] = 0; + return r; +truncate: + r = -1; + while (pos->klass) { + switch (pos->klass) { + case 2: + if (fio_string_write_i(dest, NULL, pos->info.i)) + goto finish; + break; /* number */ + case 3: + if (fio_string_write_u(dest, NULL, pos->info.u)) + goto finish; + break; /* unsigned */ + case 4: + if (fio_string_write_hex(dest, NULL, pos->info.u)) + goto finish; + break; /* hex */ + case 5: + if (fio_string_write_bin(dest, NULL, pos->info.u)) + goto finish; + break; /* binary */ + case 6: /* float */ + len = snprintf(dest->buf + dest->len, 19, "%.15g", pos->info.f); + if (dest->capa < dest->len + len + 2) + goto finish; + dest->len += len; + break; + default: + if (fio_string_write(dest, NULL, pos->info.str.buf, pos->info.str.len)) + goto finish; + } + ++pos; + } + goto finish; +} + +/* ***************************************************************************** +Escaping / Un-Escaping Primitives (not for encoding) +***************************************************************************** */ + +typedef struct { + fio_str_info_s *restrict dest; + fio_string_realloc_fn reallocate; + const void *restrict src; + const size_t len; + /* moves to the next character (or character sequence) to alter. */ + const uint8_t *(*next)(const uint8_t *restrict s, const uint8_t *restrict e); + /* + * `dest` will be NULL when calculating length to be written. + * + * `*s` is the source data. + * + * `e` is the end-of-bounds position (src + len). + * + * Returns the number of characters that would have been written. + * + * Note: must update `s` to point to the next character after the altered + * sequence. + */ + size_t (*diff)(uint8_t *restrict dest, + const uint8_t *restrict *restrict s, + const uint8_t *restrict e); + /* + * Writes (un)escaped data to `dest`. + * + * Behaves the same as `diff` only writes data to `dest`. + * + * `dest` is the same number of bytes as reported by `diff` (or more). + */ + size_t (*write)(uint8_t *restrict dest, + const uint8_t *restrict *restrict s, + const uint8_t *restrict e); + /* If `len` of `src` is less then `skip_diff_len`, skips the test. */ + uint32_t skip_diff_len; + /* If set, will not allow a partial write when memory allocation fails. */ + uint32_t refuse_partial; +} fio___string_altering_args_s; + +/** + * Writes an escaped data into the string after un-escaping the data. + */ +FIO_IFUNC int fio___string_altering_cycle( + const fio___string_altering_args_s args) { + int r = 0; + if (((long long)args.len < 1) | !args.src | !args.dest) + return r; + const uint8_t *s = (const uint8_t *)args.src; + const uint8_t *e = s + args.len; + const uint8_t *p = s; + fio_str_info_s d = *args.dest; + size_t first_stop = 0; + size_t updater = 0; + /* we need to allocate memory - limit to result's length */ + if (d.len + args.len >= d.capa) { + updater = (args.len > args.skip_diff_len); + size_t written_length = args.len; + if (updater) { /* skip memory reduction for small strings */ + written_length = 0; + p = s; + for (;;) { + const uint8_t *p2 = args.next(p, e); + if (!p2) + break; + written_length += p2 - p; + p = p2; + first_stop |= (0ULL - updater) & ((p - s) + 1); + updater = 0; + written_length += args.diff(NULL, &p, e); + if (p + 1 > e) + break; + } + } + written_length += e - p; + /* allocate extra required space. */ + FIO_ASSERT_DEBUG(written_length > 0, "string (un)escape reduced too much"); + if (d.len + written_length >= d.capa && + fio_string___write_validate_len(&d, args.reallocate, &written_length)) { + r = -1; + if (args.refuse_partial) + goto finish; + e = (const uint8_t *)d.capa - (d.len + 1); + } + } + + /* copy unescaped head of string (if it's worth our time), saves one memchr */ + if (((!first_stop) & updater) | (first_stop > 16)) { + if (!first_stop) + first_stop = (e - s) + 1; + --first_stop; + FIO_MEMMOVE(d.buf + d.len, s, first_stop); + d.len += first_stop; + s += first_stop; + } + p = s; + + /* start copying and un-escaping as needed */ + while (p < e) { + const uint8_t *p2 = args.next(p, e); + if (!p2) + break; + if (p2 - p) { + updater = p2 - p; + FIO_MEMMOVE(d.buf + d.len, p, updater); + d.len += updater; + } + p = p2; + d.len += args.write((uint8_t *)d.buf + d.len, &p, e); + } + if (p < e) { + updater = e - p; + FIO_MEMCPY(d.buf + d.len, p, updater); + d.len += updater; + } + +finish: + d.buf[d.len] = 0; + *args.dest = d; + return r; +} + +/* ***************************************************************************** +String C / JSON escaping +***************************************************************************** */ + +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +SFUNC int fio_string_write_escape(fio_str_info_s *restrict dest, + fio_string_realloc_fn reallocate, + const void *restrict src, + size_t len) { + /* Escaping map, test if bit 64 is set or not. Created using Ruby Script: + map = []; 256.times { |i| map << ((i > 126 || i < 35) ? 48.chr : 64.chr) }; + map[' '.ord] = 64.chr; map['!'.ord] = 64.chr; + ["\b","\f","\n","\r","\t",'\\','"'].each {|c| map[c.ord] = 49.chr }; + str = map.join(''); puts "static const uint8_t escape_map[256]= " + + "\"#{str.slice(0,64)}\"" + + "\"#{str.slice(64,64)}\"" + + "\"#{str.slice(128,64)}\"" + + "\"#{str.slice(192,64)}\";" + */ + static const uint8_t escape_map[256] = + "00000000111011000000000000000000@@1@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@1@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@0" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000"; + int r = 0; + if ((!len | !src | !dest)) + return r; + size_t extra_space = 0; + size_t first_stop = 0; + size_t updater = 1; + const uint8_t *s = (const uint8_t *)src; + const uint8_t *e = s + len; + const uint8_t *p = s; + + /* test memory length requirements – unlikely to be avoided (len * 5) */ + for (; (p < e); ++p) { + if ((escape_map[*p] & 64)) /* hope for compiler magic */ + continue; + size_t valid_utf8_len = fio_utf8_char_len(p); + if (valid_utf8_len > 1) { + p += valid_utf8_len - 1; + continue; + } + first_stop |= (0ULL - updater) & (p - s); + updater = 0; + /* count extra bytes */ + ++extra_space; /* the '\' character followed by escape sequence */ + /* constant-time "if" (bit mask) – known escape or \xFF / \uFFFF escaping */ + extra_space += (escape_map[*p] - 1) & (3 + ((*p < 127) << 1)); + } + + /* reserve space and copy any valid first_stop */ + /* the + 3 adds room for the likely use case of JSON: "\",\"" */ + if ((dest->capa < dest->len + extra_space + len + 1) && + (!reallocate || + reallocate(dest, + fio_string_capa4len(dest->len + extra_space + len + 3)))) { + r = -1; + len = dest->capa - (dest->len + 6); + if (dest->capa < len + 6) + return r; + } + + /* copy unescaped head of string (if it's worth our time) */ + if (((!first_stop) & updater & (escape_map[*s] == 64)) || first_stop > 16) { + if (!first_stop) + first_stop = len; + FIO_MEMMOVE(dest->buf + dest->len, s, first_stop); + dest->len += first_stop; + s += first_stop; + } + p = s; + + /* start copying and escaping as needed */ + for (;;) { + if ((escape_map[*p] & 64)) { + for (s = p; (s < e) && (escape_map[*s] & 64); ++s) + ; /* hope for compiler magic */ + updater = s - p; + FIO_MEMMOVE(dest->buf + dest->len, p, updater); + dest->len += updater; + p = s; + } + if (p >= e) + break; + size_t valid_utf8_len = fio_utf8_char_len(p); + size_t limit = e - p; + if (valid_utf8_len > limit) + valid_utf8_len = limit; + switch (valid_utf8_len) { + case 4: dest->buf[dest->len++] = *p++; /* fall through */ + case 3: dest->buf[dest->len++] = *p++; /* fall through */ + case 2: + dest->buf[dest->len++] = *p++; /* fall through */ + dest->buf[dest->len++] = *p++; /* fall through */ + continue; + default: break; + } + // FIO_ASSERT(valid_utf8_len < 2, "valid_utf8_len error!"); + dest->buf[dest->len++] = '\\'; + uint8_t ec = *p++; + switch (ec) { + case '\b': dest->buf[dest->len++] = 'b'; continue; + case '\f': dest->buf[dest->len++] = 'f'; continue; + case '\n': dest->buf[dest->len++] = 'n'; continue; + case '\r': dest->buf[dest->len++] = 'r'; continue; + case '\t': dest->buf[dest->len++] = 't'; continue; + case '\\': dest->buf[dest->len++] = '\\'; continue; + case ' ': dest->buf[dest->len++] = ' '; continue; + case '"': dest->buf[dest->len++] = '"'; continue; + default: + /* pass through character */ + first_stop = (ec > 34); + dest->buf[dest->len - first_stop] = ec; + /* escaping all control characters and non-UTF-8 characters */ + first_stop = (ec < 127); + const char in_hex[2] = {(char)fio_i2c(ec >> 4), (char)fio_i2c(ec & 15)}; + dest->buf[dest->len] = 'u'; /* UTF-8 encoding (remains valid) */ + dest->buf[dest->len += first_stop] = '0'; + dest->buf[dest->len += first_stop] = '0'; + dest->buf[dest->len += first_stop] = in_hex[0]; + dest->buf[dest->len += first_stop] = in_hex[1]; + dest->len += first_stop; + } + } + dest->buf[dest->len] = 0; + return r; +} + +FIO_SFUNC const uint8_t *fio___string_write_unescape_next( + const uint8_t *restrict s, + const uint8_t *restrict e) { + if (*s == '\\') + return s; + return (const uint8_t *)FIO_MEMCHR(s, '\\', e - s); +} + +FIO_SFUNC size_t +fio___string_write_unescape_diff(uint8_t *restrict dest, + const uint8_t *restrict *restrict ps, + const uint8_t *restrict e) { + size_t r = 1; + unsigned step = 1; + const uint8_t *s = *ps; + ++s; + unsigned peek = ((*s == 'x') & (e - s > 2)); + peek &= (unsigned)(fio_c2i(s[peek]) < 16) & (fio_c2i(s[peek + peek]) < 16); + step |= (peek << 1); + // peek &= (fio_c2i(s[peek]) > 7); + r += peek; /* assumes \xFF is unescaped as UTF-8, up to 2 bytes */ + + peek = ((*s == 'u') & (e - s > 4)); + peek &= (unsigned)(fio_c2i(s[peek]) < 16) & (fio_c2i(s[peek + peek]) < 16) & + (fio_c2i(s[peek + peek + peek]) < 16) & + (fio_c2i(s[peek + peek + peek + peek]) < 16); + r |= (peek << 1); /* assumes \uFFFF in maximum length, ignores UTF-16 pairs */ + step |= (peek << 2); + + s += step; + *ps = s; + return r; + (void)dest; +} +FIO_IFUNC size_t +fio___string_write_unescape_write(uint8_t *restrict dest, + const uint8_t *restrict *restrict ps, + const uint8_t *restrict e) { + unsigned r = 1; + const uint8_t *restrict s = *ps; + s += ((s + 1) < e); /* skip '\\' byte */ + switch (*s) { + case 'b': + *dest = '\b'; + ++s; + break; /* from switch */ + case 'f': + *dest = '\f'; + ++s; + break; /* from switch */ + case 'n': + *dest = '\n'; + ++s; + break; /* from switch */ + case 'r': + *dest = '\r'; + ++s; + break; /* from switch */ + case 't': + *dest = '\t'; + ++s; + break; /* from switch */ + case 'u': { + /* test UTF-8 notation */ + if ((s + 4 < e) && ((unsigned)(fio_c2i(s[1]) < 16) & (fio_c2i(s[2]) < 16) & + (fio_c2i(s[3]) < 16) & (fio_c2i(s[4]) < 16))) { + uint32_t u = (((fio_c2i(s[1]) << 4) | fio_c2i(s[2])) << 8) | + ((fio_c2i(s[3]) << 4) | fio_c2i(s[4])); + if ((s + 10 < e) && + (((fio_c2i(s[1]) << 4) | fio_c2i(s[2])) == 0xD8U && s[5] == '\\' && + s[6] == 'u' && + ((unsigned)(fio_c2i(s[7]) < 16) & (fio_c2i(s[8]) < 16) & + (fio_c2i(s[9]) < 16) & (fio_c2i(s[10]) < 16)))) { + /* surrogate-pair (high/low code points) */ + u = (u & 0x03FF) << 10; + u |= (((((fio_c2i(s[7]) << 4) | fio_c2i(s[8])) << 8) | + ((fio_c2i(s[9]) << 4) | fio_c2i(s[10]))) & + 0x03FF); + u += 0x10000; + s += 6; + } + r = fio_utf8_write(dest, u); + s += 5; + break; /* from switch */ + } else + goto invalid_escape; + } + case 'x': { /* test for hex notation */ + if (fio_c2i(s[1]) < 16 && fio_c2i(s[2]) < 16) { + *dest = (fio_c2i(s[1]) << 4) | fio_c2i(s[2]); + s += 3; + break; /* from switch */ + } else + goto invalid_escape; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { /* test for octal notation */ + if (s[0] >= '0' && s[0] <= '7' && s[1] >= '0' && s[1] <= '7') { + *dest = ((s[0] - '0') << 3) | (s[1] - '0'); + s += 2; + break; /* from switch */ + } else + goto invalid_escape; + } + case '"': + case '\\': + case '/': + /* fall through */ + default: + invalid_escape: + *dest = *s++; + } + *ps = s; + return r; +} +FIO_IFUNC int fio_string_write_unescape(fio_str_info_s *restrict dest, + fio_string_realloc_fn alloc, + const void *src, + size_t len) { + return fio___string_altering_cycle((fio___string_altering_args_s){ + .dest = dest, + .reallocate = alloc, + .src = src, + .len = len, + .next = fio___string_write_unescape_next, + .diff = fio___string_write_unescape_diff, + .write = fio___string_write_unescape_write, + .skip_diff_len = 127, + .refuse_partial = 1, + }); +} + +/* ***************************************************************************** +String Base32 support +***************************************************************************** */ + +/** Writes data to String using base64 encoding. */ +SFUNC int fio_string_write_base32enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *raw, + size_t raw_len) { + static const uint8_t base32ecncode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + int r = 0; + size_t expected = ((raw_len * 8) / 5) + 1; + if (fio_string___write_validate_len(dest, reallocate, &expected)) { + return (r = -1); /* no partial encoding. */ + } + expected = dest->len; + size_t bits = 0, store = 0; + for (size_t i = 0; i < raw_len; ++i) { + store = (store << 8) | (size_t)((uint8_t *)raw)[i]; + bits += 8; + if (bits < 25) + continue; + while (bits > 4) { + uint8_t val = base32ecncode[(31U & (store >> (bits - 5)))]; + dest->buf[dest->len++] = val; + bits -= 5; + } + } + while (bits > 4) { + uint8_t val = base32ecncode[(31U & (store >> (bits - 5)))]; + dest->buf[dest->len++] = val; + bits -= 5; + } + if (bits) { + // dest->buf[dest->len++] = base32ecncode[store & ((1U << bits) - 1)]; + dest->buf[dest->len++] = base32ecncode[31U & (store << (5 - bits))]; + dest->buf[dest->len] = '='; + dest->len += !!((dest->len - expected) % 5); + } + dest->buf[dest->len] = 0; + return r; +} + +/** Writes decoded base64 data to String. */ +SFUNC int fio_string_write_base32dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len) { + /* ABCDEF6HIJK3MN6PQRSTUV6XYZ234567 + a = []; + 256.times { a << 255 } + b = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".bytes + b.length.times {|i| a[b[i]] = i } + b = "abcdefghijklmnopqrstuvwxyz234567".bytes + b.length.times {|i| a[b[i]] = i } + b = " \r\n\t\b".bytes + b.length.times {|i| a[b[i]] = 32 } + a.map! {|n| n.to_s 10 } + puts "const static uint8_t base32decode[256] = { #{a.join(", ") } }; " +*/ + static const uint8_t base32decode[256] = { + 255, 255, 255, 255, 255, 255, 255, 255, 32, 32, 32, 255, 255, 32, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 32, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + int r = 0; + size_t expected = ((encoded_len * 5) / 8) + 1; + if (fio_string___write_validate_len(dest, reallocate, &expected)) { + return (r = -1); /* no partial encoding. */ + } + size_t val = 0; + size_t bits = 0; + uint8_t *s = (uint8_t *)dest->buf + dest->len; + for (size_t i = 0; i < encoded_len; ++i) { + size_t dec = (size_t)base32decode[((uint8_t *)encoded)[i]]; + if (dec == 32) + continue; + if (dec > 31) + break; + bits += 5; + val = (val << 5) | dec; + if (bits < 40) + continue; + do { + *s++ = (0xFF & (val >> (bits - 8))); + bits -= 8; + } while (bits > 7); + } + while (bits > 7) { + *s++ = (0xFF & (val >> (bits - 8))); + bits -= 8; + } + if (bits) { /* letfover bits considered padding */ + // *s++ = 0xFF & (val << (8 - bits)); + } + dest->len = (size_t)(s - (uint8_t *)dest->buf); + dest->buf[dest->len] = 0; + return r; +} + +/* ***************************************************************************** +String Base64 support +***************************************************************************** */ + +/** Writes data to String using Base64 encoding. */ +SFUNC int fio_string_write_base64enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *data, + size_t len, + uint8_t url_encoded) { + int r = 0; + if (!dest || !data || !len) + return r; + static const char *encmap[2] = { + /* Regular, URL encoding*/ + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=", + }; + + /* the base64 encoding array */ + const char *encoding = encmap[!!url_encoded]; + + /* base64 length and padding information */ + size_t groups = len / 3; + const size_t mod = len - (groups * 3); + size_t target_size = (groups + (mod != 0)) * 4; + + if (fio_string___write_validate_len(dest, reallocate, &target_size)) { + return (r = -1); /* no partial encoding. */ + } + char *writer = dest->buf + dest->len; + const unsigned char *reader = (const unsigned char *)data; + dest->len += target_size; + /* write encoded data */ + while (groups) { + --groups; + const unsigned char tmp1 = *(reader++); + const unsigned char tmp2 = *(reader++); + const unsigned char tmp3 = *(reader++); + + *(writer++) = encoding[(tmp1 >> 2) & 63]; + *(writer++) = encoding[(((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15))]; + *(writer++) = encoding[((tmp2 & 15) << 2) | ((tmp3 >> 6) & 3)]; + *(writer++) = encoding[tmp3 & 63]; + } + + /* write padding / ending */ + switch (mod) { + case 2: { + const unsigned char tmp1 = *(reader++); + const unsigned char tmp2 = *(reader++); + + *(writer++) = encoding[(tmp1 >> 2) & 63]; + *(writer++) = encoding[((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15)]; + *(writer++) = encoding[((tmp2 & 15) << 2)]; + *(writer++) = '='; + } break; + case 1: { + const unsigned char tmp1 = *(reader++); + + *(writer++) = encoding[(tmp1 >> 2) & 63]; + *(writer++) = encoding[(tmp1 & 3) << 4]; + *(writer++) = '='; + *(writer++) = '='; + } break; + } + dest->buf[dest->len] = 0; + return r; +} + +/** Writes decoded base64 data to String. */ +SFUNC int fio_string_write_base64dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded_, + size_t len) { + /* Base64 decoding array. Generation script (Ruby): +s = ["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="] +valid = []; (0..255).each {|i| valid[i] = 0 }; +decoder = []; (0..127).each {|i| decoder[i] = 0 }; +s.each {|d| d.bytes.each_with_index { |b, i| decoder[b] = i; valid[b] = 1 } }; +p valid; p decoder; nil + */ + static const uint8_t base64_valid[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + static const uint8_t base64_decodes[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 62, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, + }; + int r = 0; + if (!dest || !encoded_ || !len) + return r; + const uint8_t *encoded = (const uint8_t *)encoded_; + /* skip unknown data at end */ + while (len && !base64_valid[encoded[len - 1]]) { + len--; + } + if (!len) + return (r = -1); + + /* reserve memory space */ + { + size_t required_len = (((len >> 2) * 3) + 3); + if (fio_string___write_validate_len(dest, reallocate, &required_len)) { + return (r = -1); /* no partial decoding. */ + }; + } + + /* decoded and count actual length */ + size_t pos = 0; + uint8_t b64wrd[4]; + const uint8_t *stop = encoded + len; + uint8_t *writer = (uint8_t *)dest->buf + dest->len; + for (;;) { + if (base64_valid[encoded[0]]) + b64wrd[pos++] = base64_decodes[encoded[0]]; + else if (!isspace(encoded[0])) + break; + ++encoded; + if (pos == 4) { + writer[0] = (b64wrd[0] << 2) | (b64wrd[1] >> 4); + writer[1] = (b64wrd[1] << 4) | (b64wrd[2] >> 2); + writer[2] = (b64wrd[2] << 6) | b64wrd[3]; + pos = 0; + writer += 3; + } + if (encoded == stop) + break; + } + switch (pos) { + case 1: b64wrd[1] = 0; /* fall through */ + case 2: b64wrd[2] = 0; /* fall through */ + case 3: b64wrd[3] = 0; /* fall through */ + case 4: + writer[0] = (b64wrd[0] << 2) | (b64wrd[1] >> 4); + writer[1] = (b64wrd[1] << 4) | (b64wrd[2] >> 2); + writer[2] = (b64wrd[2] << 6) | b64wrd[3]; + writer += 3; + } + writer -= (encoded[-1] == '=') + (encoded[-2] == '='); + if (writer < ((uint8_t *)dest->buf + dest->len)) + writer = ((uint8_t *)dest->buf + dest->len); + dest->len = (size_t)(writer - (uint8_t *)dest->buf); + dest->buf[dest->len] = 0; + return r; +} + +/* ***************************************************************************** +String URL Encoding support +***************************************************************************** */ + +/** Writes data to String using URL encoding (a.k.a., percent encoding). */ +SFUNC int fio_string_write_url_enc(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *data, + size_t data_len) { + static const uint8_t url_enc_map[256] = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; + int r = 0; + /* reserve memory space */ + { + size_t required_len = data_len; + for (size_t i = 0; i < data_len; ++i) { + required_len += url_enc_map[((uint8_t *)data)[i]]; + } + if (fio_string___write_validate_len(dest, reallocate, &required_len)) { + return (r = -1); /* no partial encoding. */ + }; + } + for (size_t i = 0; i < data_len; ++i) { + if (!url_enc_map[((uint8_t *)data)[i]]) { + dest->buf[dest->len++] = ((uint8_t *)data)[i]; + continue; + } + dest->buf[dest->len++] = '%'; + dest->buf[dest->len++] = fio_i2c(((uint8_t *)data)[i] >> 4); + dest->buf[dest->len++] = fio_i2c(((uint8_t *)data)[i] & 15); + } + dest->buf[dest->len] = 0; + return r; +} + +/** Writes decoded URL data to String. */ +FIO_IFUNC int fio_string_write_url_dec_internal( + fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len, + _Bool plus_is_included) { + int r = 0; + if (!dest || !encoded || !encoded_len) + return r; + uint8_t *pr = (uint8_t *)encoded; + uint8_t *last = pr; + uint8_t *end = pr + encoded_len; + if (dest->len + encoded_len >= dest->capa) { /* reserve only what we need */ + size_t act_len = 0; + while (end > pr && (pr = (uint8_t *)FIO_MEMCHR(pr, '%', end - pr))) { + act_len += pr - last; + last = pr + 1; + if (end - last > 1 && fio_c2i(last[0]) < 16 && fio_c2i(last[1]) < 16) + last += 2; + else if (end - last > 4 && (last[0] | 32) == 'u' && + fio_c2i(last[1]) < 16 && fio_c2i(last[2]) < 16 && + fio_c2i(last[3]) < 16 && fio_c2i(last[4]) < 16) { + last += 5; + act_len += 3; /* uXXXX length maxes out at 4 ... I think */ + } + pr = last; + } + act_len += end - last; + if (fio_string___write_validate_len(dest, reallocate, &act_len)) { + return (r = -1); /* no partial decoding. */ + }; + } + /* copy and un-encode data */ + pr = (uint8_t *)encoded; + last = pr; + end = pr + encoded_len; + while (end > pr && (pr = (uint8_t *)FIO_MEMCHR(pr, '%', end - pr))) { + const size_t slice_len = pr - last; + if (slice_len) { + FIO_MEMCPY(dest->buf + dest->len, last, slice_len); + /* test for '+' in the slice that has no % characters */ + if (plus_is_included) { + uint8_t *start_plus = (uint8_t *)dest->buf + dest->len; + uint8_t *end_plus = start_plus + slice_len; + while ( + start_plus && start_plus < end_plus && + (start_plus = + (uint8_t *)FIO_MEMCHR(start_plus, '+', end_plus - start_plus))) + *(start_plus++) = ' '; + } + } + dest->len += slice_len; + last = pr + 1; + if (end - last > 1 && fio_c2i(last[0]) < 16 && fio_c2i(last[1]) < 16) { + dest->buf[dest->len++] = (fio_c2i(last[0]) << 4) | fio_c2i(last[1]); + last += 2; + } else if (end - last > 4 && (last[0] | 32) == 'u' && + fio_c2i(last[1]) < 16 && fio_c2i(last[2]) < 16 && + fio_c2i(last[3]) < 16 && fio_c2i(last[4]) < 16) { + uint32_t u = (((fio_c2i(last[1]) << 4) | fio_c2i(last[2])) << 8) | + ((fio_c2i(last[3]) << 4) | fio_c2i(last[4])); + if (end - last > 9 && + ((fio_c2i(last[1]) << 4) | fio_c2i(last[2])) == 0xD8U && + last[5] == '%' && last[6] == 'u' && fio_c2i(last[7]) < 16 && + fio_c2i(last[8]) < 16 && fio_c2i(last[9]) < 16 && + fio_c2i(last[10]) < 16) { + /* surrogate-pair (high/low code points) */ + u = (u & 0x03FF) << 10; + u |= (((((fio_c2i(last[7]) << 4) | fio_c2i(last[8])) << 8) | + ((fio_c2i(last[9]) << 4) | fio_c2i(last[10]))) & + 0x03FF); + u += 0x10000; + last += 6; + } + dest->len += fio_utf8_write((uint8_t *)dest->buf + dest->len, u); + last += 5; + } else { + dest->buf[dest->len++] = '%'; + } + pr = last; + } + if (end > last) { + const size_t slice_len = end - last; + FIO_MEMCPY(dest->buf + dest->len, last, slice_len); + /* test for '+' in the slice that has no % characters */ + if (plus_is_included) { + uint8_t *start_plus = (uint8_t *)dest->buf + dest->len; + uint8_t *end_plus = start_plus + slice_len; + while ( + start_plus && start_plus < end_plus && + (start_plus = + (uint8_t *)FIO_MEMCHR(start_plus, '+', end_plus - start_plus))) + *(start_plus++) = ' '; + } + dest->len += slice_len; + } + dest->buf[dest->len] = 0; + return r; +} + +/** Writes decoded URL data to String. */ +SFUNC int fio_string_write_url_dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len) { + return fio_string_write_url_dec_internal(dest, + reallocate, + encoded, + encoded_len, + 1); +} + +/** Writes decoded URL data to String. */ +SFUNC int fio_string_write_path_dec(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t encoded_len) { + return fio_string_write_url_dec_internal(dest, + reallocate, + encoded, + encoded_len, + 0); +} + +/* ***************************************************************************** +String HTML escaping support +***************************************************************************** */ + +/** Writes HTML escaped data to a String. */ +SFUNC int fio_string_write_html_escape(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *data, + size_t data_len) { + /* produced using the following Ruby script: + a = (0..255).to_a.map {|i| "&#x#{i.to_s(16)};" } + must_escape = ['&', '<', '>', '"', "'", '`', '!', '@', '$', '%', + '(', ')', '=', '+', '{', '}', '[', ']'] # space? + ["\b","\f","\n","\r","\t",'\\'].each {|i| a[i.ord] = i } + (32..123).each {|i| a[i] = i.chr unless must_escape.include?(i.chr) } + {'<': "<", '>': ">", '"': "&qout;", '&': "&"}.each {|k,v| + a[k.to_s.ord] = v + } + b = a.map {|s| s.length } + puts "static const uint8_t html_escape_len[] = {", b.to_s.slice(1..-2), "};" + */ + static const uint8_t html_escape_len[] = { + 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 5, 1, 1, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 1, 6, 6, 1, 6, 6, 5, 6, 6, 6, 1, 6, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 6, 4, 1, 6, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 6, 1, 1, + 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}; + int r = 0; + size_t start = 0; + size_t pos = 0; + if (!data_len || !data || !dest) + return r; + { /* reserve memory space */ + size_t required_len = data_len; + for (size_t i = 0; i < data_len; ++i) { + required_len += html_escape_len[((uint8_t *)data)[i]]; + } + if (fio_string___write_validate_len(dest, reallocate, &required_len)) { + return (r = -1); /* no partial encoding. */ + }; + } + for (;;) { /* copy and encode data */ + while (pos < data_len && html_escape_len[((uint8_t *)data)[pos]] == 1) + ++pos; + /* don't escape valid UTF-8 */ + if (pos < data_len) + switch ( + fio_string_utf8_valid_code_point((void *)(((uint8_t *)data) + pos), + data_len - pos)) { + case 4: ++pos; /* fall through */ + case 3: ++pos; /* fall through */ + case 2: pos += 2; continue; + } + /* copy valid segment before escaping */ + if (pos != start) { + const size_t len = pos - start; + FIO_MEMCPY(dest->buf + dest->len, (uint8_t *)data + start, len); + dest->len += len; + start = pos; + } + if (pos == data_len) + break; + /* escape data */ + dest->buf[dest->len++] = '&'; + switch (((uint8_t *)data)[pos]) { + case '<': + dest->buf[dest->len++] = 'l'; + dest->buf[dest->len++] = 't'; + break; + case '>': + dest->buf[dest->len++] = 'g'; + dest->buf[dest->len++] = 't'; + break; + case '"': + dest->buf[dest->len++] = 'q'; + dest->buf[dest->len++] = 'u'; + dest->buf[dest->len++] = 'o'; + dest->buf[dest->len++] = 't'; + break; + case '&': + dest->buf[dest->len++] = 'a'; + dest->buf[dest->len++] = 'm'; + dest->buf[dest->len++] = 'p'; + break; + default: + dest->buf[dest->len++] = '#'; + dest->buf[dest->len++] = 'x'; + dest->len += ((dest->buf[dest->len] = + fio_i2c(((uint8_t *)data)[pos] >> 4)) != '0'); + dest->buf[dest->len++] = fio_i2c(((uint8_t *)data)[pos] & 15); + } + dest->buf[dest->len++] = ';'; + ++pos; + start = pos; + } + dest->buf[dest->len] = 0; + return r; +} + +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +SFUNC int fio_string_write_html_unescape(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *data, + size_t data_len) { + int r = 0; + struct { + uint64_t code; + uint32_t clen; + uint8_t r[4]; + } html_named_codes[] = { +#define FIO___STRING_HTML_CODE_POINT(named_code, result) \ + {.code = *(uint64_t *)(named_code "\0\0\0\0\0\0\0\0"), \ + .clen = (uint32_t)(sizeof(named_code) - 1), \ + .r = result} + FIO___STRING_HTML_CODE_POINT("lt", "<"), + FIO___STRING_HTML_CODE_POINT("gt", ">"), + FIO___STRING_HTML_CODE_POINT("amp", "&"), + FIO___STRING_HTML_CODE_POINT("apos", "'"), + FIO___STRING_HTML_CODE_POINT("quot", "\""), + FIO___STRING_HTML_CODE_POINT("nbsp", "\xC2\xA0"), + FIO___STRING_HTML_CODE_POINT("tab", "\t"), + FIO___STRING_HTML_CODE_POINT("ge", "≥"), + FIO___STRING_HTML_CODE_POINT("le", "≤"), + FIO___STRING_HTML_CODE_POINT("ne", "≠"), + FIO___STRING_HTML_CODE_POINT("copy", "©"), + FIO___STRING_HTML_CODE_POINT("raquo", "»"), + FIO___STRING_HTML_CODE_POINT("laquo", "«"), + FIO___STRING_HTML_CODE_POINT("rdquo", "”"), + FIO___STRING_HTML_CODE_POINT("ldquo", "“"), + FIO___STRING_HTML_CODE_POINT("reg", "®"), + FIO___STRING_HTML_CODE_POINT("asymp", "≈"), + FIO___STRING_HTML_CODE_POINT("bdquo", "„"), + FIO___STRING_HTML_CODE_POINT("bull", "•"), + FIO___STRING_HTML_CODE_POINT("cent", "¢"), + FIO___STRING_HTML_CODE_POINT("euro", "€"), + FIO___STRING_HTML_CODE_POINT("dagger", "†"), + FIO___STRING_HTML_CODE_POINT("deg", "°"), + FIO___STRING_HTML_CODE_POINT("frac14", "¼"), + FIO___STRING_HTML_CODE_POINT("frac12", "½"), + FIO___STRING_HTML_CODE_POINT("frac34", "¾"), + FIO___STRING_HTML_CODE_POINT("hellip", "…"), + FIO___STRING_HTML_CODE_POINT("lsquo", "‘"), + FIO___STRING_HTML_CODE_POINT("mdash", "—"), + FIO___STRING_HTML_CODE_POINT("middot", "·"), + FIO___STRING_HTML_CODE_POINT("ndash", "–"), + FIO___STRING_HTML_CODE_POINT("para", "¶"), + FIO___STRING_HTML_CODE_POINT("plusmn", "±"), + FIO___STRING_HTML_CODE_POINT("pound", "£"), + FIO___STRING_HTML_CODE_POINT("prime", "′"), + FIO___STRING_HTML_CODE_POINT("rsquo", "’"), + FIO___STRING_HTML_CODE_POINT("sbquo", "‚"), + FIO___STRING_HTML_CODE_POINT("sect", "§"), + FIO___STRING_HTML_CODE_POINT("trade", "™"), + FIO___STRING_HTML_CODE_POINT("yen", "¥"), + }; + if (!dest || !data || !data_len) + return r; + size_t reduced = data_len + dest->len; + uint8_t *start = (uint8_t *)data; + uint8_t *const end = start + data_len; + if (dest->len + data_len >= dest->capa) { /* reserve only what we need */ + reduced = data_len; + uint8_t *del = start; + while (end > del && (del = (uint8_t *)FIO_MEMCHR(del, '&', end - del))) { + uint8_t *tmp = del++; + /* note that in some cases the `;` might be dropped (history) */ + if (del[0] == '#') { + ++del; + del += (del[0] == 'x'); + uint64_t num = + (del[-1] == 'x' ? fio_atol16u : fio_atol10u)((char **)&del); + if (del >= end || num > 65535) /* untrusted result */ + continue; + del += (*del == ';'); + reduced -= (del - tmp); + reduced += fio_utf8_code_len((uint32_t)num); + continue; + } + union { + uint64_t u64; + uint8_t u8[8]; + } u; + for (size_t i = 0; + i < sizeof(html_named_codes) / sizeof(html_named_codes[0]); + ++i) { + u.u64 = 0; + for (size_t p = 0; p < html_named_codes[i].clen; ++p) + u.u8[p] = del[p] | 32; + if (u.u64 != html_named_codes[i].code) + continue; + del += html_named_codes[i].clen; + if (del > end) + break; + del += (del < end && del[0] == ';'); + reduced -= (del - tmp); + for (size_t j = 0; html_named_codes[i].r[j]; ++j) + ++reduced; + break; + } + } + if (fio_string___write_validate_len(dest, reallocate, &reduced)) { + return (r = -1); /* no partial decoding. */ + } + reduced += dest->len; + } + { /* copy and unescape data */ + uint8_t *del = start = (uint8_t *)data; + while (end > (start = del) && + (del = (uint8_t *)FIO_MEMCHR(del, '&', end - del))) { + if (start != del) { + const size_t len = del - start; + FIO_MEMCPY(dest->buf + dest->len, start, len); + dest->len += len; + start = del; + } + ++del; + if (del == end) + break; + if (del[0] == '#') { + ++del; + if (del + 2 > end) + break; + del += (del[0] == 'x'); + uint64_t num = + (del[-1] == 'x' ? fio_atol16u : fio_atol10u)((char **)&del); + if (*del != ';' || num > 65535) + goto untrusted_no_encode; + dest->len += + fio_utf8_write((uint8_t *)dest->buf + dest->len, (uint32_t)num); + del += (del < end && del[0] == ';'); + continue; + } + /* note that in some cases the `;` might be dropped (history) */ + for (size_t i = 0; + i < sizeof(html_named_codes) / sizeof(html_named_codes[0]); + ++i) { + union { + uint64_t u64; + uint8_t u8[8]; + } u = {0}; + for (size_t p = 0; p < html_named_codes[i].clen; ++p) + u.u8[p] = del[p] | 32; + if (u.u64 != html_named_codes[i].code) + continue; + del += html_named_codes[i].clen; + del += (del < end && del[0] == ';'); + start = del; + for (size_t j = 0; html_named_codes[i].r[j]; ++j) { + dest->buf[dest->len++] = html_named_codes[i].r[j]; + } + break; + } + if (start == del) + continue; + untrusted_no_encode: /* untrusted, don't decode */ + del += (del < end && del[0] == ';'); + FIO_MEMCPY(dest->buf + dest->len, start, del - start); + dest->len += del - start; + } + } + if (start < end) { + const size_t len = end - start; + FIO_MEMCPY(dest->buf + dest->len, start, len); + dest->len += len; + } + dest->buf[dest->len] = 0; + FIO_ASSERT_DEBUG(dest->len < reduced + 1, + "string HTML unescape reduced calculation error"); + return r; +} + +/* ***************************************************************************** +String File Reading support +***************************************************************************** */ + +FIO_IFUNC intptr_t fio___string_fd_normalise_offset(intptr_t i, + size_t file_len) { + if (i < 0) { + i += (intptr_t)file_len + 1; + if (i < 0) + i = 0; + } + return i; +} + +/** + * Writes up to `limit` bytes from `fd` into `dest`, starting at `start_at`. + * + * If `limit` is 0 (or less than 0) data will be written until EOF. + * + * If `start_at` is negative, position will be calculated from the end of the + * file where `-1 == EOF`. + * + * Note: this will fail unless used on actual files (not sockets, not pipes). + * */ +SFUNC int fio_string_readfd(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int fd, + intptr_t start_at, + size_t limit) { + int r = 0; + if (!dest) { + return r; + } + size_t file_len = fio_fd_size(fd); + start_at = fio___string_fd_normalise_offset(start_at, file_len); + if (!limit || file_len < (size_t)(limit + start_at)) { + limit = (intptr_t)file_len - start_at; + } + if (!file_len || !limit || (size_t)start_at >= file_len) { + return (r = -1); + } + r = fio_string___write_validate_len(dest, reallocate, &limit); + size_t added = fio_fd_read(fd, dest->buf + dest->len, limit, (off_t)start_at); + dest->len += added; + dest->buf[dest->len] = 0; + return r; +} + +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. + * + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. + */ +SFUNC int fio_string_readfile(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *filename, + intptr_t start_at, + size_t limit) { + int r = -1; + int fd = fio_filename_open(filename, O_RDONLY); + if (fd == -1) + return r; + r = fio_string_readfd(dest, reallocate, fd, start_at, limit); + close(fd); + return r; +} + +/** + * Writes up to `limit` bytes from `fd` into `dest`, starting at `start_at` + * and ending at the first occurrence of `token`. + * + * If `limit` is 0 (or less than 0) as much data as may be required will be + * written. + * + * If `start_at` is negative, position will be calculated from the end of the + * file where `-1 == EOF`. + * + * Note: this will fail unless used on actual seekable files (not sockets, not + * pipes). + * */ +SFUNC int fio_string_getdelim_fd(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + int fd, + intptr_t start_at, + char delim, + size_t limit) { + int r = -1; + if (!dest || fd == -1) + return (r = 0); + size_t file_len = fio_fd_size(fd); + if (!file_len) + return r; + start_at = fio___string_fd_normalise_offset(start_at, file_len); + if ((size_t)start_at >= file_len) + return r; + size_t index = fio_fd_find_next(fd, delim, (size_t)start_at); + if (index == FIO_FD_FIND_EOF) + return r; + if (limit < 1 || limit > (index - start_at) + 1) { + limit = (index - start_at) + 1; + } + + r = fio_string___write_validate_len(dest, reallocate, &limit); + size_t added = fio_fd_read(fd, dest->buf + dest->len, limit, (off_t)start_at); + dest->len += added; + dest->buf[dest->len] = 0; + return r; +} + +/** + * Opens the file `filename`, calls `fio_string_getdelim_fd` and closes the + * file. + */ +SFUNC int fio_string_getdelim_file(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const char *filename, + intptr_t start_at, + char delim, + size_t limit) { + int r = -1; + int fd = fio_filename_open(filename, O_RDONLY); + if (fd == -1) + return r; + r = fio_string_getdelim_fd(dest, reallocate, fd, start_at, delim, limit); + close(fd); + return r; +} + +/* ***************************************************************************** +Binary String Type - Embedded Strings +***************************************************************************** */ +/** default reallocation callback implementation */ +SFUNC int fio_bstr_reallocate(fio_str_info_s *dest, size_t len) { + fio___bstr_meta_s *bstr_m = NULL; + size_t new_capa = fio_string_capa4len(len + sizeof(bstr_m[0])); + if (FIO_UNLIKELY(new_capa > (size_t)0xFFFFFFFFULL)) + new_capa = (size_t)0x0FFFFFFFFULL + sizeof(bstr_m[0]); + if (dest->capa < fio_string_capa4len(sizeof(bstr_m[0]))) + goto copy_the_string; + bstr_m = (fio___bstr_meta_s *)FIO_MEM_REALLOC_( + ((fio___bstr_meta_s *)dest->buf - 1), + sizeof(bstr_m[0]) + dest->capa, + new_capa, + FIO___BSTR_META(dest->buf)->len + sizeof(bstr_m[0])); + if (!bstr_m) + return -1; +update_metadata: + dest->buf = (char *)(bstr_m + 1); + dest->capa = new_capa - sizeof(bstr_m[0]); + bstr_m->capa = (uint32_t)dest->capa; + return 0; + +copy_the_string: + bstr_m = (fio___bstr_meta_s *)FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); + if (!bstr_m) + return -1; + if (!FIO_MEM_REALLOC_IS_SAFE_) + *bstr_m = (fio___bstr_meta_s){0}; + FIO_LEAK_COUNTER_ON_ALLOC(fio_bstr_s); + if (dest->len) { + FIO_MEMCPY((bstr_m + 1), dest->buf, dest->len + 1); + bstr_m->len = (uint32_t)dest->len; + } + if (dest->capa) + fio_bstr_free(dest->buf); + goto update_metadata; +} + +/* ***************************************************************************** +String Core Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_STR +#endif /* H__FIO_STR__H */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MUSTACHE module /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Mustache-ish Template Engine + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MUSTACHE) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_MUSTACHE___H) +#define H___FIO_MUSTACHE___H + +/* ***************************************************************************** +Settings +***************************************************************************** */ + +#ifndef FIO_MUSTACHE_MAX_DEPTH +/** The maximum depth of a template's context */ +#define FIO_MUSTACHE_MAX_DEPTH 128 +#endif + +#ifndef FIO_MUSTACHE_PRESERVE_PADDING +/** Preserves padding for stand-alone variables and partial templates */ +#define FIO_MUSTACHE_PRESERVE_PADDING 0 +#endif +#ifndef FIO_MUSTACHE_LAMBDA_SUPPORT +/** Supports raw text for lambda style languages. */ +#define FIO_MUSTACHE_LAMBDA_SUPPORT 0 +#endif +#ifndef FIO_MUSTACHE_ISOLATE_PARTIALS +/** Limits the scope of partial templates to the context of their section. */ +#define FIO_MUSTACHE_ISOLATE_PARTIALS 1 +#endif + +/* ***************************************************************************** +Mustache Parser / Builder API +***************************************************************************** */ + +typedef struct fio_mustache_s fio_mustache_s; +typedef struct fio_mustache_bargs_s fio_mustache_bargs_s; + +typedef struct { + /** The file's content (if pre-loaded) */ + fio_buf_info_s data; + /** The file's name (even if preloaded, used for partials load paths) */ + fio_buf_info_s filename; + /** Loads the file's content, returning a `fio_buf_info_s` structure. */ + fio_buf_info_s (*load_file_data)(fio_buf_info_s filename, void *udata); + /** Frees the file's content from its `fio_buf_info_s` structure. */ + void (*free_file_data)(fio_buf_info_s file_data, void *udata); + /** Called when YAML front matter data was found. */ + void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata); + /** Opaque user data. */ + void *udata; +} fio_mustache_load_args_s; + +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC fio_mustache_s *fio_mustache_load(fio_mustache_load_args_s settings); +/* Allocates a new object on the heap and initializes it's memory. */ +#define fio_mustache_load(...) \ + fio_mustache_load((fio_mustache_load_args_s){__VA_ARGS__}) + +/* Frees the mustache template object (or reduces it's reference count). */ +SFUNC void fio_mustache_free(fio_mustache_s *m); + +/** Increases the mustache template's reference count. */ +SFUNC fio_mustache_s *fio_mustache_dup(fio_mustache_s *m); + +struct fio_mustache_bargs_s { + /* callback should write `txt` to output and return updated `udata.` */ + void *(*write_text)(void *udata, fio_buf_info_s txt); + /* same as `write_text`, but should also HTML escape (sanitize) data. */ + void *(*write_text_escaped)(void *udata, fio_buf_info_s raw); + /* callback should return a new context pointer with the value of `name`. */ + void *(*get_var)(void *ctx, fio_buf_info_s name); + /* if context is an Array, should return its length. */ + size_t (*array_length)(void *ctx); + /* if context is an Array, should return a context pointer @ index. */ + void *(*get_var_index)(void *ctx, size_t index); + /* should return the String value of context `var` as a `fio_buf_info_s`. */ + fio_buf_info_s (*var2str)(void *var); + /* should return non-zero if the context pointer refers to a valid value. */ + int (*var_is_truthful)(void *ctx); + /* callback signals that the `ctx` context pointer is no longer in use. */ + void (*release_var)(void *ctx); + /* returns non-zero if `ctx` is a lambda and handles section manually. */ + int (*is_lambda)(void **udata, + void *ctx, + fio_buf_info_s raw_template_section); + /* the root context for finding named values. */ + void *ctx; + /* opaque user data (settable as well as readable), the final return value. */ + void *udata; +}; + +/** Builds the template, returning the final value of `udata` (or NULL). */ +SFUNC void *fio_mustache_build(fio_mustache_s *m, fio_mustache_bargs_s); +#define fio_mustache_build(m, ...) \ + fio_mustache_build((m), ((fio_mustache_bargs_s){__VA_ARGS__})) + +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +FIO_LEAK_COUNTER_DEF(fio_mustache_s) + +/* ***************************************************************************** +Instructions (relative state) + +All instructions are 1 byte long with optional extra data. + +Instruction refer to offsets rather than absolute values. +***************************************************************************** */ +/* for ease of use, instructions are always a 1 byte numeral, */ +typedef enum { + FIO___MUSTACHE_I_STACK_POP, /* 0 extra data (marks end of array / list)? */ + FIO___MUSTACHE_I_STACK_PUSH, /* 32 bit extra data (goes to position) */ + FIO___MUSTACHE_I_GOTO_PUSH, /* 32 bit extra data (goes to position) */ + FIO___MUSTACHE_I_TXT, /* 16 bits length + data */ + FIO___MUSTACHE_I_VAR, /* 16 bits length + data */ + FIO___MUSTACHE_I_VAR_RAW, /* 16 bits length + data */ + FIO___MUSTACHE_I_ARY, /* 16 bits length + 32 bit skip-pos + data */ + FIO___MUSTACHE_I_MISSING, /* 16 bits length + 32 bit skip-pos + data */ + FIO___MUSTACHE_I_PADDING_PUSH, /* 16 bits length + data */ + FIO___MUSTACHE_I_PADDING_POP, /* 0 extra data */ +#if FIO_MUSTACHE_PRESERVE_PADDING + FIO___MUSTACHE_I_VAR_PADDED, + FIO___MUSTACHE_I_VAR_RAW_PADDED, +#endif +#if FIO_MUSTACHE_LAMBDA_SUPPORT + FIO___MUSTACHE_I_METADATA, /* raw text data, written for lambda support */ +#endif +} fio___mustache_inst_e; +/* ***************************************************************************** +Instructions - Main processor +***************************************************************************** */ + +typedef struct fio___mustache_bldr_s { + char *root; + struct fio___mustache_bldr_s *prev; + void *ctx; + fio_buf_info_s padding; + fio_mustache_bargs_s *args; +#if FIO_MUSTACHE_ISOLATE_PARTIALS + uint32_t stop; +#endif +} fio___mustache_bldr_s; + +FIO_SFUNC char *fio___mustache_i_stack_pop(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_stack_push(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_goto_push(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_txt(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_var(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_var_raw(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_ary(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_missing(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_padding_push(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_padding_pop(char *p, fio___mustache_bldr_s *); +#if FIO_MUSTACHE_PRESERVE_PADDING +FIO_SFUNC char *fio___mustache_i_var_padded(char *p, fio___mustache_bldr_s *); +FIO_SFUNC char *fio___mustache_i_var_raw_padded(char *, + fio___mustache_bldr_s *); +#endif +#if FIO_MUSTACHE_LAMBDA_SUPPORT +FIO_SFUNC char *fio___mustache_i_metadata(char *p, fio___mustache_bldr_s *); +#endif + +FIO_SFUNC void *fio___mustache_build_section(char *p, fio___mustache_bldr_s a) { + static char *(*map[])(char *, fio___mustache_bldr_s *) = { + [FIO___MUSTACHE_I_STACK_POP] = fio___mustache_i_stack_pop, + [FIO___MUSTACHE_I_STACK_PUSH] = fio___mustache_i_stack_push, + [FIO___MUSTACHE_I_GOTO_PUSH] = fio___mustache_i_goto_push, + [FIO___MUSTACHE_I_TXT] = fio___mustache_i_txt, + [FIO___MUSTACHE_I_VAR] = fio___mustache_i_var, + [FIO___MUSTACHE_I_VAR_RAW] = fio___mustache_i_var_raw, + [FIO___MUSTACHE_I_ARY] = fio___mustache_i_ary, + [FIO___MUSTACHE_I_MISSING] = fio___mustache_i_missing, + [FIO___MUSTACHE_I_PADDING_PUSH] = fio___mustache_i_padding_push, + [FIO___MUSTACHE_I_PADDING_POP] = fio___mustache_i_padding_pop, +#if FIO_MUSTACHE_PRESERVE_PADDING + [FIO___MUSTACHE_I_VAR_PADDED] = fio___mustache_i_var_padded, + [FIO___MUSTACHE_I_VAR_RAW_PADDED] = fio___mustache_i_var_raw_padded, +#endif +#if FIO_MUSTACHE_LAMBDA_SUPPORT + [FIO___MUSTACHE_I_METADATA] = fio___mustache_i_metadata, +#endif + + }; + while (p) + p = map[(uint8_t)(p[0])](p, &a); + return a.args->udata; +} + +/* ***************************************************************************** +Instructions - Helpers +***************************************************************************** */ + +/* consumes `val_name`, in whole or in part, returning the variable found. + * sets `val_name` to the unconsumed partial remaining. + */ +FIO_IFUNC void *fio___mustache_get_var_in_context(fio_mustache_bargs_s *a, + void *ctx, + fio_buf_info_s *val_name) { + void *v = ctx; + v = a->get_var(ctx, *val_name); + if (v) { + val_name->len = 0; + return v; + } + char *s = val_name->buf; + char *end = val_name->buf + val_name->len; + for (;;) { + if (s == end) + return v; + s = (char *)FIO_MEMCHR(s, '.', (size_t)(end - s)); + if (!s) + return v; + v = a->get_var(ctx, + FIO_BUF_INFO2(val_name->buf, (size_t)(s - val_name->buf))); + ++s; + if (!v) + continue; + val_name->buf = s; + val_name->len = (size_t)(end - s); + return v; + } +} + +FIO_IFUNC void *fio___mustache_get_var(fio___mustache_bldr_s *b, + fio_buf_info_s val_name) { + void *v = b->ctx; + if (val_name.len == 1 && val_name.buf[0] == '.') + return v; + for (;;) { + if (b->ctx) + v = fio___mustache_get_var_in_context(b->args, b->ctx, &val_name); + if (v) + break; +#if FIO_MUSTACHE_ISOLATE_PARTIALS + if (b->stop) + return v; +#endif + b = b->prev; + if (!b) + return v; + } + while (val_name.len && v) + v = fio___mustache_get_var_in_context(b->args, v, &val_name); + return v; +} + +FIO_SFUNC void fio___mustache_write_padding(fio___mustache_bldr_s *b) { + while (b && b->padding.len) { + if (b->padding.buf) { + b->args->udata = b->args->write_text(b->args->udata, b->padding); + } + b = b->prev; + } +} + +FIO_SFUNC void fio___mustache_write_text_simple( + fio___mustache_bldr_s *b, + fio_buf_info_s txt, + void *(*writer)(void *, fio_buf_info_s txt)) { + b->args->udata = writer(b->args->udata, txt); +} + +FIO_SFUNC void fio___mustache_write_text_complex( + fio___mustache_bldr_s *b, + fio_buf_info_s txt, + void *(*writer)(void *, fio_buf_info_s txt)) { + const char *end = txt.buf + txt.len; + for (;;) { + char *pos = (char *)FIO_MEMCHR(txt.buf, '\n', (size_t)(end - txt.buf)); + if (!pos) + break; + ++pos; + if (txt.buf[0] != '\n' || (size_t)(pos - txt.buf) > 1) + fio___mustache_write_padding(b); + b->args->udata = + writer(b->args->udata, FIO_BUF_INFO2(txt.buf, (size_t)(pos - txt.buf))); + txt.buf = pos; + if (pos < end) + continue; + return; + } + if (txt.buf < end) { + fio___mustache_write_padding(b); + b->args->udata = + writer(b->args->udata, FIO_BUF_INFO2(txt.buf, (size_t)(end - txt.buf))); + } +} + +FIO_IFUNC void fio___mustache_writer_route( + fio___mustache_bldr_s *b, + fio_buf_info_s txt, + void *(*writer)(void *, fio_buf_info_s txt)) { + void (*router[2])(fio___mustache_bldr_s *, + fio_buf_info_s txt, + void *(*writer)(void *, fio_buf_info_s txt)) = { + fio___mustache_write_text_complex, + fio___mustache_write_text_simple}; + router[!(b->padding.len)](b, txt, writer); +} + +/* ***************************************************************************** +Instruction Implementations +***************************************************************************** */ + +FIO_SFUNC char *fio___mustache_i_stack_pop(char *p, fio___mustache_bldr_s *b) { + return NULL; + (void)p, (void)b; +} +FIO_SFUNC char *fio___mustache_i_stack_push(char *p, fio___mustache_bldr_s *b) { + char *npos = p + 5; + p = b->root + fio_buf2u32u(p + 1); + fio___mustache_bldr_s builder = { + .root = b->root, + .prev = b, +#if FIO_MUSTACHE_ISOLATE_PARTIALS + .ctx = b->ctx, +#endif + .padding = FIO_BUF_INFO2(NULL, b->padding.len), + .args = b->args, +#if FIO_MUSTACHE_ISOLATE_PARTIALS + .stop = 1, +#endif + }; + fio___mustache_build_section(npos, builder); + return p; +} +FIO_SFUNC char *fio___mustache_i_goto_push(char *p, fio___mustache_bldr_s *b) { + char *npos = b->root + fio_buf2u32u(p + 1); + fio___mustache_bldr_s builder = { + .root = b->root, + .prev = b, +#if FIO_MUSTACHE_ISOLATE_PARTIALS + .ctx = b->ctx, +#endif + .padding = FIO_BUF_INFO2(NULL, b->padding.len), + .args = b->args, +#if FIO_MUSTACHE_ISOLATE_PARTIALS + .stop = 1, +#endif + }; + fio___mustache_build_section(npos, builder); + p += 5; + return p; +} +FIO_SFUNC char *fio___mustache_i_txt(char *p, fio___mustache_bldr_s *b) { + fio_buf_info_s txt = FIO_BUF_INFO2(p + 3, fio_buf2u16u(p + 1)); + p = txt.buf + txt.len; + fio___mustache_writer_route(b, txt, b->args->write_text); + return p; +} + +FIO_IFUNC char *fio___mustache_i_var_internal( + char *p, + fio___mustache_bldr_s *b, + void *(*writer)(void *, fio_buf_info_s txt)) { + fio_buf_info_s var = FIO_BUF_INFO2(p + 3, fio_buf2u16u(p + 1)); + p = var.buf + var.len; + void *v = fio___mustache_get_var(b, var); + if (!v) + return p; + var = b->args->var2str(v); + b->args->release_var(v); +#if FIO_MUSTACHE_PRESERVE_PADDING + fio___mustache_writer_route(b, var, writer); +#else + fio___mustache_write_padding(b); + b->args->udata = writer(b->args->udata, var); +#endif + return p; +} +FIO_SFUNC char *fio___mustache_i_var(char *p, fio___mustache_bldr_s *b) { + return fio___mustache_i_var_internal(p, b, b->args->write_text_escaped); +} +FIO_SFUNC char *fio___mustache_i_var_raw(char *p, fio___mustache_bldr_s *b) { + return fio___mustache_i_var_internal(p, b, b->args->write_text); +} + +FIO_SFUNC char *fio___mustache_i_ary(char *p, fio___mustache_bldr_s *b) { + fio_buf_info_s var = FIO_BUF_INFO2(p + 7, fio_buf2u16u(p + 1)); + uint32_t skip_pos = fio_buf2u32u(p + 3); + p = b->root + skip_pos; +#if FIO_MUSTACHE_LAMBDA_SUPPORT + fio_buf_info_s section_raw_txt = FIO_BUF_INFO2(NULL, 0); + if (p[0] == FIO___MUSTACHE_I_METADATA) { + section_raw_txt = FIO_BUF_INFO2(p + 3, fio_buf2u16u(p + 1)); + p = section_raw_txt.buf + section_raw_txt.len; + } +#else + const fio_buf_info_s section_raw_txt = FIO_BUF_INFO2(NULL, 0); +#endif + + void *v = fio___mustache_get_var(b, var); + if (!(b->args->var_is_truthful(v))) + return p; + size_t index = 0; + const size_t ary_len = b->args->array_length(v); + void *nctx = v; + if (ary_len) + nctx = b->args->get_var_index(v, index); + for (;;) { + ++index; + fio___mustache_bldr_s builder = { + .root = b->root, + .prev = b, + .ctx = nctx, + .padding = FIO_BUF_INFO2(NULL, b->padding.len), + .args = b->args, + }; + if (!b->args->is_lambda(&(b->args->udata), nctx, section_raw_txt)) { + fio___mustache_build_section(var.buf + var.len, builder); + } + b->args->release_var(nctx); + if (index >= ary_len) { + if (nctx != v) + b->args->release_var(v); + return p; + } + nctx = b->args->get_var_index(v, index); + } +} +FIO_SFUNC char *fio___mustache_i_missing(char *p, fio___mustache_bldr_s *b) { + fio_buf_info_s var = FIO_BUF_INFO2(p + 7, fio_buf2u16u(p + 1)); + uint32_t skip_pos = fio_buf2u32u(p + 3); + p = b->root + skip_pos; + + void *v = fio___mustache_get_var(b, var); + if (b->args->var_is_truthful(v)) { + b->args->release_var(v); + return p; + } + + fio___mustache_bldr_s builder = { + .root = b->root, + .prev = b, + .padding = FIO_BUF_INFO2(NULL, b->padding.len), + .args = b->args, + }; + fio___mustache_build_section(var.buf + var.len, builder); + return p; +} + +FIO_SFUNC char *fio___mustache_i_padding_push(char *p, + fio___mustache_bldr_s *b) { + b->padding = FIO_BUF_INFO2(p + 3, fio_buf2u16u(p + 1)); + return p + 3 + b->padding.len; +} +FIO_SFUNC char *fio___mustache_i_padding_pop(char *p, + fio___mustache_bldr_s *b) { + b->padding = FIO_BUF_INFO2(NULL, 0); + if (b->prev) + b->padding.len = b->prev->padding.len; + return p + 1; +} + +#if FIO_MUSTACHE_PRESERVE_PADDING + +FIO_SFUNC char *fio___mustache_i_var_padded(char *p, fio___mustache_bldr_s *b) { + fio_buf_info_s var = FIO_BUF_INFO2(p + 5, fio_buf2u16u(p + 1)); + fio_buf_info_s padding = FIO_BUF_INFO2(p + 5 + var.len, fio_buf2u16u(p + 3)); + p = padding.buf + padding.len; + void *v = fio___mustache_get_var(b, var); + if (!v) + return p; + var = b->args->var2str(v); + if (!var.len) + goto done; + fio___mustache_bldr_s b2 = *b; + b2.padding = padding; + fio___mustache_writer_route(&b2, var, b->args->write_text_escaped); +done: + b->args->release_var(v); + return p; +} +FIO_SFUNC char *fio___mustache_i_var_raw_padded(char *p, + fio___mustache_bldr_s *b) { + fio_buf_info_s var = FIO_BUF_INFO2(p + 5, fio_buf2u16u(p + 1)); + fio_buf_info_s padding = FIO_BUF_INFO2(p + 5 + var.len, fio_buf2u16u(p + 3)); + p = padding.buf + padding.len; + void *v = fio___mustache_get_var(b, var); + if (!v) + return p; + var = b->args->var2str(v); + if (!var.len) + goto done; + fio___mustache_bldr_s b2 = *b; + b2.padding = padding; + fio___mustache_writer_route(&b2, var, b->args->write_text); + b->args->release_var(v); + return p; +} + +#endif + +#if FIO_MUSTACHE_LAMBDA_SUPPORT +FIO_SFUNC char *fio___mustache_i_metadata(char *p, fio___mustache_bldr_s *b) { + uint32_t len = fio_buf2u16u(p + 1); + return p + 3 + len; + (void)b; +} +#endif + +/* ***************************************************************************** +Mustache delimiter testing +***************************************************************************** */ +FIO_SFUNC _Bool fio___mustache_delcmp1(const char *restrict a, + const char *restrict b) { + return 1; + (void)a, (void)b; +} +FIO_SFUNC _Bool fio___mustache_delcmp2(const char *restrict a, + const char *restrict b) { + return a[1] == b[1]; +} +FIO_SFUNC _Bool fio___mustache_delcmp3(const char *restrict a, + const char *restrict b) { + return a[1] == b[1] && a[2] == b[2]; +} +FIO_SFUNC _Bool fio___mustache_delcmp4(const char *restrict a, + const char *restrict b) { + return a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; +} + +typedef struct fio___mustache_delimiter_s { + struct { + _Bool (*cmp)(const char *restrict, const char *restrict); + uint32_t len; + char buf[4]; + } in, out; +} fio___mustache_delimiter_s; + +FIO_IFUNC fio___mustache_delimiter_s fio___mustache_delimiter_init(void) { + fio___mustache_delimiter_s r = { + .in = {.cmp = fio___mustache_delcmp2, .len = 2, .buf = {'{', '{'}}, + .out = {.cmp = fio___mustache_delcmp2, .len = 2, .buf = {'}', '}'}}, + }; + return r; +} + +/* ***************************************************************************** +Parser type & helpers +***************************************************************************** */ + +typedef struct fio___mustache_parser_s { + char *root; + struct fio___mustache_parser_s *prev; + fio_mustache_load_args_s *args; + fio___mustache_delimiter_s delim; + fio_buf_info_s fname; + fio_buf_info_s path; + fio_buf_info_s backwards; + fio_buf_info_s forwards; + uint32_t starts_at; + uint32_t depth; + uint32_t dirty; +} fio___mustache_parser_s; + +/* ***************************************************************************** +Template file loading +***************************************************************************** */ +FIO_SFUNC fio_buf_info_s +fio___mustache_load_template(fio___mustache_parser_s *p, fio_buf_info_s fname) { + /* Attempt to load templates in the following order: + * 1. Calling template folder + * 2. Parent calling folder (recursively)? + * 3. Working folder. + */ + fio_buf_info_s r = {0}; + fio_buf_info_s const extensions[] = {FIO_BUF_INFO1((char *)".mustache"), + FIO_BUF_INFO1((char *)".html"), + FIO_BUF_INFO2((char *)"", 0), + {0}}; + FIO_STR_INFO_TMP_VAR(fn, (PATH_MAX | 2094)); + if (FIO_UNLIKELY(!fname.len || fname.len > (PATH_MAX - 1))) + return r; + fio___mustache_parser_s *tp = p; + /* TODO: iterate file names to test for a match... */ + if (fname.buf[0] != FIO_FOLDER_SEPARATOR && fname.buf[0] != '/') { + for (;;) { /* test and load file with a possible relative base path... */ + /* test if file was previously loaded (with this base-path) */ + for (;;) { + if (FIO_BUF_INFO_IS_EQ(tp->fname, fname)) + goto already_exists; + if (tp->path.buf) + break; /* we arrived at current relative path root */ + tp = tp->prev; + if (!tp) + goto absolute_path_or_cwd; + } + /* test current relative path with each filename & extension combo */ + if (tp->path.len + fname.len + 32 < ((PATH_MAX | 2094) - 1)) { + for (size_t i = 0; extensions[i].buf; ++i) { + fn.len = 0; + fio_string_write2( + &fn, + NULL, + FIO_STRING_WRITE_STR2(tp->path.buf, tp->path.len), + FIO_STRING_WRITE_STR2(fname.buf, fname.len), + FIO_STRING_WRITE_STR2(extensions[i].buf, extensions[i].len)); + r = p->args->load_file_data(FIO_STR2BUF_INFO(fn), p->args->udata); + if (r.len) + goto file_loaded_successfully; + } + } + tp = tp->prev; + if (!tp) + goto absolute_path_or_cwd; + } + } + +absolute_path_or_cwd: + /* possibly full-path specified + fallback to working folder */ + for (size_t i = 0; extensions[i].buf; ++i) { + fn.len = 0; + fio_string_write2( + &fn, + NULL, + FIO_STRING_WRITE_STR2(fname.buf, fname.len), + FIO_STRING_WRITE_STR2(extensions[i].buf, extensions[i].len)); + r = p->args->load_file_data(FIO_STR2BUF_INFO(fn), p->args->udata); + if (r.len) + goto file_loaded_successfully; + } + +file_loaded_successfully: + return r; + +already_exists: + fn.len = 5; /* TODO: fixme? */ + fn.buf[0] = FIO___MUSTACHE_I_GOTO_PUSH; + fio_u2buf32u(fn.buf + 1, tp->starts_at); + p->root = fio_bstr_write(p->root, fn.buf, fn.len); + return r; +} + +FIO_SFUNC void fio___mustache_free_template(fio___mustache_parser_s *p, + fio_buf_info_s d) { + p->args->free_file_data(d, p->args->udata); +} + +/* ***************************************************************************** +Tag Helpers +***************************************************************************** */ + +/* forward declaration, implemented later */ +FIO_SFUNC int fio___mustache_parse_block(fio___mustache_parser_s *p); +FIO_SFUNC int fio___mustache_parse_template_file(fio___mustache_parser_s *p); + +FIO_IFUNC void fio___mustache_stand_alone_skip_eol(fio___mustache_parser_s *p) { + size_t offset = + !p->dirty && p->forwards.buf[0] == '\r' && p->forwards.buf[1] == '\n'; + p->forwards.buf += offset; + p->forwards.len -= offset; + offset = !p->dirty && p->forwards.buf[0] == '\n'; + p->forwards.buf += offset; + p->forwards.len -= offset; +} + +FIO_IFUNC int fio___mustache_parse_add_text(fio___mustache_parser_s *p, + fio_buf_info_s txt) { + union { + uint64_t u64[1]; + char u8[8]; + } buf; + buf.u8[0] = FIO___MUSTACHE_I_TXT; + FIO_ASSERT_DEBUG(txt.len < (1 << 16), + "(mustache) text instruction overflow!"); + fio_u2buf16u(buf.u8 + 1, txt.len); + p->root = fio_bstr_write2(p->root, + FIO_STRING_WRITE_STR2(buf.u8, 3), + FIO_STRING_WRITE_STR2(txt.buf, txt.len)); + return 0; +} + +FIO_IFUNC int fio___mustache_parse_comment(fio___mustache_parser_s *p, + fio_buf_info_s comment) { + fio___mustache_stand_alone_skip_eol(p); + return 0; + (void)comment; +} + +FIO_IFUNC int fio___mustache_parse_section_end(fio___mustache_parser_s *p, + fio_buf_info_s var) { + union { + uint64_t u64[1]; + char u8[8]; + } buf; + char *prev = NULL; + fio_buf_info_s old_var_name; + buf.u8[0] = FIO___MUSTACHE_I_STACK_POP; + if (!p->prev) + goto section_not_open; + prev = p->root + p->starts_at; + if (*prev != FIO___MUSTACHE_I_ARY && *prev != FIO___MUSTACHE_I_MISSING) + goto section_not_open; + old_var_name = FIO_BUF_INFO2(prev + 7, (size_t)fio_buf2u16u(prev + 1)); + if (!FIO_BUF_INFO_IS_EQ(old_var_name, var)) + goto value_name_mismatch; + + fio_u2buf32u(prev + 3, (uint32_t)(fio_bstr_len(p->root) + 1)); + fio___mustache_stand_alone_skip_eol(p); + +#if FIO_MUSTACHE_LAMBDA_SUPPORT + old_var_name = + FIO_BUF_INFO2(p->prev->forwards.buf, + (size_t)(p->backwards.buf - p->prev->forwards.buf)); + old_var_name.len -= (old_var_name.len && old_var_name.buf[-1] == '\n'); + old_var_name.len -= (old_var_name.len && old_var_name.buf[-1] == '\r'); + if (old_var_name.len && old_var_name.len < (1U << 16)) { + buf.u8[1] = FIO___MUSTACHE_I_METADATA; + fio_u2buf16u(buf.u8 + 2, old_var_name.len); + p->root = fio_bstr_write2( + p->root, + FIO_STRING_WRITE_STR2(buf.u8, 4), + FIO_STRING_WRITE_STR2(old_var_name.buf, old_var_name.len)); + } else +#endif + p->root = fio_bstr_write(p->root, buf.u8, 1); + return -2; + +value_name_mismatch: + FIO_LOG_ERROR( + "(mustache) template section end tag doesn't match section start:" + "\n\t\t%.*s != %.*s", + (int)var.len, + var.buf, + (int)old_var_name.len, + old_var_name.buf); + return -1; + +section_not_open: + FIO_LOG_ERROR("(mustache) section end tag with no section opening tag?" + "\n\t\t%.*s", + (int)var.len, + var.buf); + return -1; +} + +FIO_IFUNC int fio___mustache_parse_section_start(fio___mustache_parser_s *p, + fio_buf_info_s var, + size_t inverted) { + if (p->depth == FIO_MUSTACHE_MAX_DEPTH) + return -1; + fio___mustache_stand_alone_skip_eol(p); + + fio___mustache_parser_s new_section = { + .root = p->root, + .prev = p, + .args = p->args, + .delim = p->delim, + .forwards = p->forwards, + .starts_at = (uint32_t)fio_bstr_len(p->root), + .depth = p->depth + 1, + .dirty = p->dirty, + }; + union { + uint64_t u64[1]; + char u8[8]; + } buf; + buf.u8[0] = FIO___MUSTACHE_I_ARY + inverted; + fio_u2buf16u(buf.u8 + 1, var.len); + /* + 32 bit value to be filled by closure. */ + new_section.root = fio_bstr_write2(new_section.root, + FIO_STRING_WRITE_STR2(buf.u8, 7), + FIO_STRING_WRITE_STR2(var.buf, var.len)); +#if FIO_MUSTACHE_PRESERVE_PADDING + if (!p->dirty && p->backwards.len) { + buf.u8[0] = FIO___MUSTACHE_I_PADDING_PUSH; + fio_u2buf16u(buf.u8 + 1, p->backwards.len); + new_section.root = fio_bstr_write2( + new_section.root, + FIO_STRING_WRITE_STR2(buf.u8, 3), + FIO_STRING_WRITE_STR2(p->backwards.buf, p->backwards.len)); + } +#endif + int r = fio___mustache_parse_block(&new_section); + p->root = new_section.root; + p->forwards = new_section.forwards; + return r; +} + +FIO_IFUNC int fio___mustache_parse_partial(fio___mustache_parser_s *p, + fio_buf_info_s filename) { + if (p->depth == FIO_MUSTACHE_MAX_DEPTH) + return -1; + + fio___mustache_stand_alone_skip_eol(p); + + fio_buf_info_s file_content = fio___mustache_load_template(p, filename); + if (!file_content.len) + return 0; + + union { + uint64_t u64[1]; + char u8[8]; + } buf; + + buf.u8[0] = FIO___MUSTACHE_I_STACK_PUSH; + size_t ipos = fio_bstr_len(p->root) + 1; + p->root = fio_bstr_write(p->root, buf.u8, 5); + + if (!p->dirty && p->backwards.len) { + buf.u8[0] = FIO___MUSTACHE_I_PADDING_PUSH; + fio_u2buf16u(buf.u8 + 1, p->backwards.len); + p->root = fio_bstr_write2( + p->root, + FIO_STRING_WRITE_STR2(buf.u8, 3), + FIO_STRING_WRITE_STR2(p->backwards.buf, p->backwards.len)); + } + + fio___mustache_parser_s new_section = { + .root = p->root, + .prev = p, + .args = p->args, + .delim = fio___mustache_delimiter_init(), + .fname = filename, + .path = fio_filename_parse2(filename.buf, filename.len).folder, + .forwards = file_content, + .starts_at = (uint32_t)fio_bstr_len(p->root), + .depth = p->depth + 1, + .dirty = 0, + }; + + int r = fio___mustache_parse_template_file(&new_section); + buf.u8[0] = FIO___MUSTACHE_I_STACK_POP; + p->root = fio_bstr_write(new_section.root, buf.u8, 1); + fio_u2buf32u(p->root + ipos, (uint32_t)fio_bstr_len(p->root)); + fio___mustache_free_template(p, file_content); + return r; +} + +FIO_IFUNC int fio___mustache_parse_set_delim(fio___mustache_parser_s *p, + fio_buf_info_s buf) { + struct { + uint32_t len; + void *(*cpy)(void *a, const void *b); + _Bool (*cmp)(const char *restrict, const char *restrict); + } const len_map[] = { + {0}, + {1, fio_memcpy1, fio___mustache_delcmp1}, + {2, fio_memcpy2, fio___mustache_delcmp2}, + {3, fio_memcpy3, fio___mustache_delcmp3}, + {4, fio_memcpy4, fio___mustache_delcmp4}, + }; + + fio___mustache_stand_alone_skip_eol(p); + + char *end = buf.buf + buf.len; + char *pos = buf.buf; + while (pos < end && *pos != ' ' && *pos != '\t') + ++pos; + if (pos == end) + goto delim_tag_error; + buf.len = (size_t)(pos - buf.buf); + while (*pos == ' ' || *pos == '\t') + ++pos; + if (pos >= end) + goto delim_tag_error; + + if ((size_t)(end - pos) > 4UL || !(size_t)(end - pos) || !buf.len || + buf.len > 4UL) + goto delim_tag_error; + len_map[buf.len].cpy(p->delim.in.buf, buf.buf); + len_map[(size_t)(end - pos)].cpy(p->delim.out.buf, pos); + p->delim.in.cmp = len_map[buf.len].cmp; + p->delim.out.cmp = len_map[(size_t)(end - pos)].cmp; + p->delim.in.len = len_map[buf.len].len; + p->delim.out.len = len_map[(size_t)(end - pos)].len; + return 0; + +delim_tag_error: + FIO_LOG_ERROR("(mustache) delimiter tag error: %.*s", + (int)(end - buf.buf), + buf.buf); + return -1; +} + +FIO_IFUNC int fio___mustache_parse_var_name(fio___mustache_parser_s *p, + fio_buf_info_s var, + size_t raw) { + union { + uint64_t u64[1]; + char u8[8]; + } buf; + if (p->backwards.len > ((1 << 16) - 1)) + p->backwards.len = 0; + +#if FIO_MUSTACHE_PRESERVE_PADDING + if (p->backwards.len) + goto padded; +#else + fio___mustache_parse_add_text(p, p->backwards); +#endif + buf.u8[0] = (char)(FIO___MUSTACHE_I_VAR + raw); + fio_u2buf16u(buf.u8 + 1, var.len); + p->root = fio_bstr_write2(p->root, + FIO_STRING_WRITE_STR2(buf.u8, 3), + FIO_STRING_WRITE_STR2(var.buf, var.len)); + return 0; +#if FIO_MUSTACHE_PRESERVE_PADDING +padded: + /* TODO: fixme (what if padding value is already used?) */ + buf.u8[0] = (char)(FIO___MUSTACHE_I_VAR_PADDED + raw); + fio_u2buf16u(buf.u8 + 1, var.len); + fio_u2buf16u(buf.u8 + 3, p->backwards.len); + p->root = fio_bstr_write2( + p->root, + FIO_STRING_WRITE_STR2(buf.u8, 5), + FIO_STRING_WRITE_STR2(var.buf, var.len), + FIO_STRING_WRITE_STR2(p->backwards.buf, p->backwards.len)); + return 0; +#endif +} + +/* ***************************************************************************** +Tag Consumer +***************************************************************************** */ + +FIO_SFUNC int fio___mustache_parse_consume_tag(fio___mustache_parser_s *p, + fio_buf_info_s buf) { + /* remove white-space from name */ + for (; buf.len && + (buf.buf[buf.len - 1] == ' ' || buf.buf[buf.len - 1] == '\t');) + --buf.len; + if (!buf.len) { + FIO_LOG_ERROR("(mustache) template tags must contain a value!"); + return -1; + } + + while (buf.buf[0] == ' ' || buf.buf[0] == '\t') { + ++buf.buf; + --buf.len; + } + char id = buf.buf[0]; + if (!(id == '/' || id == '#' || id == '^' || id == '>' || id == '!' || + id == '&' || (id == '=' && buf.buf[buf.len - 1] == '=') || + (id == '{' && buf.buf[buf.len - 1] == '}'))) + return fio___mustache_parse_var_name(p, buf, 0); /* escaped var */ + + /* tag starts with a marker, seek new tag starting point */ + do { + ++buf.buf; + --buf.len; + if (buf.len) + continue; + FIO_LOG_ERROR("(mustache) template tags must contain a value!"); + return -1; + } while (buf.buf[0] == ' ' || buf.buf[0] == '\t'); + /* test for tag type and route to handler */ + switch (id) { + case '/': return fio___mustache_parse_section_end(p, buf); + case '#': return fio___mustache_parse_section_start(p, buf, 0); + case '^': return fio___mustache_parse_section_start(p, buf, 1); + case '>': return fio___mustache_parse_partial(p, buf); + case '!': return fio___mustache_parse_comment(p, buf); + case '=': /* fall through */ + case '{': + do /* it is known that (buf.buf ends as '=' or '}')*/ + --buf.len; + while (buf.buf[buf.len - 1] == ' ' || buf.buf[buf.len - 1] == '\t'); + + if (id == '=') + return fio___mustache_parse_set_delim(p, buf); /* fall through */ + default: /* raw var */ return fio___mustache_parse_var_name(p, buf, 1); + } +} + +/* ***************************************************************************** +File Consumer parser +***************************************************************************** */ + +FIO_SFUNC int fio___mustache_parse_block(fio___mustache_parser_s *p) { + int r = 0; + const char *end = p->forwards.buf + p->forwards.len; + fio_buf_info_s tag; + p->backwards = FIO_BUF_INFO2(p->forwards.buf, 0); + p->root = fio_bstr_reserve(p->root, p->forwards.len); + /* consume each line (in case it's a stand alone line) */ + for (;;) { + p->backwards.len = (size_t)(p->forwards.buf - p->backwards.buf); + if (p->forwards.buf >= end) + break; + if (FIO_UNLIKELY(*p->forwards.buf == p->delim.in.buf[0] && + p->delim.in.cmp(p->forwards.buf, p->delim.in.buf))) { + /* tag started */ + p->forwards.buf += p->delim.in.len; + tag = FIO_BUF_INFO2(p->forwards.buf, 0); + for (;;) { + if (p->forwards.buf + p->delim.out.len > end) + goto incomplete_tag_error; + if (p->forwards.buf[0] == p->delim.out.buf[0] && + p->delim.out.cmp(p->forwards.buf, p->delim.out.buf)) + break; + ++(p->forwards.buf); + } + /* advance tag ending when triple mustache is detected. */ + p->forwards.buf += + ((p->forwards.buf + p->delim.out.len) < end && + p->forwards.buf[0] == '}' && + p->delim.out.cmp(p->forwards.buf + 1, p->delim.out.buf)); + /* finalize tag */ + tag.len = p->forwards.buf - tag.buf; + if (!tag.len) + goto empty_tag_error; + p->forwards.buf += p->delim.out.len; + p->forwards.len = (size_t)(end - p->forwards.buf); + p->dirty |= (unsigned)(p->forwards.buf[0] && p->forwards.buf[0] != '\r' && + p->forwards.buf[0] != '\n'); + if (p->dirty && p->backwards.len) { /* not stand-alone, add txt */ + fio___mustache_parse_add_text(p, p->backwards); + p->backwards = FIO_BUF_INFO2((p->backwards.buf + p->backwards.len), 0); + } + if ((r = fio___mustache_parse_consume_tag(p, tag))) + goto done; + p->backwards = FIO_BUF_INFO2(p->forwards.buf, 0); + continue; + } + p->dirty = (unsigned)(p->forwards.buf[0] != '\n') & + (p->dirty | (unsigned)(p->forwards.buf[0] != ' ' && + p->forwards.buf[0] != '\t')); + ++p->forwards.buf; + if (p->backwards.len == ((1 << 16) - 2) || p->forwards.buf[-1] == '\n') { + p->backwards.len = p->forwards.buf - p->backwards.buf; + fio___mustache_parse_add_text(p, p->backwards); + p->backwards.buf = p->forwards.buf; + } + } + /* print leftover text? */ + if (p->backwards.len) + fio___mustache_parse_add_text(p, p->backwards); + +done: + r += ((r == -2) << 1); /* end-tag stop shouldn't propagate onward. */ + return r; +incomplete_tag_error: + FIO_LOG_ERROR("(mustache) template error, un-terminated {{tag}}:\n\t%.*s", + (int)(end - (p->backwards.buf + p->backwards.len) > 32 + ? (int)32 + : (int)(end - (p->backwards.buf + p->backwards.len))), + (p->backwards.buf + p->backwards.len)); + return (r = -1); +empty_tag_error: + FIO_LOG_ERROR("(mustache) template error, empty {{tag}}:\n\t%.*s", + (int)(end - (p->backwards.buf + p->backwards.len) > 32 + ? (int)32 + : (int)(end - (p->backwards.buf + p->backwards.len))), + (p->backwards.buf + p->backwards.len)); + return (r = -1); +} + +FIO_SFUNC int fio___mustache_parse_template_file(fio___mustache_parser_s *p) { + /* remove (possible) filename comment line */ + if (p->forwards.buf[0] == '@' && p->forwards.buf[1] == ':') { + char *pos = (char *)FIO_MEMCHR(p->forwards.buf, '\n', p->forwards.len); + if (!pos) + return 0; /* done with file... though nothing happened. */ + } + /* consume (possible) YAML front matter */ + if (p->forwards.buf[0] == '-' && p->forwards.buf[1] == '-' && + p->forwards.buf[2] == '-' && + (p->forwards.buf[3] == '\n' || p->forwards.buf[3] == '\r')) { + const char *end = p->forwards.buf + p->forwards.len; + const char *pos = p->forwards.buf; + for (;;) { + pos = (const char *)FIO_MEMCHR(pos, '\n', end - pos); + if (!pos) + return 0; /* done with file... though nothing happened. */ + ++pos; + if (pos[0] == '-' && pos[1] == '-' && pos[2] == '-' && + (pos[3] == '\n' || pos[3] == '\r' || !pos[3])) { + pos += 4; + pos += pos[0] == '\n'; + break; + } + } + p->args->on_yaml_front_matter( + FIO_BUF_INFO2(p->forwards.buf, (size_t)(pos - p->forwards.buf)), + p->args->udata); + p->forwards.len = (size_t)(pos - p->forwards.buf); + p->forwards.buf = (char *)pos; + } + return fio___mustache_parse_block(p); +} + +/* ***************************************************************************** +Default functions +***************************************************************************** */ +FIO_SFUNC fio_buf_info_s fio___mustache_dflt_load_file_data(fio_buf_info_s fn, + void *udata) { + char *data = fio_bstr_readfile(NULL, fn.buf, 0, 0); + return fio_bstr_buf(data); + (void)udata; +} + +FIO_SFUNC void fio___mustache_dflt_free_file_data(fio_buf_info_s d, + void *udata) { + fio_bstr_free(d.buf); + (void)udata; +} + +FIO_SFUNC void fio___mustache_dflt_on_yaml_front_matter(fio_buf_info_s y, + void *udata) { + (void)y, (void)udata; +} + +FIO_SFUNC void *fio___mustache_dflt_write_text(void *u, fio_buf_info_s txt) { + return (void *)fio_bstr_write((char *)u, txt.buf, txt.len); +} + +FIO_SFUNC void *fio___mustache_dflt_write_text_escaped(void *u, + fio_buf_info_s raw) { + return (void *)fio_bstr_write_html_escape((char *)u, raw.buf, raw.len); +} + +/* callback should return a new context pointer with the value of `name`. */ +FIO_SFUNC void *fio___mustache_dflt_get_var(void *ctx, fio_buf_info_s name) { + return NULL; + (void)ctx, (void)name; +} + +/* if context is an Array, should return its length. */ +FIO_SFUNC size_t fio___mustache_dflt_array_length(void *ctx) { + return 0; + (void)ctx; +} + +/* if context is an Array, should return a context pointer @ index. */ +FIO_SFUNC void *fio___mustache_dflt_get_var_index(void *ctx, size_t index) { + return NULL; + (void)ctx, (void)index; +} +/* should return the String value of context `var` as a `fio_buf_info_s`. */ +FIO_SFUNC fio_buf_info_s fio___mustache_dflt_var2str(void *var) { + return FIO_BUF_INFO2(NULL, 0); + (void)var; +} + +FIO_SFUNC int fio___mustache_dflt_var_is_truthful(void *v) { return !!v; } + +FIO_IFUNC void fio___mustache_dflt_release_var(void *ctx) { (void)ctx; } + +/* returns non-zero if `ctx` is a lambda and handles section manually. */ +FIO_SFUNC int fio___mustache_dflt_is_lambda( + void **udata, + void *ctx, + fio_buf_info_s raw_template_section) { + return 0; + (void)raw_template_section, (void)ctx, (void)udata; +} + +/* ***************************************************************************** +Public API +***************************************************************************** */ + +void fio_mustache_load___(void); /* IDE Marker */ +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC fio_mustache_s *fio_mustache_load FIO_NOOP(fio_mustache_load_args_s a) { + uint8_t should_free_data = 0; + fio_buf_info_s base_path = {0}; + fio___mustache_parser_s parser = {0}; + if (!a.load_file_data && !a.free_file_data) { + a.load_file_data = fio___mustache_dflt_load_file_data; + a.free_file_data = fio___mustache_dflt_free_file_data; + } + if (!a.filename.buf && !a.data.buf) + return NULL; + if (!a.on_yaml_front_matter) + a.on_yaml_front_matter = fio___mustache_dflt_on_yaml_front_matter; + if (a.filename.buf) { + fio_filename_s pathname; + FIO_STR_INFO_TMP_VAR(fn, (PATH_MAX | 2096)); + if (!a.filename.len) + a.filename.len = FIO_STRLEN(a.filename.buf); + if (a.filename.buf[a.filename.len]) + fio_string_write(&fn, NULL, a.filename.buf, a.filename.len); + else + fn = FIO_BUF2STR_INFO(a.filename); + if (!a.data.buf && fn.buf) { + a.data = a.load_file_data(FIO_STR2BUF_INFO(fn), a.udata); + if (!a.data.buf) + return NULL; + should_free_data = 1; + } + pathname = fio_filename_parse2(a.filename.buf, a.filename.len); + base_path = pathname.folder; + } + parser.args = &a; + parser.root = NULL; + parser.delim = fio___mustache_delimiter_init(); + parser.depth = 0; + parser.fname = a.filename; + parser.forwards = a.data; + parser.path = base_path; + if (fio___mustache_parse_template_file(&parser)) { /* parser failed(!) */ + fio_bstr_free(parser.root); + parser.root = NULL; + } + /* No need to write FIO___MUSTACHE_I_STACK_POP, as the string ends with NUL */ + if (should_free_data) + a.free_file_data(a.data, a.udata); + if (parser.root) + FIO_LEAK_COUNTER_ON_ALLOC(fio_mustache_s); + return (fio_mustache_s *)parser.root; +} + +/* Frees the mustache template object (or reduces it's reference count). */ +SFUNC void fio_mustache_free(fio_mustache_s *m) { + if (!m) + return; + FIO_LEAK_COUNTER_ON_FREE(fio_mustache_s); + fio_bstr_free((char *)m); +} + +/** Increases the mustache template's reference count. */ +SFUNC fio_mustache_s *fio_mustache_dup(fio_mustache_s *m) { + if (!m) + return m; + FIO_LEAK_COUNTER_ON_ALLOC(fio_mustache_s); + return (fio_mustache_s *)fio_bstr_copy((char *)m); +} + +void fio_mustache_build___(void); /* IDE marker */ +/** Builds the template, returning the final value of `udata` (or NULL). */ +SFUNC void *fio_mustache_build FIO_NOOP(fio_mustache_s *m, + fio_mustache_bargs_s args) { + if (!m) + return args.udata; + if (!args.write_text && !args.write_text_escaped) { + args.write_text = fio___mustache_dflt_write_text; + args.write_text_escaped = fio___mustache_dflt_write_text_escaped; + } + FIO_ASSERT(args.write_text_escaped && args.write_text, + "(mustache) fio_mustache_build requires both writer " + "callbacks!\n\t\t(or none, for a fio_bstr_s return)"); + if (!args.get_var) + args.get_var = fio___mustache_dflt_get_var; + if (!args.array_length) + args.array_length = fio___mustache_dflt_array_length; + if (!args.get_var_index) + args.get_var_index = fio___mustache_dflt_get_var_index; + if (!args.var2str) + args.var2str = fio___mustache_dflt_var2str; + if (!args.var_is_truthful) + args.var_is_truthful = fio___mustache_dflt_var_is_truthful; + if (!args.release_var) + args.release_var = fio___mustache_dflt_release_var; + if (!args.is_lambda) + args.is_lambda = fio___mustache_dflt_is_lambda; + + fio___mustache_bldr_s builder = { + .root = (char *)m, + .ctx = args.ctx, + .args = &args, + }; + return fio___mustache_build_section((char *)m, builder); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_MODULE_PTR +#undef FIO_MUSTACHE +#undef FIO___UNTAG_T +#endif /* FIO_MUSTACHE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_CRYPTO_CORE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + A Template for New Types / Modules + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_CRYPTO_CORE) && !defined(H___FIO_CRYPTO_CORE___H) +#define H___FIO_CRYPTO_CORE___H + +/* ***************************************************************************** +Module Implementation - inlined functions +***************************************************************************** */ + +/* ***************************************************************************** +Module Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_CRYPTO_CORE +#endif /* FIO_CRYPTO_CORE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_CHACHA /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + ChaCha20 & Poly1305 + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_CHACHA) && !defined(H___FIO_CHACHA___H) +#define H___FIO_CHACHA___H 1 + +/* ***************************************************************************** +ChaCha20Poly1305 API +***************************************************************************** */ + +/** + * Performs an in-place encryption of `data` using ChaCha20 with additional + * data, producing a 16 byte message authentication code (MAC) using Poly1305. + * + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nonce` MUST point to a 96 bit long memory address (12 Bytes). + * * `ad` MAY be omitted, will NOT be encrypted. + * * `data` MAY be omitted, WILL be encrypted. + * * `mac` MUST point to a buffer with (at least) 16 available bytes. + */ +SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nonce); + +/** + * Performs an in-place decryption of `data` using ChaCha20 after authenticating + * the message authentication code (MAC) using Poly1305. + * + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nonce` MUST point to a 96 bit long memory address (12 Bytes). + * * `ad` MAY be omitted ONLY IF originally omitted. + * * `data` MAY be omitted, WILL be decrypted. + * * `mac` MUST point to a buffer where the 16 byte MAC is placed. + * + * Returns `-1` on error (authentication failed). + */ +SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nonce); + +/* ***************************************************************************** +Using ChaCha20 and Poly1305 separately +***************************************************************************** */ + +/** + * Performs an in-place encryption/decryption of `data` using ChaCha20. + * + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nonce` MUST point to a 96 bit long memory address (12 Bytes). + * * `counter` is the block counter, usually 1 unless `data` is mid-cyphertext. + */ +SFUNC void fio_chacha20(void *restrict data, + size_t len, + const void *key, + const void *nonce, + uint32_t counter); + +/** + * Given a Poly1305 256bit (32 byte) key, writes the authentication code for the + * poly message and additional data into `mac_dest`. + * + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + */ +SFUNC void fio_poly1305_auth(void *restrict mac_dest, + const void *key256bits, + void *restrict message, + size_t len, + const void *additional_data, + size_t additional_data_len); + +/* ***************************************************************************** +ChaCha20Poly1305 Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Poly1305 (authentication) +Prime 2^130-5 = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +The math is mostly copied from: https://github.com/floodyberry/poly1305-donna +***************************************************************************** */ +/* + * Math copied from https://github.com/floodyberry/poly1305-donna + * + * With thanks to Andrew Moon. + */ +typedef struct { + /* r (cycle key addition) is 128 bits */ + uint64_t r[3]; + /* s (final key addition) is 128 bits */ + uint64_t s[2]; + /* Accumulator should not exceed 131 bits at the end of every cycle. */ + uint64_t a[3]; +} FIO_ALIGN(16) fio___poly_s; + +FIO_IFUNC fio___poly_s fio___poly_init(const void *key256b) { + static const uint64_t defkey[4] = {0}; + if (!key256b) + key256b = (const void *)defkey; + uint64_t t0, t1; + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + t0 = fio_buf2u64_le((uint8_t *)key256b + 0); + t1 = fio_buf2u64_le((uint8_t *)key256b + 8); + fio___poly_s pl = { + .r = + { + ((t0)&0xffc0fffffff), + (((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff), + (((t1 >> 24)) & 0x00ffffffc0f), + }, + .s = + { + fio_buf2u64_le(((uint8_t *)key256b + 16)), + fio_buf2u64_le(((uint8_t *)key256b + 24)), + }, + }; + return pl; +} +FIO_IFUNC void fio___poly_consume128bit(fio___poly_s *pl, + const void *msg, + uint64_t is_full) { + uint64_t r0, r1, r2; + uint64_t s1, s2; + uint64_t a0, a1, a2; + uint64_t c; + uint64_t d0[2], d1[2], d2[2], d[2]; + + r0 = pl->r[0]; + r1 = pl->r[1]; + r2 = pl->r[2]; + + a0 = pl->a[0]; + a1 = pl->a[1]; + a2 = pl->a[2]; + + s1 = r1 * (5 << 2); + s2 = r2 * (5 << 2); + + { + uint64_t t0, t1; + t0 = fio_buf2u64_le(msg); + t1 = fio_buf2u64_le(((uint8_t *)msg + 8)); + /* a += msg */ + a0 += ((t0)&0xFFFFFFFFFFF); + a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF); + a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) | (is_full << 40); + } + + /* a *= r */ + d0[0] = fio_math_mulc64(a0, r0, d0 + 1); + d[0] = fio_math_mulc64(a1, s2, d + 1); + d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); + d0[1] += d[1] + c; + + d[0] = fio_math_mulc64(a2, s1, d + 1); + d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); + d0[1] += d[1] + c; + + d1[0] = fio_math_mulc64(a0, r1, d1 + 1); + d[0] = fio_math_mulc64(a1, r0, d + 1); + d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); + d1[1] += d[1] + c; + + d[0] = fio_math_mulc64(a2, s2, d + 1); + d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); + d1[1] += d[1] + c; + + d2[0] = fio_math_mulc64(a0, r2, d2 + 1); + d[0] = fio_math_mulc64(a1, r1, d + 1); + d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); + d2[1] += d[1] + c; + + d[0] = fio_math_mulc64(a2, r0, d + 1); + d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); + d2[1] += d[1] + c; + + /* (partial) a %= p */ + c = (d0[0] >> 44) | (d0[1] << 20); + a0 = d0[0] & 0xfffffffffff; + d1[0] = fio_math_addc64(d1[0], c, 0, &c); + d1[1] += c; + + c = (d1[0] >> 44) | (d1[1] << 20); + a1 = d1[0] & 0xfffffffffff; + d2[0] = fio_math_addc64(d2[0], c, 0, &c); + d2[1] += c; + + c = (d2[0] >> 42) | (d2[1] << 22); + a2 = d2[0] & 0x3ffffffffff; + a0 += c * 5; + c = a0 >> 44; + a0 = a0 & 0xfffffffffff; + a1 += c; + + pl->a[0] = a0; + pl->a[1] = a1; + pl->a[2] = a2; +} + +FIO_IFUNC void fio___poly_finilize(fio___poly_s *pl) { + uint64_t a0, a1, a2, c; + uint64_t g0, g1, g2; + uint64_t t0, t1; + + /* fully carry a */ + a0 = pl->a[0]; + a1 = pl->a[1]; + a2 = pl->a[2]; + + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += c; + c = (a2 >> 42); + a2 &= 0x3FFFFFFFFFF; + a0 += c * 5; + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += c; + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += c; + c = (a2 >> 42); + a2 &= 0x3FFFFFFFFFF; + a0 += c * 5; + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += c; + + /* compute a + -p */ + g0 = a0 + 5; + c = (g0 >> 44); + g0 &= 0xFFFFFFFFFFF; + g1 = a1 + c; + c = (g1 >> 44); + g1 &= 0xFFFFFFFFFFF; + g2 = a2 + c - ((uint64_t)1 << 42); + + /* select h if h < p, or h + -p if h >= p */ + c = (g2 >> ((sizeof(uint64_t) * 8) - 1)) - 1; + g0 &= c; + g1 &= c; + g2 &= c; + c = ~c; + a0 = (a0 & c) | g0; + a1 = (a1 & c) | g1; + a2 = (a2 & c) | g2; + + /* a = (a + Poly S key) */ + t0 = pl->s[0]; + t1 = pl->s[1]; + + a0 += ((t0)&0xFFFFFFFFFFF); + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF) + c; + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) + c; + a2 &= 0x3FFFFFFFFFF; + + /* mac = a % (2^128) */ + a0 = ((a0) | (a1 << 44)); + a1 = ((a1 >> 20) | (a2 << 24)); + pl->a[0] = a0; + pl->a[1] = a1; +} + +FIO_IFUNC void fio___poly_consume_msg(fio___poly_s *pl, + uint8_t *msg, + size_t len) { + /* read 16 byte blocks */ + uint64_t n[2]; + for (size_t i = 31; i < len; i += 32) { + fio___poly_consume128bit(pl, msg, 1); + fio___poly_consume128bit(pl, msg + 16, 1); + msg += 32; + } + if ((len & 16)) { + fio___poly_consume128bit(pl, msg, 1); + msg += 16; + } + if ((len & 15)) { + n[0] = 0; + n[1] = 0; + fio_memcpy15x(n, msg, len); + n[0] = fio_ltole64(n[0]); + n[1] = fio_ltole64(n[1]); + ((uint8_t *)n)[len & 15] = 0x01; + fio___poly_consume128bit(pl, (void *)n, 0); + } +} + +/* Given a Poly1305 key, writes a MAC into `mac_dest`. */ +SFUNC void fio_poly1305_auth(void *restrict mac, + const void *key, + void *restrict msg, + size_t len, + const void *ad, + size_t ad_len) { + fio___poly_s pl = fio___poly_init(key); + fio___poly_consume_msg(&pl, (uint8_t *)ad, ad_len); + fio___poly_consume_msg(&pl, (uint8_t *)msg, len); + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); +} + +/* ***************************************************************************** +ChaCha20 (encryption) +***************************************************************************** */ + +#define FIO___CHACHA_VROUND(count, a, b, c, d) \ + for (size_t i = 0; i < count; ++i) { \ + a[i] += b[i]; \ + d[i] ^= a[i]; \ + d[i] = (d[i] << 16) | (d[i] >> (32 - 16)); \ + c[i] += d[i]; \ + b[i] ^= c[i]; \ + b[i] = (b[i] << 12) | (b[i] >> (32 - 12)); \ + a[i] += b[i]; \ + d[i] ^= a[i]; \ + d[i] = (d[i] << 8) | (d[i] >> (32 - 8)); \ + c[i] += d[i]; \ + b[i] ^= c[i]; \ + b[i] = (b[i] << 7) | (b[i] >> (32 - 7)); \ + } + +FIO_IFUNC fio_u512 fio___chacha_init(const void *key, + const void *nonce, + uint32_t counter) { + fio_u512 o = { + .u32 = + { + // clang-format off + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + fio_buf2u32_le(key), + fio_buf2u32_le((uint8_t *)key + 4), + fio_buf2u32_le((uint8_t *)key + 8), + fio_buf2u32_le((uint8_t *)key + 12), + fio_buf2u32_le((uint8_t *)key + 16), + fio_buf2u32_le((uint8_t *)key + 20), + fio_buf2u32_le((uint8_t *)key + 24), + fio_buf2u32_le((uint8_t *)key + 28), + counter, + fio_buf2u32_le(nonce), + fio_buf2u32_le((uint8_t *)nonce + 4), + fio_buf2u32_le((uint8_t *)nonce + 8), + }, // clang-format on + }; + return o; +} + +FIO_SFUNC void fio___chacha_vround20(const fio_u512 c, uint8_t *restrict data) { + uint32_t v[16]; + for (size_t i = 0; i < 16; ++i) { + v[i] = c.u32[i]; + } + for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ + FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); + fio_u32x4_reshuffle((v + 4), 1, 2, 3, 0); + fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); + fio_u32x4_reshuffle((v + 12), 3, 0, 1, 2); + FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); + fio_u32x4_reshuffle((v + 4), 3, 0, 1, 2); + fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); + fio_u32x4_reshuffle((v + 12), 1, 2, 3, 0); + } + for (size_t i = 0; i < 16; ++i) { + v[i] += c.u32[i]; + } + +#if __BIG_ENDIAN__ + for (size_t i = 0; i < 16; ++i) { + v[i] = fio_bswap32(v[i]); + } +#endif + { + uint32_t d[16]; + fio_memcpy64(d, data); + for (size_t i = 0; i < 16; ++i) { + d[i] ^= v[i]; + } + fio_memcpy64(data, d); + } +} + +FIO_SFUNC void fio___chacha_vround20x2(fio_u512 c, uint8_t *restrict data) { + uint32_t v[32]; + for (size_t i = 0; i < 16; ++i) { + v[i + (i & (4 | 8))] = c.u32[i]; + v[i + 4 + (i & (4 | 8))] = c.u32[i]; + } + ++v[28]; + for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ + FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); + fio_u32x8_reshuffle((v + 8), 1, 2, 3, 0, 5, 6, 7, 4); + fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); + fio_u32x8_reshuffle((v + 24), 3, 0, 1, 2, 7, 4, 5, 6); + FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); + fio_u32x8_reshuffle((v + 8), 3, 0, 1, 2, 7, 4, 5, 6); + fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); + fio_u32x8_reshuffle((v + 24), 1, 2, 3, 0, 5, 6, 7, 4); + } + for (size_t i = 0; i < 16; ++i) { + v[i + (i & (4 | 8))] += c.u32[i]; + v[i + 4 + (i & (4 | 8))] += c.u32[i]; + } + ++v[28]; + +#if __BIG_ENDIAN__ + for (size_t i = 0; i < 32; ++i) { + v[i] = fio_bswap32(v[i]); + } +#endif + { + fio_u32x8_reshuffle((v + 4), 4, 5, 6, 7, 0, 1, 2, 3); + fio_u32x8_reshuffle((v + 20), 4, 5, 6, 7, 0, 1, 2, 3); + uint32_t d[8]; + fio_memcpy32(d, data); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[i]; + } + fio_memcpy32(data, d); + + fio_memcpy32(d, data + 32); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[16 + i]; + } + fio_memcpy32(data + 32, d); + + fio_memcpy32(d, data + 64); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[8 + i]; + } + fio_memcpy32(data + 64, d); + + fio_memcpy32(d, data + 96); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[24 + i]; + } + fio_memcpy32(data + 96, d); + } +} + +SFUNC void fio_chacha20(void *restrict data, + size_t len, + const void *key, + const void *nonce, + uint32_t counter) { + fio_u512 c = fio___chacha_init(key, nonce, counter); + for (size_t pos = 127; pos < len; pos += 128) { + fio___chacha_vround20x2(c, (uint8_t *)data); + c.u32[12] += 2; /* block counter */ + data = (void *)((uint8_t *)data + 128); + } + if ((len & 64)) { + fio___chacha_vround20(c, (uint8_t *)data); + data = (void *)((uint8_t *)data + 64); + ++c.u32[12]; + } + if ((len & 63)) { + fio_u512 dest; /* no need to initialize, junk data disregarded. */ + fio_memcpy63x(dest.u64, data, len); + fio___chacha_vround20(c, dest.u8); + fio_memcpy63x(data, dest.u64, len); + } +} + +/* ***************************************************************************** +ChaCha20Poly1305 Encryption with Authentication +***************************************************************************** */ + +FIO_IFUNC fio_u512 fio___chacha20_mixround(fio_u512 c) { + fio_u512 k = {.u64 = {0}}; + fio___chacha_vround20(c, k.u8); + return k; +} +SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nonce) { + fio_u512 c = fio___chacha_init(key, nonce, 0); + fio___poly_s pl; + { + fio_u512 c2 = fio___chacha20_mixround(c); + pl = fio___poly_init(&c2); + } + ++c.u32[12]; /* block counter */ + for (size_t i = 31; i < adlen; i += 32) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); + ad = (void *)((uint8_t *)ad + 32); + } + if (adlen & 16) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + ad = (void *)((uint8_t *)ad + 16); + } + if (adlen & 15) { + uint64_t tmp[2] = {0}; /* 16 byte pad */ + fio_memcpy15x(tmp, ad, adlen); + fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); + } + for (size_t i = 127; i < len; i += 128) { + fio___chacha_vround20x2(c, (uint8_t *)data); + fio___poly_consume128bit(&pl, data, 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 64), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 80), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 96), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 112), 1); + c.u32[12] += 2; /* block counter */ + data = (void *)((uint8_t *)data + 128); + } + if ((len & 64)) { + fio___chacha_vround20(c, (uint8_t *)data); + fio___poly_consume128bit(&pl, data, 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); + ++c.u32[12]; /* block counter */ + data = (void *)((uint8_t *)data + 64); + } + if ((len & 63)) { + fio_u512 dest; + fio_memcpy63x(dest.u8, data, len); + fio___chacha_vround20(c, dest.u8); + fio_memcpy63x(data, dest.u8, len); + uint8_t *p = dest.u8; + if ((len & 32)) { + fio___poly_consume128bit(&pl, p, 1); + fio___poly_consume128bit(&pl, (p + 16), 1); + p += 32; + } + if ((len & 16)) { + fio___poly_consume128bit(&pl, p, 1); + p += 16; + } + if ((len & 15)) { + /* zero out poly padding */ + for (size_t i = (len & 15UL); i < 16; i++) + p[i] = 0; + fio___poly_consume128bit(&pl, p, 1); + } + } + { + uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; + fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); + } + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); +} + +SFUNC void fio_chacha20_poly1305_auth(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nonce) { + fio___poly_s pl; + { + fio_u512 c = fio___chacha_init(key, nonce, 0); + c = fio___chacha20_mixround(c); /* computes poly1305 key */ + pl = fio___poly_init(&c); + } + for (size_t i = 31; i < adlen; i += 32) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); + ad = (void *)((uint8_t *)ad + 32); + } + if (adlen & 16) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + ad = (void *)((uint8_t *)ad + 16); + } + if (adlen & 15) { + uint64_t tmp[2] = {0}; /* 16 byte pad */ + fio_memcpy15x(tmp, ad, adlen); + fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); + } + fio___poly_consume_msg(&pl, (uint8_t *)data, (len & (~15ULL))); + if ((len & 15)) { + fio_u128 dest = {0}; /* 16 byte pad */ + fio_memcpy15x(dest.u64, (uint8_t *)data + (len & (~15ULL)), len); + fio___poly_consume128bit(&pl, (uint8_t *)(dest.u64), 1); + } + { + uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; + fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); + } + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); +} + +SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nonce) { + uint64_t auth[2]; + fio_chacha20_poly1305_auth(&auth, data, len, ad, adlen, key, nonce); + if (((auth[0] ^ fio_buf2u64u(mac)) | + (auth[1] ^ fio_buf2u64u(((char *)mac + 8))))) + return -1; + fio_chacha20(data, len, key, nonce, 1); + return 0; +} +/* ***************************************************************************** +Module Cleanup +***************************************************************************** +*/ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_CHACHA +#endif /* FIO_CHACHA */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SHA1 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + SHA 1 + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SHA1) && !defined(H___FIO_SHA1___H) +#define H___FIO_SHA1___H +/* ***************************************************************************** +SHA 1 +***************************************************************************** */ + +/** The data type containing the SHA1 digest (result). */ +typedef union { +#ifdef __SIZEOF_INT128__ + __uint128_t align__; +#else + uint64_t align__; +#endif + uint32_t v[5]; + uint8_t digest[20]; +} fio_sha1_s; + +/** + * A simple, non streaming, implementation of the SHA1 hashing algorithm. + * + * Do NOT use - SHA1 is broken... but for some reason some protocols still + * require it's use (i.e., WebSockets), so it's here for your convenience. + */ +SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len); + +/** Returns the digest length of SHA1 in bytes (20 bytes) */ +FIO_IFUNC size_t fio_sha1_len(void); + +/** Returns the 20 Byte long digest of a SHA1 object. */ +FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s); + +/* ***************************************************************************** +SHA 1 Implementation - inlined static functions +***************************************************************************** */ + +/** returns the digest length of SHA1 in bytes */ +FIO_IFUNC size_t fio_sha1_len(void) { return 20; } + +/** returns the digest of a SHA1 object. */ +FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s) { return s->digest; } + +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_IFUNC void fio___sha1_round512(uint32_t *old, /* state */ + uint32_t *w /* 16 words */) { +#if FIO___HAS_ARM_INTRIN + /* Code adjusted from: + * https://github.com/noloader/SHA-Intrinsics/blob/master/sha1-arm.c + * Credit to Jeffrey Walton. + */ + uint32x4_t w0, w1, w2, w3; + uint32x4_t t0, t1, v0, v_old; + uint32_t e0, e1, e_old; + e0 = e_old = old[4]; + v_old = vld1q_u32(old); + v0 = v_old; + + /* load to vectors */ + w0 = vld1q_u32(w); + w1 = vld1q_u32(w + 4); + w2 = vld1q_u32(w + 8); + w3 = vld1q_u32(w + 12); + /* make little endian */ + w0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w0))); + w1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w1))); + w2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w2))); + w3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w3))); + + t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); + t1 = vaddq_u32(w1, vdupq_n_u32(0x5A827999)); + + /* round: 0-3 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e0, t0); + t0 = vaddq_u32(w2, vdupq_n_u32(0x5A827999)); + w0 = vsha1su0q_u32(w0, w1, w2); + + /* round: 4-7 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e1, t1); + t1 = vaddq_u32(w3, vdupq_n_u32(0x5A827999)); + w0 = vsha1su1q_u32(w0, w3); + w1 = vsha1su0q_u32(w1, w2, w3); + + /* round: 8-11 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e0, t0); + t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); + w1 = vsha1su1q_u32(w1, w0); + w2 = vsha1su0q_u32(w2, w3, w0); + +#define FIO_SHA1_ROUND_(K, rn_fn, n, ni, n0, n1, n2, n3) \ + e##n = vsha1h_u32(vgetq_lane_u32(v0, 0)); \ + v0 = rn_fn(v0, e##ni, t##ni); \ + t##ni = vaddq_u32(w##n1, vdupq_n_u32(K)); \ + w##n2 = vsha1su1q_u32(w##n2, w##n1); \ + w##n3 = vsha1su0q_u32(w##n3, w##n0, w##n1); + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 1, 0, 1, 2, 3, 0) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 0, 1, 2, 3) + + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 1, 0, 1, 2, 3, 0) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 1, 2, 3, 0) + + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 1, 0, 1, 2, 3, 0) +#undef FIO_SHA1_ROUND_ + /* round: 68-71 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e1, t1); + t1 = vaddq_u32(w3, vdupq_n_u32(0xCA62C1D6)); + w0 = vsha1su1q_u32(w0, w3); + + /* round: 72-75 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e0, t0); + + /* round: 76-79 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e1, t1); + + /* combine and store */ + e0 += e_old; + v0 = vaddq_u32(v_old, v0); + vst1q_u32(old, v0); + old[4] = e0; + +#else /* !FIO___HAS_ARM_INTRIN portable implementation */ + + uint32_t v[8] = {0}; /* copy old state to new + reserve registers (8 not 6) */ + for (size_t i = 0; i < 5; ++i) + v[i] = old[i]; + + for (size_t i = 0; i < 16; ++i) /* convert read buffer to Big Endian */ + w[i] = fio_ntol32(w[i]); + +#define FIO___SHA1_ROUND4(K, F, i) \ + FIO___SHA1_ROUND((K), (F), i); \ + FIO___SHA1_ROUND((K), (F), i + 1); \ + FIO___SHA1_ROUND((K), (F), i + 2); \ + FIO___SHA1_ROUND((K), (F), i + 3); +#define FIO___SHA1_ROUND16(K, F, i) \ + FIO___SHA1_ROUND4((K), (F), i); \ + FIO___SHA1_ROUND4((K), (F), i + 4); \ + FIO___SHA1_ROUND4((K), (F), i + 8); \ + FIO___SHA1_ROUND4((K), (F), i + 12); +#define FIO___SHA1_ROUND20(K, F, i) \ + FIO___SHA1_ROUND16(K, F, i); \ + FIO___SHA1_ROUND4((K), (F), i + 16); + +#define FIO___SHA1_ROTATE_OLD(K, F, i) \ + v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ + v[4] = v[3]; \ + v[3] = v[2]; \ + v[2] = fio_lrot32(v[1], 30); \ + v[1] = v[0]; \ + v[0] = v[5]; + +#define FIO___SHA1_ROTATE(K, F, i) \ + v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ + v[1] = fio_lrot32(v[1], 30); \ + fio_u32x8_reshuffle(v, 5, 0, 1, 2, 3, 5, 6, 7); + +#define FIO___SHA1_CALC_WORD(i) \ + fio_lrot32( \ + (w[(i + 13) & 15] ^ w[(i + 8) & 15] ^ w[(i + 2) & 15] ^ w[(i)&15]), \ + 1); + +#define FIO___SHA1_ROUND(K, F, i) FIO___SHA1_ROTATE(K, F, i); + /* perform first 16 rounds with simple words as copied from data */ + FIO___SHA1_ROUND16(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 0); + +/* change round definition so now we compute the word's value per round */ +#undef FIO___SHA1_ROUND +#define FIO___SHA1_ROUND(K, F, i) \ + w[(i)&15] = FIO___SHA1_CALC_WORD(i); \ + FIO___SHA1_ROTATE(K, F, i); + + /* complete last 4 round from the first 20 round group */ + FIO___SHA1_ROUND4(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 16); + + /* remaining 20 round groups */ + FIO___SHA1_ROUND20(0x6ED9EBA1, (v[1] ^ v[2] ^ v[3]), 20); + FIO___SHA1_ROUND20(0x8F1BBCDC, ((v[1] & (v[2] | v[3])) | (v[2] & v[3])), 40); + FIO___SHA1_ROUND20(0xCA62C1D6, (v[1] ^ v[2] ^ v[3]), 60); + /* sum and store */ + for (size_t i = 0; i < 5; ++i) + old[i] += v[i]; + +#undef FIO___SHA1_ROTATE +#undef FIO___SHA1_ROTATE_OLD +#undef FIO___SHA1_CALC_WORD +#undef FIO___SHA1_ROUND +#undef FIO___SHA1_ROUND4 +#undef FIO___SHA1_ROUND16 +#undef FIO___SHA1_ROUND20 +#endif /* FIO___HAS_ARM_INTRIN */ +} +/** + * A simple, non streaming, implementation of the SHA1 hashing algorithm. + * + * Do NOT use - SHA1 is broken... but for some reason some protocols still + * require it's use (i.e., WebSockets), so it's here for your convinience. + */ +SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len) { + fio_sha1_s s FIO_ALIGN(16) = {.v = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}; + uint32_t vec[16] FIO_ALIGN(16); + + const uint8_t *buf = (const uint8_t *)data; + + for (size_t i = 63; i < len; i += 64) { + fio_memcpy64(vec, buf); + fio___sha1_round512(s.v, vec); + buf += 64; + } + for (size_t i = 0; i < 16; ++i) { + vec[i] = 0; + } + if ((len & 63)) { + uint32_t tbuf[16] = {0}; + fio_memcpy63x(tbuf, buf, len); + fio_memcpy64(vec, tbuf); + } + ((uint8_t *)vec)[(len & 63)] = 0x80; + + if ((len & 63) > 55) { + fio___sha1_round512(s.v, vec); + for (size_t i = 0; i < 16; ++i) { + vec[i] = 0; + } + } + len <<= 3; + len = fio_lton64(len); + vec[14] = (uint32_t)(len & 0xFFFFFFFF); + vec[15] = (uint32_t)(len >> 32); + fio___sha1_round512(s.v, vec); + for (size_t i = 0; i < 5; ++i) { + s.v[i] = fio_ntol32(s.v[i]); + } + return s; +} + +/** HMAC-SHA1, resulting in a 20 byte authentication code. */ +SFUNC fio_sha1_s fio_sha1_hmac(const void *key, + uint64_t key_len, + const void *msg, + uint64_t msg_len) { + fio_sha1_s inner FIO_ALIGN(16) = {.v = + { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}, + outer FIO_ALIGN(16) = {.v = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}; + fio_u512 v = fio_u512_init64(0), k = fio_u512_init64(0); + const uint8_t *buf = (const uint8_t *)msg; + + /* copy key */ + if (key_len > 64) + goto key_too_long; + if (key_len == 64) + fio_memcpy64(k.u8, key); + else + fio_memcpy63x(k.u8, key, key_len); + /* prepare inner key */ + for (size_t i = 0; i < 8; ++i) + k.u64[i] ^= (uint64_t)0x3636363636363636ULL; + + /* hash inner key block */ + fio___sha1_round512(inner.v, k.u32); + /* consume data */ + for (size_t i = 63; i < msg_len; i += 64) { + fio_memcpy64(v.u8, buf); + fio___sha1_round512(inner.v, v.u32); + buf += 64; + } + /* finalize temporary hash */ + if ((msg_len & 63)) { + v = fio_u512_init64(0); + fio_memcpy63x(v.u8, buf, msg_len); + } + v.u8[(msg_len & 63)] = 0x80; + if ((msg_len & 63) > 55) { + fio___sha1_round512(inner.v, v.u32); + v = fio_u512_init64(0); + } + msg_len += 64; /* add the 64 byte inner key to the length count */ + msg_len <<= 3; + msg_len = fio_lton64(msg_len); + v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFFUL); + v.u32[15] = (uint32_t)(msg_len >> 32); + fio___sha1_round512(inner.v, v.u32); + for (size_t i = 0; i < 5; ++i) + inner.v[i] = fio_ntol32(inner.v[i]); + + /* switch key to outer key */ + for (size_t i = 0; i < 8; ++i) + k.u64[i] ^= + ((uint64_t)0x3636363636363636ULL ^ (uint64_t)0x5C5C5C5C5C5C5C5CULL); + + /* hash outer key block */ + fio___sha1_round512(outer.v, k.u32); + /* hash inner (temporary) hash result and finalize */ + v = fio_u512_init64(0); + for (size_t i = 0; i < 5; ++i) + v.u32[i] = inner.v[i]; + v.u8[20] = 0x80; + msg_len = ((64U + 20U) << 3); + msg_len = fio_lton64(msg_len); + v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFF); + v.u32[15] = (uint32_t)(msg_len >> 32); + fio___sha1_round512(outer.v, v.u32); + for (size_t i = 0; i < 5; ++i) + outer.v[i] = fio_ntol32(outer.v[i]); + + return outer; + +key_too_long: + inner = fio_sha1(key, key_len); + return fio_sha1_hmac(inner.digest, 20, msg, msg_len); +} +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_SHA1 */ +#undef FIO_SHA1 +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SHA2 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + SHA 2 + SHA-256 / SHA-512 and variations + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SHA2) && !defined(H___FIO_SHA2___H) +#define H___FIO_SHA2___H +/* ***************************************************************************** +SHA 2 API +***************************************************************************** */ + +/** Streaming SHA-256 type. */ +typedef struct { + fio_u256 hash; + fio_u512 cache; + uint64_t total_len; +} fio_sha256_s; + +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len); + +/** initializes a fio_u256 so the hash can consume streaming data. */ +FIO_IFUNC fio_sha256_s fio_sha256_init(void); +/** Feed data into the hash */ +SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len); +/** finalizes a fio_u256 with the SHA 256 hash. */ +SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h); + +/** Streaming SHA-512 type. */ +typedef struct { + fio_u512 hash; + fio_u1024 cache; + uint64_t total_len; +} fio_sha512_s; + +/** A simple, non streaming, implementation of the SHA-512 hashing algorithm. */ +FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len); + +/** initializes a fio_u512 so the hash can consume streaming data. */ +FIO_IFUNC fio_sha512_s fio_sha512_init(void); +/** Feed data into the hash */ +SFUNC void fio_sha512_consume(fio_sha512_s *h, const void *data, uint64_t len); +/** finalizes a fio_u512 with the SHA 512 hash. */ +SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h); + +/* ***************************************************************************** +Implementation - static / inline functions. +***************************************************************************** */ + +/** initializes a fio_u256 so the hash can be consumed. */ +FIO_IFUNC fio_sha256_s fio_sha256_init(void) { + fio_sha256_s h = {.hash.u32 = {0x6A09E667ULL, + 0xBB67AE85ULL, + 0x3C6EF372ULL, + 0xA54FF53AULL, + 0x510E527FULL, + 0x9B05688CULL, + 0x1F83D9ABULL, + 0x5BE0CD19ULL}}; + return h; +} + +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len) { + fio_sha256_s h = fio_sha256_init(); + fio_sha256_consume(&h, data, len); + return fio_sha256_finalize(&h); +} + +/** initializes a fio_u256 so the hash can be consumed. */ +FIO_IFUNC fio_sha512_s fio_sha512_init(void) { + fio_sha512_s h = {.hash.u64 = {0x6A09E667F3BCC908ULL, + 0xBB67AE8584CAA73BULL, + 0x3C6EF372FE94F82BULL, + 0xA54FF53A5F1D36F1ULL, + 0x510E527FADE682D1ULL, + 0x9B05688C2B3E6C1FULL, + 0x1F83D9ABFB41BD6BULL, + 0x5BE0CD19137E2179ULL}}; + return h; +} + +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len) { + fio_sha512_s h = fio_sha512_init(); + fio_sha512_consume(&h, data, len); + return fio_sha512_finalize(&h); +} + +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Implementation - SHA-256 +***************************************************************************** */ + +FIO_IFUNC void fio___sha256_round(fio_u256 *h, const uint8_t *block) { + const uint32_t sha256_consts[64] = { + 0x428A2F98ULL, 0x71374491ULL, 0xB5C0FBCFULL, 0xE9B5DBA5ULL, 0x3956C25BULL, + 0x59F111F1ULL, 0x923F82A4ULL, 0xAB1C5ED5ULL, 0xD807AA98ULL, 0x12835B01ULL, + 0x243185BEULL, 0x550C7DC3ULL, 0x72BE5D74ULL, 0x80DEB1FEULL, 0x9BDC06A7ULL, + 0xC19BF174ULL, 0xE49B69C1ULL, 0xEFBE4786ULL, 0x0FC19DC6ULL, 0x240CA1CCULL, + 0x2DE92C6FULL, 0x4A7484AAULL, 0x5CB0A9DCULL, 0x76F988DAULL, 0x983E5152ULL, + 0xA831C66DULL, 0xB00327C8ULL, 0xBF597FC7ULL, 0xC6E00BF3ULL, 0xD5A79147ULL, + 0x06CA6351ULL, 0x14292967ULL, 0x27B70A85ULL, 0x2E1B2138ULL, 0x4D2C6DFCULL, + 0x53380D13ULL, 0x650A7354ULL, 0x766A0ABBULL, 0x81C2C92EULL, 0x92722C85ULL, + 0xA2BFE8A1ULL, 0xA81A664BULL, 0xC24B8B70ULL, 0xC76C51A3ULL, 0xD192E819ULL, + 0xD6990624ULL, 0xF40E3585ULL, 0x106AA070ULL, 0x19A4C116ULL, 0x1E376C08ULL, + 0x2748774CULL, 0x34B0BCB5ULL, 0x391C0CB3ULL, 0x4ED8AA4AULL, 0x5B9CCA4FULL, + 0x682E6FF3ULL, 0x748F82EEULL, 0x78A5636FULL, 0x84C87814ULL, 0x8CC70208ULL, + 0x90BEFFFAULL, 0xA4506CEBULL, 0xBEF9A3F7ULL, 0xC67178F2ULL}; + + uint32_t v[8]; + for (size_t i = 0; i < 8; ++i) { + v[i] = h->u32[i]; + } + /* read data as an array of 16 big endian 32 bit integers. */ + uint32_t w[16] FIO_ALIGN(16); + fio_memcpy64(w, block); + for (size_t i = 0; i < 16; ++i) { + w[i] = fio_lton32(w[i]); /* no-op on big endien systems */ + } + +#define FIO___SHA256_ROUND_INNER_COMMON() \ + uint32_t t2 = \ + ((v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2])) + \ + (fio_rrot32(v[0], 2) ^ fio_rrot32(v[0], 13) ^ fio_rrot32(v[0], 22)); \ + fio_u32x8_reshuffle(v, 7, 0, 1, 2, 3, 4, 5, 6); \ + v[4] += t1; \ + v[0] = t1 + t2; + + for (size_t i = 0; i < 16; ++i) { + const uint32_t t1 = + v[7] + sha256_consts[i] + w[i] + ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + + (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); + FIO___SHA256_ROUND_INNER_COMMON(); + } + for (size_t i = 0; i < 48; ++i) { /* expand block */ + w[(i & 15)] = + (fio_rrot32(w[((i + 14) & 15)], 17) ^ + fio_rrot32(w[((i + 14) & 15)], 19) ^ (w[((i + 14) & 15)] >> 10)) + + w[((i + 9) & 15)] + w[(i & 15)] + + (fio_rrot32(w[((i + 1) & 15)], 7) ^ fio_rrot32(w[((i + 1) & 15)], 18) ^ + (w[((i + 1) & 15)] >> 3)); + const uint32_t t1 = + v[7] + sha256_consts[i + 16] + w[(i & 15)] + + ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + + (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); + FIO___SHA256_ROUND_INNER_COMMON(); + } + for (size_t i = 0; i < 8; ++i) + h->u32[i] += v[i]; /* compress block with previous state */ + +#undef FIO___SHA256_ROUND_INNER_COMMON +} + +/** consume data and feed it to hash. */ +SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len) { + const uint8_t *r = (const uint8_t *)data; + const size_t old_total = h->total_len; + const size_t new_total = len + h->total_len; + h->total_len = new_total; + /* manage cache */ + if (old_total & 63) { + const size_t offset = (old_total & 63); + if (len + offset < 64) { /* not enough - copy to cache */ + fio_memcpy63x((h->cache.u8 + offset), r, len); + return; + } + /* consume cache */ + const size_t byte2copy = 64UL - offset; + fio_memcpy63x(h->cache.u8 + offset, r, byte2copy); + fio___sha256_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 64); + r += byte2copy; + len -= byte2copy; + } + const uint8_t *end = r + (len & (~(uint64_t)63ULL)); + while ((uintptr_t)r < (uintptr_t)end) { + fio___sha256_round(&h->hash, r); + r += 64; + } + fio_memcpy63x(h->cache.u64, r, len); +} + +SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h) { + if (h->total_len == ((uint64_t)0ULL - 1ULL)) + return h->hash; + const size_t total = h->total_len; + const size_t remainder = total & 63; + h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ + if ((remainder) > 47) { /* make sure there's room to attach `total_len` */ + fio___sha256_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 64); + } + h->cache.u64[7] = fio_lton64((total << 3)); + fio___sha256_round(&h->hash, h->cache.u8); + for (size_t i = 0; i < 8; ++i) + h->hash.u32[i] = fio_ntol32(h->hash.u32[i]); /* back to big endien */ + h->total_len = ((uint64_t)0ULL - 1ULL); + return h->hash; +} + +/* ***************************************************************************** +Implementation - SHA-512 +***************************************************************************** */ + +FIO_IFUNC void fio___sha512_round(fio_u512 *h, const uint8_t *block) { + const uint64_t sha512_consts[80] = { + 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, + 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, + 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, + 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, + 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, + 0xC19BF174CF692694, 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, + 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, 0x2DE92C6F592B0275, + 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, + 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, + 0xBF597FC7BEEF0EE4, 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, + 0x06CA6351E003826F, 0x142929670A0E6E70, 0x27B70A8546D22FFC, + 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, + 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, + 0x92722C851482353B, 0xA2BFE8A14CF10364, 0xA81A664BBC423001, + 0xC24B8B70D0F89791, 0xC76C51A30654BE30, 0xD192E819D6EF5218, + 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, + 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, + 0x34B0BCB5E19B48A8, 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, + 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, 0x748F82EE5DEFB2FC, + 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, + 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, + 0xC67178F2E372532B, 0xCA273ECEEA26619C, 0xD186B8C721C0C207, + 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, 0x06F067AA72176FBA, + 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, + 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, + 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, + 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817}; + + uint64_t t1, t2; /* used often... */ + /* copy original state */ + uint64_t v[8] FIO_ALIGN(16); + for (size_t i = 0; i < 8; ++i) + v[i] = h->u64[i]; + + /* read data as an array of 16 big endian 64 bit integers. */ + uint64_t w[16] FIO_ALIGN(16); + fio_memcpy128(w, block); + for (size_t i = 0; i < 16; ++i) + w[i] = fio_lton64(w[i]); /* no-op on big endien systems */ + +#define FIO___SHA512_ROUND_UNROLL(s) \ + t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ + (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ + fio_rrot64(v[(4 - s) & 7], 41)) + \ + ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ + ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ + t2 = \ + (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ + fio_rrot64(v[(0 - s) & 7], 39)) + \ + ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ + (v[(1 - s) & 7] & v[(2 - s) & 7])); \ + v[(3 - s) & 7] += t1; \ + v[(7 - s) & 7] = t1 + t2 + + /* perform 80 "shuffle" rounds */ + for (size_t i = 0; i < 16; i += 8) { + FIO___SHA512_ROUND_UNROLL(0); + FIO___SHA512_ROUND_UNROLL(1); + FIO___SHA512_ROUND_UNROLL(2); + FIO___SHA512_ROUND_UNROLL(3); + FIO___SHA512_ROUND_UNROLL(4); + FIO___SHA512_ROUND_UNROLL(5); + FIO___SHA512_ROUND_UNROLL(6); + FIO___SHA512_ROUND_UNROLL(7); + } +#undef FIO___SHA512_ROUND_UNROLL +#define FIO___SHA512_ROUND_UNROLL(s) \ + t1 = (i + s + 14) & 15; \ + t2 = (i + s + 1) & 15; \ + t1 = fio_rrot64(w[t1], 19) ^ fio_rrot64(w[t1], 61) ^ (w[t1] >> 6); \ + t2 = fio_rrot64(w[t2], 1) ^ fio_rrot64(w[t2], 8) ^ (w[t2] >> 7); \ + w[(i + s) & 15] = t1 + t2 + w[(i + s + 9) & 15] + w[(i + s) & 15]; \ + t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ + (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ + fio_rrot64(v[(4 - s) & 7], 41)) + \ + ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ + ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ + t2 = \ + (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ + fio_rrot64(v[(0 - s) & 7], 39)) + \ + ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ + (v[(1 - s) & 7] & v[(2 - s) & 7])); \ + v[(3 - s) & 7] += t1; \ + v[(7 - s) & 7] = t1 + t2 + + for (size_t i = 16; i < 80; i += 8) { + FIO___SHA512_ROUND_UNROLL(0); + FIO___SHA512_ROUND_UNROLL(1); + FIO___SHA512_ROUND_UNROLL(2); + FIO___SHA512_ROUND_UNROLL(3); + FIO___SHA512_ROUND_UNROLL(4); + FIO___SHA512_ROUND_UNROLL(5); + FIO___SHA512_ROUND_UNROLL(6); + FIO___SHA512_ROUND_UNROLL(7); + } + /* sum/store state */ + for (size_t i = 0; i < 8; ++i) + h->u64[i] += v[i]; +} + +/** Feed data into the hash */ +SFUNC void fio_sha512_consume(fio_sha512_s *restrict h, + const void *restrict data, + uint64_t len) { + const uint8_t *r = (const uint8_t *)data; + const size_t old_total = h->total_len; + const size_t new_total = len + h->total_len; + h->total_len = new_total; + /* manage cache */ + if (old_total & 127) { + const size_t offset = (old_total & 127); + if (len + offset < 128) { /* not enough - copy to cache */ + fio_memcpy127x((h->cache.u8 + offset), r, len); + return; + } + /* consume cache */ + const size_t byte2copy = 128UL - offset; + fio_memcpy127x(h->cache.u8 + offset, r, byte2copy); + fio___sha512_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 128); + r += byte2copy; + len -= byte2copy; + } + const uint8_t *end = r + (len & (~(uint64_t)127ULL)); + while ((uintptr_t)r < (uintptr_t)end) { + fio___sha512_round(&h->hash, r); + r += 128; + } + fio_memcpy127x(h->cache.u64, r, len); +} + +/** finalizes a fio_u512 with the SHA 512 hash. */ +SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h) { + if (h->total_len == ((uint64_t)0ULL - 1ULL)) + return h->hash; + const size_t total = h->total_len; + const size_t remainder = total & 127; + h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ + if ((remainder) > 112) { /* make sure there's room to attach `total_len` */ + fio___sha512_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 128); + } + h->cache.u64[15] = fio_lton64((total << 3)); + fio___sha512_round(&h->hash, h->cache.u8); + for (size_t i = 0; i < 8; ++i) + h->hash.u64[i] = fio_ntol64(h->hash.u64[i]); /* back to/from big endien */ + h->total_len = ((uint64_t)0ULL - 1ULL); + return h->hash; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_SHA2 */ +#undef FIO_SHA2 +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ED25519 /* Development inclusion - ignore line */ +#define FIO_SHA2 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Elliptic Curve ED25519 (WIP) + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if 0 && defined(FIO_ED25519) && !defined(H___FIO_ED25519___H) +#define H___FIO_ED25519___H + +/* ***************************************************************************** +TODO: ED 25519 + +ED-25519 key generation, key exchange and signatures are crucial to complete the +minimal building blocks that would allow to secure inter-machine communication +in mostly secure environments. Of course the use of a tested cryptographic +library (where accessible) might be preferred, but some security is better than +none. +***************************************************************************** */ + +/* ***************************************************************************** +ED25519 API +***************************************************************************** */ + +/** ED25519 Key Pair */ +typedef struct { + fio_u512 private_key; /* Private key (with extra internal storage?) */ + fio_u256 public_key; /* Public key */ +} fio_ed25519_s; + +/* Generates a random ED25519 keypair. */ +SFUNC void fio_ed25519_keypair(fio_ed25519_s *keypair); + +/* Sign a message using ED25519 */ +SFUNC void fio_ed25519_sign(uint8_t *signature, + const fio_buf_info_s message, + const fio_ed25519_s *keypair); + +/* Verify an ED25519 signature */ +SFUNC int fio_ed25519_verify(const uint8_t *signature, + const fio_buf_info_s message, + const fio_u256 *public_key); + +/* ***************************************************************************** +Implementation - inlined static functions +***************************************************************************** */ + +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* prevent ED25519 keys from having a small period (cyclic value). */ +FIO_IFUNC void fio___ed25519_clamp_on_key(uint8_t *k) { + k[0] &= 0xF8U; /* zero out 3 least significant bits (emulate mul by 8) */ + k[31] &= 0x7FU; /* unset most significant bit (constant time fix) */ + k[31] |= 0x40U; /* set the 255th bit (making sure the value is big) */ +} + +static fio_u256 FIO___ED25519_PRIME = fio_u256_init64(0x7FFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFED); +/* Obfuscate or recover ED25519 keys to prevent easy memory scraping */ +FIO_IFUNC void fio___ed25519_flip(fio_ed25519_s *k) { + /* Generate a deterministic mask */ + uint64_t msk = + k->public_key.u64[3] + (uint64_t)(uintptr_t)(void *)&fio_ed25519_keypair; + /* XOR mask the private key */ + fio_u512_cxor64(&k->private_key, &k->private_key, msk); + /* XOR mask the first 192 bits of the public key */ + k->public_key.u64[0] ^= msk; + k->public_key.u64[1] ^= msk; + k->public_key.u64[2] ^= msk; +} + +/* Elliptic Curve Point Addition for Ed25519 */ +FIO_IFUNC void fio___ed25519_point_add(fio_u1024 *R, const fio_u1024 *P) { + /* Extract coordinates for P1 and P2 (R and P) */ + fio_u256 X1 = R->u256[0], Y1 = R->u256[1], Z1 = R->u256[2], T1 = R->u256[3]; + fio_u256 X2 = P->u256[0], Y2 = P->u256[1], Z2 = P->u256[2], T2 = P->u256[3]; + + fio_u256 A, B, C, D, X3, Y3, Z3, T3; + + /* A = (Y1 - X1) * (Y2 - X2) */ + fio_u256 Y1_minus_X1 = fio_u256_sub(Y1, X1); + fio_u256 Y2_minus_X2 = fio_u256_sub(Y2, X2); + A = fio_u256_mul(Y1_minus_X1, Y2_minus_X2); + + /* B = (Y1 + X1) * (Y2 + X2) */ + fio_u256 Y1_plus_X1 = fio_u256_add(Y1, X1); + fio_u256 Y2_plus_X2 = fio_u256_add(Y2, X2); + B = fio_u256_mul(Y1_plus_X1, Y2_plus_X2); + + /* C = 2 * T1 * T2 * d */ + C = fio_u256_mul(fio_u256_mul(T1, T2), ED25519_D); + + /* D = 2 * Z1 * Z2 */ + D = fio_u256_mul(fio_u256_mul(Z1, Z2), fio_u256_two()); + + /* X3 = (B - A) * (D - C) */ + X3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_sub(D, C)); + + /* Y3 = (B + A) * (D + C) */ + Y3 = fio_u256_mul(fio_u256_add(B, A), fio_u256_add(D, C)); + + /* Z3 = D * C */ + Z3 = fio_u256_mul(D, C); + + /* T3 = (B - A) * (B + A) */ + T3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_add(B, A)); + + /* Update R with the result */ + R->u256[0] = X3; /* X */ + R->u256[1] = Y3; /* Y */ + R->u256[2] = Z3; /* Z */ + R->u256[3] = T3; /* T */ +} + +/* Helper function: Scalar multiplication on the elliptic curve */ +FIO_IFUNC void fio___ed25519_mul(fio_u512 *result, + const fio_u512 *scalar, + const fio_u512 *point) { + /* Start with the point */ + fio_u512 R[2] = {{0}, point[0]}; /* Identity point */ + + /* Step 2: Perform the Montgomery ladder scalar multiplication */ + for (int i = 255; i >= 0; --i) { + uint64_t bit = (scalar->u64[i >> 6] >> (i & 63)) & 1U; + /* Elliptic curve point addition and doubling */ + fio___ed25519_point_add(R, R + 1); + fio___ed25519_point_double(R + bit); + } + + /* Step 3: The final result is stored in R0 */ + *result = R[0]; +} + +/* Helper function: Modular reduction for Ed25519 */ +FIO_IFUNC void fio___ed25519_mod_reduce(fio_u256 *s) { + /* TODO: Implement modular reduction for Ed25519 scalar */ +} + +/* ED25519 Base Point (G) */ +const fio_u512 FIO___ED25519_BASEPOINT = { + .u64 = + { + 0x216936D3CD6E53FEULL, /* x-coordinate (lower 64 bits) */ + 0xC0A4E231FDD6DC5CULL, /* x-coordinate (upper 64 bits) */ + 0x6666666666666666ULL, /* y-coordinate (lower 64 bits) */ + 0x6666666666666666ULL /* y-coordinate (upper 64 bits) */ + }, +}; + +/* Generate ED25519 keypair */ +SFUNC fio_ed25519_s fio_ed25519_keypair(void) { + fio_ed25519_s keypair; + /* Generate the 512-bit (clamped) private key */ + keypair.private_key.u64[0] = fio_rand64(); + keypair.private_key.u64[1] = fio_rand64(); + keypair.private_key.u64[2] = fio_rand64(); + keypair.private_key.u64[3] = fio_rand64(); + keypair.private_key = fio_sha512(keypair.private_key.u8, 32); + fio___ed25519_clamp_on_key(keypair.private_key.u8); + /* TODO: Derive the public key */ + fio_u256_mul(fio_u512 * result, const fio_u256 *a, const fio_u256 *b) + fio___ed25519_mul(&keypair.public_key, + &keypair.private_key, + &FIO___ED25519_BASEPOINT); + /* Maybe... */ + + /* Mask data, so it's harder to scrape in case of a memory dump. */ + fio___ed25519_flip(&keypair); + return keypair; +} + +/* Sign a message using ED25519 */ +SFUNC void fio_ed25519_sign(uint8_t *signature, + const fio_buf_info_s message, + const fio_ed25519_s *keypair) { + fio_sha512_s sha; + fio_u512 r, h; + fio_u256 R; + + /* Step 1: Hash the private key and message */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, + keypair->private_key.u8 + 32, + 32); /* Hash private key second part */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ + r = fio_sha512_finalize(&sha); /* Finalize the hash */ + + /* Step 2: Clamp and scalar multiply */ + fio___ed25519_clamp_on_key(r.u8); + fio___ed25519_mul((fio_ed25519_s *)&R, &r, &FIO___ED25519_BASEPOINT); + + /* Step 3: Compute 's' */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, R.u8, 32); /* Hash R */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash message */ + h = fio_sha512_finalize(&sha); /* Compute H(R || message) */ + fio___ed25519_mod_reduce(h.u8); /* Modular reduction of the hash */ + + /* Step 4: Create the signature */ + memcpy(signature, R.u8, 32); /* Copy R to the signature */ + memcpy(signature + 32, h.u8, 32); /* Copy the reduced hash 's' */ +} + +/* Verify an ED25519 signature */ +SFUNC int fio_ed25519_verify(const uint8_t *signature, + const fio_buf_info_s message, + const fio_u256 *public_key) { + fio_sha512_s sha; + fio_u512 r, h; + uint8_t calculated_R[32]; + + /* Step 1: Recalculate R */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, public_key->u8, 32); /* Hash the public key */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ + r = fio_sha512_finalize(&sha); /* Finalize the hash */ + + fio___ed25519_mul((fio_ed25519_s *)calculated_R, &r, &public_key->u8); + + /* Step 2: Compare calculated R with signature R using FIO_MEMCMP */ + return FIO_MEMCMP(calculated_R, signature, 32) == 0; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_ED25519 +#endif /* FIO_ED25519 */ +/* ************************************************************************** */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_STR_NAME fio /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Dynamic Strings (binary safe) + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef FIO_STR_SMALL +#ifndef FIO_STR_NAME +#define FIO_STR_NAME FIO_STR_SMALL +#endif +#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY +#define FIO_STR_OPTIMIZE4IMMUTABILITY 1 +#endif +#endif /* FIO_STR_SMALL */ + +#if defined(FIO_STR_NAME) + +#ifndef FIO_STR_OPTIMIZE_EMBEDDED +/** + * For each unit (0 by default), adds `sizeof(char *)` bytes to the type size, + * increasing the amount of strings that could be embedded within the type + * without additional memory allocation. + * + * For example, when using a reference counter wrapper on a 64bit system, it + * would make sense to set this value to 1 - allowing the type size to fully + * utilize a 16 byte memory allocation alignment. + */ +#define FIO_STR_OPTIMIZE_EMBEDDED 0 +#endif + +#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY +/** + * Minimizes the struct size, storing only string length and pointer. + * + * By avoiding extra (mutable related) data, such as the allocated memory's + * capacity, strings require less memory. However, this does introduce a + * performance penalty when editing the string data. + */ +#define FIO_STR_OPTIMIZE4IMMUTABILITY 0 +#endif + +#if FIO_STR_OPTIMIZE4IMMUTABILITY +/* enforce limit after which FIO_STR_OPTIMIZE4IMMUTABILITY makes no sense */ +#if FIO_STR_OPTIMIZE_EMBEDDED > 1 +#undef FIO_STR_OPTIMIZE_EMBEDDED +#define FIO_STR_OPTIMIZE_EMBEDDED 1 +#endif +#else +/* enforce limit due to 6 bit embedded string length limit (assumes 64 bit) */ +#if FIO_STR_OPTIMIZE_EMBEDDED > 4 +#undef FIO_STR_OPTIMIZE_EMBEDDED +#define FIO_STR_OPTIMIZE_EMBEDDED 4 +#endif +#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY*/ + +/* ***************************************************************************** +String API - Initialization and Destruction +***************************************************************************** */ + +/** + * The `fio_str_s` type should be considered opaque. + * + * The type's attributes should be accessed ONLY through the accessor + * functions: `fio_str2cstr`, `fio_str_len`, `fio_str2ptr`, `fio_str_capa`, + * etc'. + * + * Note: when the `small` flag is present, the structure is ignored and used + * as raw memory for a small String (no additional allocation). This changes + * the String's behavior drastically and requires that the accessor functions + * be used. + */ +typedef struct { + /* String flags: + * + * bit 1: small string. + * bit 2: frozen string. + * bit 3: static (non allocated) string (big strings only). + * bit 3-8: small string length (up to 64 bytes). + */ + uint8_t special; + uint8_t reserved[(sizeof(void *) * (1 + FIO_STR_OPTIMIZE_EMBEDDED)) - + (sizeof(uint8_t))]; /* padding length */ +#if !FIO_STR_OPTIMIZE4IMMUTABILITY + size_t capa; /* known capacity for longer Strings */ + size_t len; /* String length for longer Strings */ +#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY */ + char *buf; /* pointer for longer Strings */ +} FIO_NAME(FIO_STR_NAME, s); + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_STR_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_STR_PTR FIO_NAME(FIO_STR_NAME, s) * +#endif + +#ifndef FIO_STR_INIT +/** + * This value should be used for initialization. For example: + * + * // on the stack + * fio_str_s str = FIO_STR_INIT; + * + * // or on the heap + * fio_str_s *str = malloc(sizeof(*str)); + * *str = FIO_STR_INIT; + * + * Remember to cleanup: + * + * // on the stack + * fio_str_destroy(&str); + * + * // or on the heap + * fio_str_free(str); + * free(str); + */ +#define FIO_STR_INIT \ + { .special = 0 } + +/** + * This macro allows the container to be initialized with existing data, as long + * as it's memory was allocated with the same allocator (`malloc` / + * `fio_malloc`). + * + * The `capacity` value should exclude the NUL character (if exists). + * + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) + */ +#define FIO_STR_INIT_EXISTING(buffer, length, capacity) \ + { .capa = (capacity), .len = (length), .buf = (buffer) } + +/** + * This macro allows the container to be initialized with existing static data, + * that shouldn't be freed. + * + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) + */ +#define FIO_STR_INIT_STATIC(buffer) \ + { \ + .special = 4, .capa = FIO_STRLEN((buffer)), .len = FIO_STRLEN((buffer)), \ + .buf = (char *)(buffer) \ + } + +/** + * This macro allows the container to be initialized with existing static data, + * that shouldn't be freed. + * + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) + */ +#define FIO_STR_INIT_STATIC2(buffer, length) \ + { .special = 4, .capa = (length), .len = (length), .buf = (char *)(buffer) } + +#endif /* FIO_STR_INIT */ + +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/** Allocates a new String object on the heap. */ +FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void); + +/** + * Destroys the string and frees the container (if allocated with `new`). + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s); +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** + * Initializes the container with the provided static / constant string. + * + * The string will be copied to the container **only** if it will fit in the + * container itself. Otherwise, the supplied pointer will be used as is and it + * should remain valid until the string is destroyed. + * + * The final string can be safely be destroyed (using the `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s, + const char *str, + size_t len); + +/** + * Initializes the container with a copy of the provided dynamic string. + * + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s, + const char *str, + size_t len); + +/** + * Initializes the container with a copy of an existing String object. + * + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, + FIO_STR_PTR src); + +/** + * Frees the String's resources and re-initializes the container. + * + * Note: if the container isn't allocated on the stack, it should be freed + * separately using the appropriate `free` function. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s); + +/** + * Returns a C string with the existing data, re-initializing the String. + * + * Note: the String data is removed from the container, but the container + * isn't freed. + * + * Returns NULL if there's no String data. + * + * NOTE: Returned string is ALWAYS dynamically allocated. Remember to free. + */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s); + +/** Frees the pointer returned by `detach`. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr); + +/* ***************************************************************************** +String API - String state (data pointers, length, capacity, etc') +***************************************************************************** */ + +/** Returns the String's complete state (capacity, length and pointer). */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s); + +/** Returns the String's partial state (length and pointer). */ +FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s); + +/** Returns a pointer (`char *`) to the String's content. */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s); + +/** Returns the String's length in bytes. */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s); + +/** Returns the String's existing capacity (total used & available memory). */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s); + +/** Prevents further manipulations to the String's content. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s); + +/** Returns true if the string is frozen. */ +FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s); + +/** Returns 1 if memory was allocated (and the String must be destroyed). */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s); + +/** Binary comparison returns `1` if both strings are equal and `0` if not. */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1, + const FIO_STR_PTR str2); + +/** + * Returns the string's Risky Hash value. + * + * Note: Hash algorithm might change without notice. + */ +FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s, + uint64_t seed); + +/* ***************************************************************************** +String API - Memory management +***************************************************************************** */ + +/** + * Sets the new String size without reallocating any memory (limited by + * existing capacity). + * + * Returns the updated state of the String. + * + * Note: When shrinking, any existing data beyond the new size may be + * corrupted. + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s, + size_t size); + +/** + * Performs a best attempt at minimizing memory consumption. + * + * Actual effects depend on the underlying memory allocator and it's + * implementation. Not all allocators will free any memory. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s); + +#if !FIO_STR_OPTIMIZE4IMMUTABILITY +/** + * Reserves (at least) `amount` of bytes for the string's data. + * + * The reserved count includes used data. If `amount` is less than the current + * string length, the string will be truncated(!). + * + * Note: When optimized for immutability (`FIO_STR_SMALL`), this may corrupt the + * string length data. + * + * Make sure to call `resize` with the updated information once the editing is + * done. + * + * Returns the updated state of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, reserve)(FIO_STR_PTR s, + size_t amount); +#define FIO_STR_RESERVE_NAME reserve +#else +/** INTERNAL - DO NOT USE! */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, __reserve)(FIO_STR_PTR s, + size_t amount); +#define FIO_STR_RESERVE_NAME __reserve +#endif +/* ***************************************************************************** +String API - UTF-8 State +***************************************************************************** */ + +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s); + +/** Returns the String's length in UTF-8 characters. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s); + +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int FIO_NAME(FIO_STR_NAME, + utf8_select)(FIO_STR_PTR s, intptr_t *pos, size_t *len); + +/* ***************************************************************************** +String API - Content Manipulation and Review +***************************************************************************** */ + +/** Writes data at the end of the String. */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s, + const void *src, + size_t src_len); + +/** + * Appends the `src` String to the end of the `dest` String. + * + * If `dest` is empty, the resulting Strings will be equal. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest, + FIO_STR_PTR const src); + +/** Alias for fio_str_concat */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, join)(FIO_STR_PTR dest, + FIO_STR_PTR const src) { + return FIO_NAME(FIO_STR_NAME, concat)(dest, src); +} + +/** + * Replaces the data in the String - replacing `old_len` bytes starting at + * `start_pos`, with the data at `src` (`src_len` bytes long). + * + * Negative `start_pos` values are calculated backwards, `-1` == end of + * String. + * + * When `old_len` is zero, the function will insert the data at `start_pos`. + * + * If `src_len == 0` than `src` will be ignored and the data marked for + * replacement will be erased. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s, + intptr_t start_pos, + size_t old_len, + const void *src, + size_t src_len); + +/** Writes data at the end of the String. See `fio_string_write2`. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + __write2)(FIO_STR_PTR s, + const fio_string_write_s srcs[]); + +#ifndef FIO_STR_WRITE2 +#define FIO_STR_WRITE2(str_name, dest, ...) \ + FIO_NAME(str_name, __write2)(dest, (fio_string_write_s[]){__VA_ARGS__, {0}}) +#endif +/* ***************************************************************************** +String API - Numerals +***************************************************************************** */ + +/** Writes a number at the end of the String using normal base 10 notation. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s, + int64_t num); + +/** Writes a number at the end of the String using Hex (base 16) notation. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s, + int64_t num); + +/* Writes a binary representation of `i` to the String */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s, + int64_t num); + +/* ***************************************************************************** +String API - printf style support +***************************************************************************** */ + +/** + * Writes to the String using a vprintf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, vprintf)(FIO_STR_PTR s, + const char *format, + va_list argv); + +/** + * Writes to the String using a printf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + printf)(FIO_STR_PTR s, const char *format, ...); + +/* ***************************************************************************** +String API - C / JSON escaping +***************************************************************************** */ + +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s, + const void *data, + size_t data_len); + +/** + * Writes an escaped data into the string after unescaping the data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s, + const void *escaped, + size_t len); + +/* ***************************************************************************** +String API - Base64 support +***************************************************************************** */ + +/** + * Writes data at the end of the String, encoding the data as Base64 encoded + * data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64enc)(FIO_STR_PTR s, + const void *data, + size_t data_len, + uint8_t url_encoded); + +/** + * Writes decoded base64 data to the end of the String. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64dec)(FIO_STR_PTR s, + const void *encoded, + size_t encoded_len); + +/* ***************************************************************************** +String API - HTML escaping support +***************************************************************************** */ + +/** Writes HTML escaped data to a String. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s, + const void *raw, + size_t len); + +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_html_unescape)(FIO_STR_PTR s, + const void *escaped, + size_t len); + +/* ***************************************************************************** +String API - writing data from files to the String +***************************************************************************** */ + +/** + * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's + * contents (or a slice of it) at the end of the String. If `limit == 0`, than + * the data will be read until EOF. + * + * The file should be a regular file or the operation might fail (can't be used + * for sockets). + * + * The file descriptor will remain open and should be closed manually. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s, + int fd, + intptr_t start_at, + intptr_t limit); +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. + * + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s, + const char *filename, + intptr_t start_at, + intptr_t limit); +/* ***************************************************************************** +String API - Testing +***************************************************************************** */ +#ifdef FIO_STR_WRITE_TEST_FUNC +/** + * Tests the fio_str functionality. + */ +SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void); +#endif +/* ***************************************************************************** + + + String Implementation + + IMPLEMENTATION - INLINED + + +***************************************************************************** */ + +/* used here, but declared later (reference counter is static / global). */ + +SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void); +SFUNC void FIO_NAME(FIO_STR_NAME, __object_free)(FIO_NAME(FIO_STR_NAME, s) * s); +SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, + size_t new_capa); +SFUNC int FIO_NAME(FIO_STR_NAME, + __default_copy_and_reallocate)(fio_str_info_s *dest, + size_t new_capa); +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa); + +/* ***************************************************************************** +String Macro Helpers +***************************************************************************** */ + +#define FIO_STR_IS_SMALL(s) ((((s)->special & 1) | !(s)->buf)) +#define FIO_STR_SMALL_LEN(s) ((size_t)((s)->special >> 2)) +#define FIO_STR_SMALL_LEN_SET(s, l) \ + ((s)->special = (((s)->special & 2) | ((uint8_t)(l) << 2) | 1)) +#define FIO_STR_SMALL_CAPA(s) ((sizeof(*(s)) - 2) & 63) +#define FIO_STR_SMALL_DATA(s) ((char *)((s)->reserved)) + +#define FIO_STR_BIG_DATA(s) ((s)->buf) +#define FIO_STR_BIG_IS_DYNAMIC(s) (!((s)->special & 4)) +#define FIO_STR_BIG_SET_STATIC(s) ((s)->special |= 4) +#define FIO_STR_BIG_FREE_BUF(s) \ + (FIO_NAME(FIO_STR_NAME, __default_free)((s)->buf, FIO_STR_BIG_CAPA((s)))) + +#define FIO_STR_IS_FROZEN(s) ((s)->special & 2) +#define FIO_STR_FREEZE_(s) ((s)->special |= 2) +#define FIO_STR_THAW_(s) ((s)->special ^= (uint8_t)2) + +#if FIO_STR_OPTIMIZE4IMMUTABILITY + +#define FIO_STR_BIG_LEN(s) \ + ((sizeof(void *) == 4) \ + ? (((uint32_t)(s)->reserved[0]) | ((uint32_t)(s)->reserved[1] << 8) | \ + ((uint32_t)(s)->reserved[2] << 16)) \ + : (((uint64_t)(s)->reserved[0]) | ((uint64_t)(s)->reserved[1] << 8) | \ + ((uint64_t)(s)->reserved[2] << 16) | \ + ((uint64_t)(s)->reserved[3] << 24) | \ + ((uint64_t)(s)->reserved[4] << 32) | \ + ((uint64_t)(s)->reserved[5] << 40) | \ + ((uint64_t)(s)->reserved[6] << 48))) +#define FIO_STR_BIG_LEN_SET(s, l) \ + do { \ + if (sizeof(void *) == 4) { \ + if (!((l) & ((~(uint32_t)0) << 24))) { \ + (s)->reserved[0] = (l)&0xFF; \ + (s)->reserved[1] = ((uint32_t)(l) >> 8) & 0xFF; \ + (s)->reserved[2] = ((uint32_t)(l) >> 16) & 0xFF; \ + } else { \ + FIO_LOG_ERROR("facil.io small string length error - too long"); \ + (s)->reserved[0] = 0xFF; \ + (s)->reserved[1] = 0xFF; \ + (s)->reserved[2] = 0xFF; \ + } \ + } else { \ + if (!((l) & ((~(uint64_t)0) << 56))) { \ + (s)->reserved[0] = (l)&0xFF; \ + (s)->reserved[1] = ((uint64_t)(l) >> 8) & 0xFF; \ + (s)->reserved[2] = ((uint64_t)(l) >> 16) & 0xFF; \ + (s)->reserved[3] = ((uint64_t)(l) >> 24) & 0xFF; \ + (s)->reserved[4] = ((uint64_t)(l) >> 32) & 0xFF; \ + (s)->reserved[5] = ((uint64_t)(l) >> 40) & 0xFF; \ + (s)->reserved[6] = ((uint64_t)(l) >> 48) & 0xFF; \ + } else { \ + FIO_LOG_ERROR("facil.io small string length error - too long"); \ + (s)->reserved[0] = 0xFF; \ + (s)->reserved[1] = 0xFF; \ + (s)->reserved[2] = 0xFF; \ + (s)->reserved[3] = 0xFF; \ + (s)->reserved[4] = 0xFF; \ + (s)->reserved[5] = 0xFF; \ + (s)->reserved[6] = 0xFF; \ + } \ + } \ + } while (0) +#define FIO_STR_BIG_CAPA(s) fio_string_capa4len(FIO_STR_BIG_LEN((s))) +#define FIO_STR_BIG_CAPA_SET(s, capa) +#else +#define FIO_STR_BIG_LEN(s) ((s)->len) +#define FIO_STR_BIG_LEN_SET(s, l) ((s)->len = (l)) +#define FIO_STR_BIG_CAPA(s) ((s)->capa) +#define FIO_STR_BIG_CAPA_SET(s, capa) (FIO_STR_BIG_CAPA(s) = (capa)) +#endif + +/* ***************************************************************************** +String Information Round-tripping +***************************************************************************** */ + +/** Returns the String's complete state (capacity, length and pointer). */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s_) { + fio_str_info_s r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, r); + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + r = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), + FIO_STR_SMALL_LEN(s), + FIO_STR_SMALL_CAPA(s)); + else + r = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), + FIO_STR_BIG_LEN(s), + FIO_STR_BIG_CAPA(s)); + r.capa &= ((size_t)0ULL - (!FIO_STR_IS_FROZEN(s))); + return r; +} + +/** Returns the String's partial state (length and pointer). */ +FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s_) { + fio_buf_info_s r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, r); + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + r = FIO_BUF_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); + else + r = FIO_BUF_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); + return r; +} + +/* Internal(!): updated String data according to `info`. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, __info_update)(const FIO_STR_PTR s_, + fio_str_info_s info) { + /* internally used function, tagging already validated. */ + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (info.buf == FIO_STR_SMALL_DATA(s)) { + s->special |= 1; + FIO_STR_SMALL_LEN_SET(s, info.len); + return; + } + s->special = 0; + FIO_STR_BIG_LEN_SET(s, info.len); + FIO_STR_BIG_CAPA_SET(s, info.capa); + s->buf = info.buf; +} + +/* Internal(!): updated String data according to `info`. */ +FIO_IFUNC fio_string_realloc_fn FIO_NAME(FIO_STR_NAME, + __realloc_func)(const FIO_STR_PTR s_) { + fio_string_realloc_fn options[] = { + FIO_NAME(FIO_STR_NAME, __default_reallocate), + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate), + }; + /* internally used function, tagging already validated. */ + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return options[FIO_STR_IS_SMALL(s) | !FIO_STR_BIG_IS_DYNAMIC(s)]; +} + +/* ***************************************************************************** +String Constructors (inline) +***************************************************************************** */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/** Allocates a new String object on the heap. */ +FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void) { + FIO_NAME(FIO_STR_NAME, s) *const s = FIO_NAME(FIO_STR_NAME, __object_new)(); + if (!FIO_MEM_REALLOC_IS_SAFE_ && s) { + *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; + } +#ifdef DEBUG + { + FIO_NAME(FIO_STR_NAME, s) tmp = {0}; + FIO_ASSERT(!FIO_MEMCMP(&tmp, s, sizeof(tmp)), + "new " FIO_MACRO2STR( + FIO_NAME(FIO_STR_NAME, s)) " object not initialized!"); + } +#endif + return (FIO_STR_PTR)FIO_PTR_TAG(s); +} + +/** Destroys the string and frees the container (if allocated with `new`). */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { + FIO_STR_BIG_FREE_BUF(s); + } + FIO_NAME(FIO_STR_NAME, __object_free)(s); +} + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** + * Frees the String's resources and reinitializes the container. + * + * Note: if the container isn't allocated on the stack, it should be freed + * separately using the appropriate `free` function. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { + FIO_STR_BIG_FREE_BUF(s); + } + *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; +} + +/** + * Returns a C string with the existing data, re-initializing the String. + * + * Note: the String data is removed from the container, but the container + * isn't freed. + * + * Returns NULL if there's no String data. + */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s_) { + char *data = NULL; + FIO_PTR_TAG_VALID_OR_RETURN(s_, data); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + + if (FIO_STR_IS_SMALL(s)) { + if (FIO_STR_SMALL_LEN(s)) { /* keep these ifs apart */ + fio_str_info_s cpy = + FIO_STR_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); + data = cpy.buf; + } + } else { + if (FIO_STR_BIG_IS_DYNAMIC(s)) { + data = FIO_STR_BIG_DATA(s); + } else if (FIO_STR_BIG_LEN(s)) { + fio_str_info_s cpy = + FIO_STR_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); + data = cpy.buf; + } + } + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + return data; +} + +/** + * Performs a best attempt at minimizing memory consumption. + * + * Actual effects depend on the underlying memory allocator and it's + * implementation. Not all allocators will free any memory. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s_) { +#if FIO_STR_OPTIMIZE4IMMUTABILITY + (void)s_; +#else + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s) || !FIO_STR_BIG_IS_DYNAMIC(s) || + fio_string_capa4len(FIO_NAME(FIO_STR_NAME, len)(s_)) >= + FIO_NAME(FIO_STR_NAME, capa)(s_)) + return; + FIO_NAME(FIO_STR_NAME, s) tmp = FIO_STR_INIT; + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + FIO_NAME(FIO_STR_NAME, init_copy) + ((FIO_STR_PTR)FIO_PTR_TAG(&tmp), i.buf, i.len); + FIO_NAME(FIO_STR_NAME, destroy)(s_); + *s = tmp; +#endif +} + +/* ***************************************************************************** +String Initialization (inline) +***************************************************************************** */ + +/** + * Initializes the container with the provided static / constant string. + * + * The string will be copied to the container **only** if it will fit in the + * container itself. Otherwise, the supplied pointer will be used as is and it + * should remain valid until the string is destroyed. + * + * The final string can be safely be destroyed (using the `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s_, + const char *str, + size_t len) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + if (len < FIO_STR_SMALL_CAPA(s)) { + FIO_STR_SMALL_LEN_SET(s, len); + if (len && str) + FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); + FIO_STR_SMALL_DATA(s)[len] = 0; + + i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); + return i; + } + FIO_STR_BIG_DATA(s) = (char *)str; + FIO_STR_BIG_LEN_SET(s, len); + FIO_STR_BIG_CAPA_SET(s, len); + FIO_STR_BIG_SET_STATIC(s); + i = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), len, 0); + return i; +} + +/** + * Initializes the container with the provided dynamic string. + * + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s_, + const char *str, + size_t len) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + if (len < FIO_STR_SMALL_CAPA(s)) { + FIO_STR_SMALL_LEN_SET(s, len); + if (len && str) + FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); + FIO_STR_SMALL_DATA(s)[len] = 0; + + i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); + return i; + } + i = FIO_STR_INFO2((char *)str, len); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&i, len); + FIO_STR_BIG_CAPA_SET(s, i.capa); + FIO_STR_BIG_DATA(s) = i.buf; + FIO_STR_BIG_LEN_SET(s, len); + return i; +} + +/** + * Initializes the container with a copy of an existing String object. + * + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, + FIO_STR_PTR src) { + fio_str_info_s i; + i = FIO_NAME(FIO_STR_NAME, info)(src); + i = FIO_NAME(FIO_STR_NAME, init_copy)(dest, i.buf, i.len); + return i; +} + +/* ***************************************************************************** +String Information (inline) +***************************************************************************** */ + +/** Returns a pointer (`char *`) to the String's content. */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, NULL); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + char *results[] = {(FIO_STR_BIG_DATA(s)), (FIO_STR_SMALL_DATA(s))}; + return results[FIO_STR_IS_SMALL(s)]; +} + +/** Returns the String's length in bytes. */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + size_t results[] = {(FIO_STR_BIG_LEN(s)), (FIO_STR_SMALL_LEN(s))}; + return results[FIO_STR_IS_SMALL(s)]; +} + +/** Returns the String's existing capacity (total used & available memory). */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + return FIO_STR_SMALL_CAPA(s); + if (FIO_STR_BIG_IS_DYNAMIC(s)) + return FIO_STR_BIG_CAPA(s); + return 0; +} + +/** + * Sets the new String size without reallocating any memory (limited by + * existing capacity). + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s_, + size_t size) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) { + return i; + } + /* resize may be used to reserve memory in advance while setting size */ + if (i.capa > size) { + i.len = size; + i.buf[i.len] = 0; + } else { + fio_string_write(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + NULL, + size - i.len); + } + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + + return i; +} + +/** + * Prevents further manipulations to the String's content. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + FIO_STR_FREEZE_(s); +} + +/** + * Returns true if the string is frozen. + */ +FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 1); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return FIO_STR_IS_FROZEN(s); +} + +/** Returns 1 if memory was allocated and (the String must be destroyed). */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return (!FIO_STR_IS_SMALL(s) & FIO_STR_BIG_IS_DYNAMIC(s)); +} + +/** + * Binary comparison returns `1` if both strings are equal and `0` if not. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1_, + const FIO_STR_PTR str2_) { + if (str1_ == str2_) + return 1; + FIO_PTR_TAG_VALID_OR_RETURN(str1_, 0); + FIO_PTR_TAG_VALID_OR_RETURN(str2_, 0); + fio_buf_info_s s1 = FIO_NAME(FIO_STR_NAME, buf)(str1_); + fio_buf_info_s s2 = FIO_NAME(FIO_STR_NAME, buf)(str2_); + return FIO_BUF_INFO_IS_EQ(s1, s2); +} + +/** + * Returns the string's Risky Hash value. + * + * Note: Hash algorithm might change without notice. + */ +FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s_, + uint64_t seed) { + fio_buf_info_s i = FIO_NAME(FIO_STR_NAME, buf)(s_); + return fio_risky_hash((void *)i.buf, i.len, seed); +} + +/* ***************************************************************************** +String API - Content Manipulation and Review (inline) +***************************************************************************** */ + +/** Writes data at the end of the String. */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), src, len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/* ***************************************************************************** + + + String Implementation + + IMPLEMENTATION + + +***************************************************************************** */ + +/* ***************************************************************************** +External functions +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, destroy)) + +/* ***************************************************************************** +String Core Callbacks - Memory management +***************************************************************************** */ +SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void) { + FIO_NAME(FIO_STR_NAME, s) *r = + (FIO_NAME(FIO_STR_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, (sizeof(*r)), 0); + if (r) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, s)); + return r; +} +SFUNC void FIO_NAME(FIO_STR_NAME, + __object_free)(FIO_NAME(FIO_STR_NAME, s) * s) { + if (!s) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, s)); + FIO_MEM_FREE_(s, sizeof(*s)); +} + +SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, + size_t new_capa) { + new_capa = fio_string_capa4len(new_capa); + void *tmp = FIO_MEM_REALLOC_(dest->buf, dest->capa, new_capa, dest->len); + if (!tmp) + return -1; + if (!dest->buf) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); + dest->capa = new_capa; + dest->buf = (char *)tmp; + return 0; +} +SFUNC int FIO_NAME(FIO_STR_NAME, + __default_copy_and_reallocate)(fio_str_info_s *dest, + size_t new_capa) { + if (dest->len && new_capa < dest->len) + new_capa = dest->len; + new_capa = fio_string_capa4len(new_capa); + void *tmp = FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); + if (!tmp) + return -1; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); + if (dest->len) + FIO_MEMCPY(tmp, dest->buf, dest->len); + ((char *)tmp)[dest->len] = 0; + dest->capa = new_capa; + dest->buf = (char *)tmp; + return 0; +} +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); + FIO_MEM_FREE_(ptr, capa); + (void)capa; /* if unused */ +} +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop)(void *str) { (void)str; } +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop2)(fio_str_info_s str) { + (void)str; +} + +/* ***************************************************************************** +String Implementation - Memory management +***************************************************************************** */ + +/** Frees the pointer returned by `detach`. */ +SFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); + FIO_MEM_FREE_(ptr, -1); +} + +/** + * Reserves at least `amount` of bytes for the string's data. + * + * Returns the current state of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + FIO_STR_RESERVE_NAME)(FIO_STR_PTR s_, + size_t amount) { + fio_str_info_s state = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, state); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + state = FIO_NAME(FIO_STR_NAME, info)(s_); + if (FIO_STR_IS_FROZEN(s)) + return state; + amount += state.len; + if (state.capa <= amount) { + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_)(&state, amount); + state.buf[state.len] = 0; + FIO_NAME(FIO_STR_NAME, __info_update)(s_, state); + } else if (state.capa > FIO_STR_SMALL_CAPA(s) && + amount <= FIO_STR_SMALL_CAPA(s) && + state.len <= FIO_STR_SMALL_CAPA(s)) { + FIO_NAME(FIO_STR_NAME, s) tmp; + state = FIO_NAME(FIO_STR_NAME, init_copy)((FIO_STR_PTR)FIO_PTR_TAG(&tmp), + state.buf, + state.len); + FIO_NAME(FIO_STR_NAME, destroy)(s_); + *s = tmp; + } + return state; +} + +/* ***************************************************************************** +String Implementation - UTF-8 State +***************************************************************************** */ + +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_len(state); +} + +/** Returns the String's length in UTF-8 characters. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s_) { + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_len(state); +} + +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int FIO_NAME(FIO_STR_NAME, + utf8_select)(FIO_STR_PTR s_, intptr_t *pos, size_t *len) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, -1); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_select(state, pos, len); +} + +/* ***************************************************************************** +String Implementation - Content Manipulation and Review +***************************************************************************** */ + +/** + * Writes a number at the end of the String using normal base 10 notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_i(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes a number at the end of the String using Hex (base 16) notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_hex(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes a number at the end of the String using binary notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_bin(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Appends the `src` String to the end of the `dest` String. + * + * If `dest` is empty, the resulting Strings will be equal. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest_, + FIO_STR_PTR const src_) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(dest_); + if (!i.capa) + return i; + FIO_PTR_TAG_VALID_OR_RETURN(src_, i); + fio_str_info_s src = FIO_NAME(FIO_STR_NAME, info)(src_); + if (!src.len) + return i; + fio_string_write(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(dest_), + src.buf, + src.len); + FIO_NAME(FIO_STR_NAME, __info_update)(dest_, i); + return i; +} + +/** + * Replaces the data in the String - replacing `old_len` bytes starting at + * `start_pos`, with the data at `src` (`src_len` bytes long). + * + * Negative `start_pos` values are calculated backwards, `-1` == end of + * String. + * + * When `old_len` is zero, the function will insert the data at `start_pos`. + * + * If `src_len == 0` than `src` will be ignored and the data marked for + * replacement will be erased. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s_, + intptr_t start_pos, + size_t old_len, + const void *src, + size_t src_len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_replace(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + start_pos, + old_len, + src, + src_len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes a number at the end of the String using binary notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + __write2)(FIO_STR_PTR s_, + const fio_string_write_s srcs[]) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write2 FIO_NOOP(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + srcs); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes to the String using a vprintf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 0) + FIO_NAME(FIO_STR_NAME, + vprintf)(FIO_STR_PTR s_, const char *format, va_list argv) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_vprintf(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + format, + argv); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes to the String using a printf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 3) + FIO_NAME(FIO_STR_NAME, printf)(FIO_STR_PTR s_, const char *format, ...) { + va_list argv; + va_start(argv, format); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, vprintf)(s_, format, argv); + va_end(argv); + return state; +} + +/* ***************************************************************************** +String API - C / JSON escaping +***************************************************************************** */ + +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_escape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + src, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes an escaped data into the string after unescaping the data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_unescape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + src, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/* ***************************************************************************** +String - Base64 support +***************************************************************************** */ + +/** + * Writes data at the end of the String, encoding the data as Base64 encoded + * data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64enc)(FIO_STR_PTR s_, + const void *data, + size_t len, + uint8_t url_encoded) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_base64enc(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len, + url_encoded); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes decoded base64 data to the end of the String. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64dec)(FIO_STR_PTR s_, + const void *encoded_, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_base64dec(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + encoded_, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/* ***************************************************************************** +String API - HTML escaping support +***************************************************************************** */ + +/** Writes HTML escaped data to a String. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s_, + const void *data, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_html_escape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_html_unescape)(FIO_STR_PTR s_, + const void *data, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_html_unescape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/* ***************************************************************************** +String - read file +***************************************************************************** */ + +/** + * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's + * contents (or a slice of it) at the end of the String. If `limit == 0`, than + * the data will be read until EOF. + * + * The file should be a regular file or the operation might fail (can't be used + * for sockets). + * + * The file descriptor will remain open and should be closed manually. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s_, + int fd, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_readfd(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + fd, + start_at, + limit); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. + * + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s_, + const char *filename, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_readfile(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + filename, + start_at, + limit); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/* ***************************************************************************** + + + String Test + + +***************************************************************************** */ +#ifdef FIO_STR_WRITE_TEST_FUNC + +/** + * Tests the fio_str functionality. + */ +SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void) { + FIO_NAME(FIO_STR_NAME, s) str = {0}; /* test zeroed out memory */ +#define FIO__STR_SMALL_CAPA FIO_STR_SMALL_CAPA(&str) + FIO_STR_PTR pstr = FIO_PTR_TAG((&str)); + fprintf( + stderr, + "* Testing core string features for " FIO_MACRO2STR(FIO_STR_NAME) ".\n"); + fprintf(stderr, + "* String container size (without wrapper): %zu\n", + sizeof(FIO_NAME(FIO_STR_NAME, s))); + fprintf(stderr, + "* Self-contained capacity (FIO_STR_SMALL_CAPA): %zu\n", + FIO__STR_SMALL_CAPA); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), "new string is frozen"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, + "small string capacity returned %zu", + FIO_NAME(FIO_STR_NAME, capa)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, + "small string length reporting error!"); + FIO_ASSERT( + FIO_NAME(FIO_STR_NAME, ptr)(pstr) == ((char *)(&str) + 1), + "small string pointer reporting error (%zd offset)!", + (ssize_t)(((char *)(&str) + 1) - FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_NAME(FIO_STR_NAME, write)(pstr, "World", 4); + FIO_ASSERT(FIO_STR_IS_SMALL(&str), + "small string writing error - not small on small write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, + "Small string capacity reporting error after write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, + "small string length reporting error after write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == (char *)&str + 1, + "small string pointer reporting error after write!"); + FIO_ASSERT(!FIO_NAME(FIO_STR_NAME, ptr)(pstr)[4] && + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, + "small string NUL missing after write (%zu)!", + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), + "small string write error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == + FIO_NAME(FIO_STR_NAME, info)(pstr).buf, + "small string `data` != `info.buf` (%p != %p)", + (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), + (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); + + FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) + (pstr, sizeof(FIO_NAME(FIO_STR_NAME, s))); + FIO_ASSERT(!FIO_STR_IS_SMALL(&str), + "Long String reporting as small after capacity update!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) >= + sizeof(FIO_NAME(FIO_STR_NAME, s)) - 1, + "Long String capacity update error (%zu != %zu)!", + FIO_NAME(FIO_STR_NAME, capa)(pstr), + FIO_STR_SMALL_CAPA(&str)); + + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == + FIO_NAME(FIO_STR_NAME, info)(pstr).buf, + "Long String `ptr` !>= " + "`cstr(s).buf` (%p != %p)", + (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), + (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); + +#if FIO_STR_OPTIMIZE4IMMUTABILITY + /* immutable string length is updated after `reserve` to reflect new capa */ + FIO_NAME(FIO_STR_NAME, resize)(pstr, 4); +#endif + FIO_ASSERT( + FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, + "Long String length changed during conversion from small string (%zu)!", + FIO_NAME(FIO_STR_NAME, len)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == str.buf, + "Long String pointer reporting error after capacity update!"); + FIO_ASSERT(FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, + "Long String NUL missing after capacity update (%zu)!", + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), + "Long String value changed after capacity update (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_NAME(FIO_STR_NAME, write)(pstr, "d!", 2); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "World!"), + "Long String `write` error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_NAME(FIO_STR_NAME, replace)(pstr, 0, 0, "Hello ", 6); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello World!"), + "Long String `insert` error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_NAME(FIO_STR_NAME, resize)(pstr, 6); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello "), + "Long String `resize` clipping error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_NAME(FIO_STR_NAME, replace)(pstr, 6, 0, "My World!", 9); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello My World!"), + "Long String `replace` error when testing overflow (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) + (pstr, FIO_NAME(FIO_STR_NAME, len)(pstr)); /* may truncate */ + + FIO_NAME(FIO_STR_NAME, replace)(pstr, -10, 2, "Big", 3); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), + "Long String `replace` error when testing splicing (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == + fio_string_capa4len(FIO_STRLEN("Hello Big World!")) || + !FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Long String `replace` capacity update error " + "(%zu >=? %zu)!", + FIO_NAME(FIO_STR_NAME, capa)(pstr), + fio_string_capa4len(FIO_STRLEN("Hello Big World!"))); + + if (FIO_NAME(FIO_STR_NAME, len)(pstr) < (sizeof(str) - 2)) { + FIO_NAME(FIO_STR_NAME, compact)(pstr); + FIO_ASSERT(FIO_STR_IS_SMALL(&str), + "Compacting didn't change String to small!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == + FIO_STRLEN("Hello Big World!"), + "Compacting altered String length! (%zu != %zu)!", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_STRLEN("Hello Big World!")); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), + "Compact data error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == sizeof(str) - 2, + "Compacted String capacity reporting error!"); + } else { + FIO_LOG_DEBUG2("* Skipped `compact` test (irrelevant for type)."); + } + + { + FIO_NAME(FIO_STR_NAME, freeze)(pstr); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), + "Frozen String not flagged as frozen."); + fio_str_info_s old_state = FIO_NAME(FIO_STR_NAME, info)(pstr); + FIO_NAME(FIO_STR_NAME, write)(pstr, "more data to be written here", 28); + FIO_NAME(FIO_STR_NAME, replace) + (pstr, 2, 1, "more data to be written here", 28); + fio_str_info_s new_state = FIO_NAME(FIO_STR_NAME, info)(pstr); + FIO_ASSERT(old_state.len == new_state.len, "Frozen String length changed!"); + FIO_ASSERT(old_state.buf == new_state.buf, + "Frozen String pointer changed!"); + FIO_ASSERT( + old_state.capa == new_state.capa, + "Frozen String capacity changed (allowed, but shouldn't happen)!"); + FIO_STR_THAW_(&str); + } + FIO_NAME(FIO_STR_NAME, printf)(pstr, " %u", 42); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World! 42"), + "`printf` data error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + + { + FIO_NAME(FIO_STR_NAME, s) str2 = FIO_STR_INIT; + FIO_STR_PTR pstr2 = FIO_PTR_TAG(&str2); + FIO_NAME(FIO_STR_NAME, concat)(pstr2, pstr); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), + "`concat` error, strings not equal (%s != %s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); + FIO_NAME(FIO_STR_NAME, write)(pstr2, ":extra data", 11); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), + "`write` error after copy, strings equal " + "((%zu)%s == (%zu)%s)!", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr), + FIO_NAME(FIO_STR_NAME, len)(pstr2), + FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); + + FIO_NAME(FIO_STR_NAME, destroy)(pstr2); + } + + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + + FIO_NAME(FIO_STR_NAME, write_i)(pstr, -42); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 3 && + !memcmp("-42", FIO_NAME(FIO_STR_NAME, ptr)(pstr), 3), + "write_i output error ((%zu) %s != -42)", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + { + fprintf(stderr, "* Testing string `readfile`.\n"); + FIO_NAME(FIO_STR_NAME, s) *s = FIO_NAME(FIO_STR_NAME, new)(); + FIO_ASSERT(FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s), + "error, string not allocated (%p)!", + (void *)s); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, readfile)(s, __FILE__, 0, 0); + + FIO_ASSERT(state.len && state.buf, + "error, no data was read for file %s!", + __FILE__); +#if defined(H___FIO_CSTL_COMBINED___H) + FIO_ASSERT(!memcmp(state.buf, + "/* " + "******************************************************" + "***********************", + 80), + "content error, header mismatch!\n %s", + state.buf); +#endif /* H___FIO_CSTL_COMBINED___H */ + fprintf(stderr, "* Testing UTF-8 validation and length.\n"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_valid)(s), + "`utf8_valid` error, code in this file " + "should be valid!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_len)(s) && + (FIO_NAME(FIO_STR_NAME, utf8_len)(s) <= + FIO_NAME(FIO_STR_NAME, len)(s)) && + (FIO_NAME(FIO_STR_NAME, utf8_len)(s) >= + (FIO_NAME(FIO_STR_NAME, len)(s)) >> 1), + "`utf8_len` error, invalid value (%zu / %zu!", + FIO_NAME(FIO_STR_NAME, utf8_len)(s), + FIO_NAME(FIO_STR_NAME, len)(s)); + + if (1) { + /* String content == whole file (this file) */ + intptr_t pos = -10; + size_t len = 20; + fprintf(stderr, "* Testing UTF-8 positioning.\n"); + + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, + "`select` returned error for negative " + "pos! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == + (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */ + "`utf8_select` error, negative position " + "invalid! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == 10, + "`utf8_select` error, trancated length " + "invalid! (%zd)", + (ssize_t)len); + pos = 10; + len = 20; + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, + "`utf8_select` returned error! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == 10, + "`utf8_select` error, position invalid! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == 20, + "`utf8_select` error, length invalid! (%zd)", + (ssize_t)len); + } + FIO_NAME(FIO_STR_NAME, free)(s); + } + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + if (1) { + /* Testing Static initialization and writing */ +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); +#else + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); +#endif + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || + FIO_STR_IS_SMALL(&str), + "Static string capacity non-zero."); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, + "Static string length should be automatically calculated."); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Static strings shouldn't be dynamic."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const) + (pstr, + "Welcome to a very long static string that should not fit within a " + "containing struct... hopefuly", + 95); +#else + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC( + "Welcome to a very long static string that should not fit within a " + "containing struct... hopefuly"); +#endif + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || + FIO_STR_IS_SMALL(&str), + "Static string capacity non-zero."); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, + "Static string length should be automatically calculated."); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Static strings shouldn't be dynamic."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); +#else + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); +#endif + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, write)(pstr, " Home", 5); + FIO_ASSERT(state.capa > 0, "Static string not converted to non-static."); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr) || + FIO_STR_IS_SMALL(&str), + "String should be dynamic after `write`."); + + char *cstr = FIO_NAME(FIO_STR_NAME, detach)(pstr); + FIO_ASSERT(cstr, "`detach` returned NULL"); + FIO_ASSERT(!memcmp(cstr, "Welcome Home\0", 13), + "`detach` string error: %s", + cstr); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, + "`detach` data wasn't cleared."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); /*not really needed... detached... */ + FIO_NAME(FIO_STR_NAME, dealloc)(cstr); + } + { + fprintf(stderr, "* Testing Base64 encoding / decoding.\n"); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); /* does nothing, but why not... */ + + FIO_NAME(FIO_STR_NAME, s) b64message = FIO_STR_INIT; + fio_str_info_s b64i = FIO_NAME(FIO_STR_NAME, write)( + FIO_PTR_TAG(&b64message), + "Hello World, this is the voice of peace:)", + 41); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + b64i = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&b64message), &c, 1); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)) == + (size_t)(42 + i), + "Base64 message length error (%zu != %zu)", + FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)), + (size_t)(42 + i)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, + ptr)(FIO_PTR_TAG(&b64message))[41 + i] == (char)c, + "Base64 message data error"); + } + fio_str_info_s encoded = + FIO_NAME(FIO_STR_NAME, write_base64enc)(pstr, b64i.buf, b64i.len, 1); + /* prevent encoded data from being deallocated during unencoding */ + encoded = FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME)( + pstr, + encoded.len + ((encoded.len >> 2) * 3) + 8); + fio_str_info_s decoded; + { + FIO_NAME(FIO_STR_NAME, s) tmps; + FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_PTR_TAG(&tmps), pstr); + decoded = FIO_NAME(FIO_STR_NAME, write_base64dec)( + pstr, + FIO_NAME(FIO_STR_NAME, ptr)(FIO_PTR_TAG(&tmps)), + FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&tmps))); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&tmps)); + encoded.buf = decoded.buf; + } + FIO_ASSERT(encoded.len, "Base64 encoding failed"); + FIO_ASSERT(decoded.len > encoded.len, + "Base64 decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(b64i.len == decoded.len - encoded.len, + "Base 64 roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + b64i.len, + decoded.len - encoded.len, + decoded.len, + encoded.len, + decoded.buf); + + FIO_ASSERT(!memcmp(b64i.buf, decoded.buf + encoded.len, b64i.len), + "Base 64 roundtrip failed:\n %s", + decoded.buf); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&b64message)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + } + { + fprintf(stderr, "* Testing JSON style character escaping / unescaping.\n"); + FIO_NAME(FIO_STR_NAME, s) unescaped = FIO_STR_INIT; + fio_str_info_s ue; + const char *utf8_sample = /* three hearts, small-big-small*/ + "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95"; + FIO_NAME(FIO_STR_NAME, write) + (FIO_PTR_TAG(&unescaped), utf8_sample, FIO_STRLEN(utf8_sample)); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + ue = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&unescaped), &c, 1); + } + fio_str_info_s encoded = + FIO_NAME(FIO_STR_NAME, write_escape)(pstr, ue.buf, ue.len); + // fprintf(stderr, "* %s\n", encoded.buf); + fio_str_info_s decoded; + { + FIO_NAME(FIO_STR_NAME, s) tmps; + FIO_NAME(FIO_STR_NAME, init_copy2)(&tmps, pstr); + decoded = FIO_NAME(FIO_STR_NAME, + write_unescape)(pstr, + FIO_NAME(FIO_STR_NAME, ptr)(&tmps), + FIO_NAME(FIO_STR_NAME, len)(&tmps)); + FIO_NAME(FIO_STR_NAME, destroy)(&tmps); + encoded.buf = decoded.buf; + } + FIO_ASSERT(!memcmp(encoded.buf, utf8_sample, FIO_STRLEN(utf8_sample)), + "valid UTF-8 data shouldn't be escaped:\n%.*s\n%s", + (int)encoded.len, + encoded.buf, + decoded.buf); + FIO_ASSERT(encoded.len, "JSON encoding failed"); + FIO_ASSERT(decoded.len > encoded.len, + "JSON decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(ue.len == decoded.len - encoded.len, + "JSON roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + ue.len, + decoded.len - encoded.len, + decoded.len, + encoded.len, + decoded.buf); + + FIO_ASSERT(!memcmp(ue.buf, decoded.buf + encoded.len, ue.len), + "JSON roundtrip failed:\n %s", + decoded.buf); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&unescaped)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + } +} +#undef FIO__STR_SMALL_CAPA +#undef FIO_STR_WRITE_TEST_FUNC +#endif /* FIO_STR_WRITE_TEST_FUNC */ + +/* ***************************************************************************** +String Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ + +#undef FIO_STR_SMALL +#undef FIO_STR_SMALL_CAPA +#undef FIO_STR_SMALL_DATA +#undef FIO_STR_SMALL_LEN +#undef FIO_STR_SMALL_LEN_SET + +#undef FIO_STR_BIG_CAPA +#undef FIO_STR_BIG_CAPA_SET +#undef FIO_STR_BIG_DATA +#undef FIO_STR_BIG_FREE_BUF +#undef FIO_STR_BIG_IS_DYNAMIC +#undef FIO_STR_BIG_LEN +#undef FIO_STR_BIG_LEN_SET +#undef FIO_STR_BIG_SET_STATIC + +#undef FIO_STR_FREEZE_ + +#undef FIO_STR_IS_FROZEN +#undef FIO_STR_IS_SMALL +#undef FIO_STR_NAME + +#undef FIO_STR_OPTIMIZE4IMMUTABILITY +#undef FIO_STR_OPTIMIZE_EMBEDDED +#undef FIO_STR_PTR +#undef FIO_STR_THAW_ +#undef FIO_STR_RESERVE_NAME + +#endif /* FIO_STR_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ARRAY_NAME ary /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Dynamic Arrays + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef FIO_ARRAY_NAME + +#ifndef FIO_ARRAY_NOT_FOUND +#define FIO_ARRAY_NOT_FOUND ((uint32_t)-1) +#endif + +#ifdef FIO_ARRAY_TYPE_STR +#ifndef FIO_ARRAY_TYPE +#define FIO_ARRAY_TYPE fio_keystr_s +#endif +#ifndef FIO_ARRAY_TYPE_COPY +#define FIO_ARRAY_TYPE_COPY(dest, src) ((dest) = fio_keystr_init((src))) +#endif +#ifndef FIO_ARRAY_TYPE_DESTROY +#define FIO_ARRAY_TYPE_DESTROY(obj) fio_keystr_destroy(&(obj)); +#endif +#ifndef FIO_ARRAY_TYPE_CMP +#define FIO_ARRAY_TYPE_CMP(a, b) fio_keystr_is_eq((a), (b)) +#endif +#undef FIO_ARRAY_DESTROY_AFTER_COPY +#define FIO_ARRAY_DESTROY_AFTER_COPY 1 +#endif + +#ifndef FIO_ARRAY_TYPE +/** The type for array elements (an array of FIO_ARRAY_TYPE) */ +#define FIO_ARRAY_TYPE void * +/** An invalid value for that type (if any). */ +#define FIO_ARRAY_TYPE_INVALID NULL +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 +#else +#ifndef FIO_ARRAY_TYPE_INVALID +/** An invalid value for that type (if any). */ +#define FIO_ARRAY_TYPE_INVALID ((FIO_ARRAY_TYPE){0}) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 +#endif +#endif + +#ifndef FIO_ARRAY_TYPE_INVALID_SIMPLE +/** Is the FIO_ARRAY_TYPE_INVALID object memory is all zero? (yes = 1) */ +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 0 +#endif + +#ifndef FIO_ARRAY_TYPE_COPY +/** Handles a copy operation for an array's element. */ +#define FIO_ARRAY_TYPE_COPY(dest, src) (dest) = (src) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_COPY_SIMPLE 1 +#endif + +#ifndef FIO_ARRAY_TYPE_DESTROY +/** Handles a destroy / free operation for an array's element. */ +#define FIO_ARRAY_TYPE_DESTROY(obj) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_DESTROY_SIMPLE 1 +#endif + +#ifndef FIO_ARRAY_TYPE_CMP +/** Handles a comparison operation for an array's element. */ +#define FIO_ARRAY_TYPE_CMP(a, b) (a) == (b) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_CMP_SIMPLE 1 +#endif + +#ifndef FIO_ARRAY_TYPE_CONCAT_COPY +#define FIO_ARRAY_TYPE_CONCAT_COPY FIO_ARRAY_TYPE_COPY +#define FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE FIO_ARRAY_TYPE_COPY_SIMPLE +#endif +/** + * The FIO_ARRAY_DESTROY_AFTER_COPY macro should be set if + * FIO_ARRAY_TYPE_DESTROY should be called after FIO_ARRAY_TYPE_COPY when an + * object is removed from the array after being copied to an external container + * (an `old` pointer) + */ +#ifndef FIO_ARRAY_DESTROY_AFTER_COPY +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE && !FIO_ARRAY_TYPE_COPY_SIMPLE +#define FIO_ARRAY_DESTROY_AFTER_COPY 1 +#else +#define FIO_ARRAY_DESTROY_AFTER_COPY 0 +#endif +#endif + +/* Extra empty slots when allocating memory. */ +#ifndef FIO_ARRAY_PADDING +#define FIO_ARRAY_PADDING 4 +#endif + +/* + * Uses the array structure to embed object, if there's space for them. + * + * This optimizes small arrays and specifically touplets. For `void *` type + * arrays this allows for 2 objects to be embedded, resulting in faster access + * due to cache locality and reduced pointer redirection. + * + * For large arrays, it is better to disable this feature. + * + * Note: values larger than 1 add a memory allocation cost to the array + * container, adding enough room for at least `FIO_ARRAY_ENABLE_EMBEDDED - 1` + * items. + */ +#ifndef FIO_ARRAY_ENABLE_EMBEDDED +#define FIO_ARRAY_ENABLE_EMBEDDED 1 +#endif + +/* Sets memory growth to exponentially increase. Consumes more memory. */ +#ifndef FIO_ARRAY_EXPONENTIAL +#define FIO_ARRAY_EXPONENTIAL 0 +#endif + +#undef FIO_ARRAY_SIZE2WORDS +#define FIO_ARRAY_SIZE2WORDS(size) \ + ((sizeof(FIO_ARRAY_TYPE) & 1) ? (((size) & (~15)) + 16) \ + : (sizeof(FIO_ARRAY_TYPE) & 2) ? (((size) & (~7)) + 8) \ + : (sizeof(FIO_ARRAY_TYPE) & 4) ? (((size) & (~3)) + 4) \ + : (sizeof(FIO_ARRAY_TYPE) & 8) ? (((size) & (~1)) + 2) \ + : (size)) + +/* ***************************************************************************** +Dynamic Arrays - type +***************************************************************************** */ + +/** an Array type. */ +typedef struct FIO_NAME(FIO_ARRAY_NAME, s) { + /* start common header (with embedded array type) */ + /** the offset to the first item. */ + uint32_t start; + /** The offset to the first empty location the array. */ + uint32_t end; + /* end common header (with embedded array type) */ + /** The array's capacity - limited to 32bits, but we use the extra padding. */ + uint32_t capa; + /** a pointer to the array's memory (if not embedded) */ + FIO_ARRAY_TYPE *ary; +#if FIO_ARRAY_ENABLE_EMBEDDED > 1 + /** Do we wanted larger small-array optimizations? */ + FIO_ARRAY_TYPE + extra_memory_for_embedded_arrays[(FIO_ARRAY_ENABLE_EMBEDDED - 1)] +#endif +} FIO_NAME(FIO_ARRAY_NAME, s); + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_ARRAY_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, s) * +#endif + +/* ***************************************************************************** +Dynamic Arrays - API +***************************************************************************** */ + +#ifndef FIO_ARRAY_INIT +/* Initialization macro. */ +#define FIO_ARRAY_INIT \ + { 0 } +#endif + +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/* Allocates a new array object on the heap and initializes it's memory. */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); + +/* Frees an array's internal data AND it's container! */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* Destroys any objects stored in the array and frees the internal state. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary); + +/** Returns the number of elements in the Array. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary); + +/** Returns the current, temporary, array capacity (it's dynamic). */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary); + +/** + * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an + * error. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary); + +/** + * Returns a pointer to the C array containing the objects. + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary); + +/** + * Reserves a minimal capacity for additional elements to be added to the array. + * + * If `capa` is negative, new memory will be allocated at the beginning of the + * array rather then it's end. + * + * Returns the array's new capacity. + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary, + int64_t capa); + +/** + * Adds all the items in the `src` Array to the end of the `dest` Array. + * + * The `src` Array remain untouched. + * + * Always returns the destination array (`dest`). + */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest, + FIO_ARRAY_PTR src); + +/** + * Sets `index` to the value in `data`. + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + * + * If `old` isn't NULL, the existing data will be copied to the location pointed + * to by `old` before the copy in the Array is destroyed. + * + * Returns a pointer to the new object, or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary, + int64_t index, + FIO_ARRAY_TYPE data, + FIO_ARRAY_TYPE *old); + +/** + * Returns the value located at `index` (no copying is performed). + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + */ +FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary, + int64_t index); + +/** + * Returns the index of the object or (uint32_t)-1 if the object wasn't found. + * + * If `start_at` is negative (i.e., -1), than seeking will be performed in + * reverse, where -1 == last index (-2 == second to last, etc'). + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data, + int64_t start_at); + +/** + * Removes an object from the array, MOVING all the other objects to prevent + * "holes" in the data. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns 0 on success and -1 on error. + * + * This action is O(n) where n in the length of the array. + * It could get expensive. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary, + int64_t index, + FIO_ARRAY_TYPE *old); + +/** + * Removes all occurrences of an object from the array (if any), MOVING all the + * existing objects to prevent "holes" in the data. + * + * Returns the number of items removed. + * + * This action is O(n) where n in the length of the array. + * It could get expensive. + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); + +/** Attempts to lower the array's memory consumption. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary); + +/** + * Pushes an object to the end of the Array. Returns a pointer to the new object + * or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); + +/** + * Removes an object from the end of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary, FIO_ARRAY_TYPE *old); + +/** + * Unshifts an object to the beginning of the Array. Returns a pointer to the + * new object or NULL on error. + * + * This could be expensive, causing `memmove`. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); + +/** + * Removes an object from the beginning of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE *old); + +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_ARRAY_NAME, each_s) { + /** The array iterated. Once set, cannot be safely changed. */ + FIO_ARRAY_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_ARRAY_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; + /** The object / value at the current index. */ + FIO_ARRAY_TYPE value; + /* memory padding used for FIOBJ */ + uint64_t padding; +} FIO_NAME(FIO_ARRAY_NAME, each_s); + +/** + * Iteration using a callback for each entry in the array. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, + each)(FIO_ARRAY_PTR ary, + int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * + info), + void *udata, + int64_t start_at); + +#ifndef FIO_ARRAY_EACH +/** + * Iterates through the array using a `for` loop. + * + * Access the object with the pointer `pos`. The `pos` variable can be named + * however you please. + * + * Avoid editing the array during a FOR loop, although I hope it's possible, I + * wouldn't count on it. + * + * **Note**: this variant supports automatic pointer tagging / untagging. + */ +#define FIO_ARRAY_EACH(array_name, array, pos) \ + for (FIO_NAME(array_name, ____type_t) \ + *first___ai = NULL, \ + *pos = FIO_NAME(array_name, each_next)((array), &first___ai, NULL); \ + pos; \ + pos = FIO_NAME(array_name, each_next)((array), &first___ai, pos)) +#endif + +/** + * Returns a pointer to the (next) object in the array. + * + * Returns a pointer to the first object if `pos == NULL` and there are objects + * in the array. + * + * The first pointer is automatically set and it allows object insertions and + * memory effecting functions to be called from within the loop. + * + * If the object in `pos` (or an object before it) were removed, consider + * passing `pos-1` to the function, to avoid skipping any elements while + * looping. + * + * Returns the next object if both `first` and `pos` are valid. + * + * Returns NULL if `pos` was the last object or no object exist. + * + * Returns the first object if either `first` or `pos` are invalid. + * + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, + each_next)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE **first, + FIO_ARRAY_TYPE *pos); + +/* ***************************************************************************** +Dynamic Arrays - embedded arrays +***************************************************************************** */ +typedef struct { + /* start common header */ + /** the offset to the first item. */ + uint32_t start; + /** The offset to the first empty location the array. */ + uint32_t end; + /* end common header */ + FIO_ARRAY_TYPE embedded[]; +} FIO_NAME(FIO_ARRAY_NAME, ___embedded_s); + +#define FIO_ARRAY2EMBEDDED(a) ((FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) *)(a)) + +#if FIO_ARRAY_ENABLE_EMBEDDED +#define FIO_ARRAY_IS_EMBEDDED(a) \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ + (((a)->start > (a)->end) || !(a)->ary)) +#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ + (uintptr_t)(ptr) > (uintptr_t)(ary) && \ + (uintptr_t)(ptr) < (uintptr_t)((ary) + 1)) +#define FIO_ARRAY_EMBEDDED_CAPA \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) > \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) \ + ? 0 \ + : ((sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) - \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) / \ + sizeof(FIO_ARRAY_TYPE))) + +#else +#define FIO_ARRAY_IS_EMBEDDED(a) 0 +#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) 0 +#define FIO_ARRAY_EMBEDDED_CAPA 0 + +#endif /* FIO_ARRAY_ENABLE_EMBEDDED */ +/* ***************************************************************************** +Inlined functions +***************************************************************************** */ +/** Returns the number of elements in the Array. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return ary->end - ary->start; + case 1: return ary->start; + } + return 0; +} + +/** Returns the current, temporary, array capacity (it's dynamic). */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return (uint32_t)ary->capa; + case 1: return FIO_ARRAY_EMBEDDED_CAPA; + } + return 0; +} + +/** + * Returns a pointer to the C array containing the objects. + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return ary->ary + ary->start; + case 1: return FIO_ARRAY2EMBEDDED(ary)->embedded; + } + return NULL; +} + +/** + * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an + * error. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, -1); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + return FIO_ARRAY_IS_EMBEDDED(ary); + (void)ary; /* if unused (never embedded) */ +} + +/** + * Returns the value located at `index` (no copying is performed). + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + */ +FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary_, + int64_t index) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, FIO_ARRAY_TYPE_INVALID); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + FIO_ARRAY_TYPE *a; + size_t count; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + a = ary->ary + ary->start; + count = ary->end - ary->start; + break; + case 1: + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + count = ary->start; + break; + default: return FIO_ARRAY_TYPE_INVALID; + } + + if (index < 0) { + index += count; + if (index < 0) + return FIO_ARRAY_TYPE_INVALID; + } + if ((uint32_t)index >= count) + return FIO_ARRAY_TYPE_INVALID; + return a[index]; +} + +/* Returns a pointer to the (next) object in the array. */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, + each_next)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE **first, + FIO_ARRAY_TYPE *pos) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + int64_t count; + FIO_ARRAY_TYPE *a; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + count = ary->end - ary->start; + a = ary->ary + ary->start; + break; + case 1: + count = ary->start; + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + break; + default: return NULL; + } + intptr_t i; + if (!count || !first) + return NULL; + if (!pos || !(*first) || (*first) > pos) { + i = -1; + } else { + i = (intptr_t)(pos - (*first)); + } + *first = a; + ++i; + if (i >= count) + return NULL; + return i + a; +} + +/** Used internally for the EACH macro */ +typedef FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, ____type_t); + +/* ***************************************************************************** +Exported functions +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/* ***************************************************************************** +Helper macros +***************************************************************************** */ +#if FIO_ARRAY_EXPONENTIAL +#define FIO_ARRAY_ADD2CAPA(capa) (((capa) << 1) + FIO_ARRAY_PADDING) +#else +#define FIO_ARRAY_ADD2CAPA(capa) ((capa) + FIO_ARRAY_PADDING) +#endif + +/* ***************************************************************************** +Dynamic Arrays - internal helpers +***************************************************************************** */ + +#define FIO_ARRAY_POS2ABS(ary, pos) \ + (pos >= 0 ? (ary->start + pos) : (ary->end - pos)) + +#define FIO_ARRAY_AB_CT(cond, a, b) ((b) ^ ((0 - ((cond)&1)) & ((a) ^ (b)))) + +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, destroy)) +/* ***************************************************************************** +Dynamic Arrays - implementation +***************************************************************************** */ + +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new array object on the heap and initializes it's memory. */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void) { + FIO_NAME(FIO_ARRAY_NAME, s) *a = + (FIO_NAME(FIO_ARRAY_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*a), 0); + if (!FIO_MEM_REALLOC_IS_SAFE_ && a) { + *a = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; + } + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, s)); + return (FIO_ARRAY_PTR)FIO_PTR_TAG(a); +} + +/* Frees an array's internal data AND it's container! */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + FIO_NAME(FIO_ARRAY_NAME, destroy)(ary_); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, s)); + FIO_MEM_FREE_(ary, sizeof(*ary)); +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* Destroys any objects stored in the array and frees the internal state. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + union { + FIO_NAME(FIO_ARRAY_NAME, s) a; + FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) e; + } tmp = {.a = *ary}; + *ary = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; + + switch ( + FIO_NAME_BL(FIO_ARRAY_NAME, embedded)((FIO_ARRAY_PTR)FIO_PTR_TAG(&tmp))) { + case 0: +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE + for (size_t i = tmp.a.start; i < tmp.a.end; ++i) { + FIO_ARRAY_TYPE_DESTROY(tmp.a.ary[i]); + } +#endif + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); + FIO_MEM_FREE_(tmp.a.ary, tmp.a.capa * sizeof(*tmp.a.ary)); + return; + case 1: +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE + while (tmp.e.start--) { + FIO_ARRAY_TYPE_DESTROY((tmp.e.embedded[tmp.e.start])); + } +#endif + return; + } + return; +} + +/** Reserves a minimal capacity for the array. */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary_, + int64_t capa_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + if (capa_ > UINT32_MAX || capa_ < ((int64_t)0LL - UINT32_MAX)) + return ary->capa; + uint32_t abs_capa = ((capa_ >= 0) ? (uint32_t)capa_ : (uint32_t)(0 - capa_)); + uint32_t capa; + FIO_ARRAY_TYPE *tmp; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + abs_capa += ary->end - ary->start; + capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); + if (abs_capa <= ary->capa) + return (uint32_t)ary->capa; + /* objects don't move, use only realloc */ + if ((capa_ >= 0) || (capa_ < 0 && ary->start > 0)) { + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(ary->ary, + 0, + sizeof(*tmp) * capa, + sizeof(*tmp) * ary->end); + if (!tmp) + return (uint32_t)ary->capa; + if (!ary->ary) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + ary->capa = capa; + ary->ary = tmp; + return capa; + } else { /* moving objects, starting with a fresh piece of memory */ + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); + const uint32_t count = ary->end - ary->start; + if (!tmp) + return (uint32_t)ary->capa; + if (!ary->ary) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + if (capa_ >= 0) { /* copy items at beginning of memory stack */ + if (count) { + FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*tmp)); + } + FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = count, + .capa = capa, + .ary = tmp, + }; + return capa; + } else { /* copy items at ending of memory stack */ + if (count) { + FIO_MEMCPY(tmp + (capa - count), + ary->ary + ary->start, + count * sizeof(*tmp)); + } + FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (capa - count), + .end = capa, + .capa = capa, + .ary = tmp, + }; + } + } + return capa; + case 1: + abs_capa += ary->start; + capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); + if (abs_capa <= FIO_ARRAY_EMBEDDED_CAPA) + return FIO_ARRAY_EMBEDDED_CAPA; + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); + if (!tmp) + return FIO_ARRAY_EMBEDDED_CAPA; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + if (capa_ >= 0) { + /* copy items at beginning of memory stack */ + if (ary->start) { + FIO_MEMCPY(tmp, + FIO_ARRAY2EMBEDDED(ary)->embedded, + ary->start * sizeof(*tmp)); + } + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = ary->start, + .capa = capa, + .ary = tmp, + }; + return capa; + } + /* copy items at ending of memory stack */ + if (ary->start) { + FIO_MEMCPY(tmp + (capa - ary->start), + FIO_ARRAY2EMBEDDED(ary)->embedded, + ary->start * sizeof(*tmp)); + } + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (capa - ary->start), + .end = capa, + .capa = capa, + .ary = tmp, + }; + return capa; + default: return 0; + } +} + +/** + * Adds all the items in the `src` Array to the end of the `dest` Array. + * + * The `src` Array remain untouched. + * + * Returns `dest` on success or NULL on error (i.e., no memory). + */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest_, + FIO_ARRAY_PTR src_) { + FIO_PTR_TAG_VALID_OR_RETURN(dest_, (FIO_ARRAY_PTR)NULL); + FIO_PTR_TAG_VALID_OR_RETURN(src_, (FIO_ARRAY_PTR)NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *dest = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), dest_); + FIO_NAME(FIO_ARRAY_NAME, s) *src = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), src_); + if (!dest || !src) + return dest_; + const uint32_t offset = FIO_NAME(FIO_ARRAY_NAME, count)(dest_); + const uint32_t added = FIO_NAME(FIO_ARRAY_NAME, count)(src_); + const uint32_t total = offset + added; + if (!added) + return dest_; + + if (total < offset || total + offset < total) + return NULL; /* item count overflow */ + + const uint32_t capa = FIO_NAME(FIO_ARRAY_NAME, reserve)(dest_, added); + + if (!FIO_ARRAY_IS_EMBEDDED(dest) && dest->start + total > capa) { + /* we need to move the existing items due to the offset */ + FIO_MEMMOVE(dest->ary, + dest->ary + dest->start, + (dest->end - dest->start) * sizeof(*dest->ary)); + dest->start = 0; + dest->end = offset; + } +#if FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE + /* copy data */ + FIO_MEMCPY(FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_) + offset, + FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_), + added); +#else + { + FIO_ARRAY_TYPE *const a1 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_); + FIO_ARRAY_TYPE *const a2 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_); + for (uint32_t i = 0; i < added; ++i) { + FIO_ARRAY_TYPE_CONCAT_COPY(a1[i + offset], a2[i]); + } + } +#endif /* FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE */ + /* update dest */ + if (!FIO_ARRAY_IS_EMBEDDED(dest)) { + dest->end += added; + return dest_; + } else + dest->start = total; + return dest_; +} + +/** + * Sets `index` to the value in `data`. + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + * + * If `old` isn't NULL, the existing data will be copied to the location pointed + * to by `old` before the copy in the Array is destroyed. + * + * Returns a pointer to the new object, or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary_, + int64_t index, + FIO_ARRAY_TYPE data, + FIO_ARRAY_TYPE *old) { + FIO_ARRAY_TYPE *a = NULL; + FIO_NAME(FIO_ARRAY_NAME, s) * ary; + uint32_t count; + uint8_t pre_existing = 1; + + FIO_PTR_TAG_VALID_OR_GOTO(ary_, invalid); + + ary = FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + + if (index < 0) { + index += count; + if (index < 0) + goto negative_expansion; + } + + if ((size_t)index > 0xFFFFFFFFULL) + goto invalid; + + if ((uint32_t)index >= count) { + if ((uint32_t)index == count) + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, FIO_ARRAY_ADD2CAPA(index)); + else + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (uint32_t)index + 1); + if (FIO_ARRAY_IS_EMBEDDED(ary)) + goto expand_embedded; + goto expansion; + } + + a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + +done: + + /* copy / clear object */ + if (pre_existing) { + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], a[index]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(a[index]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(a[index]); + } + } else if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + } + FIO_ARRAY_TYPE_COPY(a[index], FIO_ARRAY_TYPE_INVALID); + FIO_ARRAY_TYPE_COPY(a[index], data); + return a + index; + +expansion: + + pre_existing = 0; + a = ary->ary; + { + uint8_t was_moved = 0; + /* test if we need to move objects to make room at the end */ + if (ary->start + (uint32_t)index >= ary->capa) { + FIO_MEMMOVE(ary->ary, ary->ary + ary->start, (count) * sizeof(*ary->ary)); + ary->start = 0; + ary->end = (uint32_t)index + 1; + was_moved = 1; + } + /* initialize memory in between objects */ + if (was_moved || !FIO_MEM_REALLOC_IS_SAFE_ || + !FIO_ARRAY_TYPE_INVALID_SIMPLE) { +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a + count, 0, ((uint32_t)index - count) * sizeof(*ary->ary)); +#else + for (size_t i = count; i <= (size_t)index; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + } + ary->end = (uint32_t)index + 1; + } + goto done; + +expand_embedded: + pre_existing = 0; + ary->start = (uint32_t)index + 1; + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + goto done; + +negative_expansion: + pre_existing = 0; + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (index - count)); + index = 0 - index; + if (index > ary->capa) + goto invalid; + if ((FIO_ARRAY_IS_EMBEDDED(ary))) + goto negative_expansion_embedded; + a = ary->ary; + if (index > (int32_t)ary->start) { + FIO_MEMMOVE(a + index, a + ary->start, count * sizeof(*a)); + ary->end = (uint32_t)index + count; + ary->start = (uint32_t)index; + } + index = ary->start - (uint32_t)index; + if ((uint32_t)(index + 1) < ary->start) { +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a + index, 0, (ary->start - index) * (sizeof(*a))); +#else + for (size_t i = index; i < (size_t)ary->start; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + } + ary->start = (uint32_t)index; + goto done; + +negative_expansion_embedded: + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + FIO_MEMMOVE(a + index, a, count * count * sizeof(*a)); +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a, 0, index * (sizeof(a))); +#else + for (size_t i = 0; i < (size_t)index; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + index = 0; + goto done; + +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + } + + return a; +} + +/** + * Returns the index of the object or (uint32_t)-1 if the object wasn't found. + * + * If `start_at` is negative (i.e., -1), than seeking will be performed in + * reverse, where -1 == last index (-2 == second to last, etc'). + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data, + int64_t start_at) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + if (!a) + return -1; + size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + if (start_at >= 0) { + /* seek forwards */ + if ((uint32_t)start_at >= count) + start_at = (int32_t)count; + while ((uint32_t)start_at < count) { + if (FIO_ARRAY_TYPE_CMP(a[start_at], data)) + return (uint32_t)start_at; + ++start_at; + } + } else { + /* seek backwards */ + if (start_at + (int32_t)count < 0) + return -1; + count += start_at; + count += 1; + while (count--) { + if (FIO_ARRAY_TYPE_CMP(a[count], data)) + return (uint32_t)count; + } + } + return -1; +} + +/** + * Removes an object from the array, MOVING all the other objects to prevent + * "holes" in the data. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns 0 on success and -1 on error. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary_, + int64_t index, + FIO_ARRAY_TYPE *old) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count; + if (!a) + goto invalid; + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + + if (index < 0) { + index += count; + if (index < 0) { + FIO_LOG_WARNING( + FIO_MACRO2STR(FIO_NAME(FIO_ARRAY_NAME, + remove)) " called with a negative index lower " + "than the element count."); + goto invalid; + } + } + if ((uint32_t)index >= count) + goto invalid; + if (!index) { + FIO_NAME(FIO_ARRAY_NAME, shift)(ary_, old); + return 0; + } + if ((uint32_t)index + 1 == count) { + FIO_NAME(FIO_ARRAY_NAME, pop)(ary_, old); + return 0; + } + + if (old) { + FIO_ARRAY_TYPE_COPY(*old, a[index]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(a[index]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(a[index]); + } + + if ((uint32_t)(index + 1) < count) { + FIO_MEMMOVE(a + index, a + index + 1, (count - (index + 1)) * sizeof(*a)); + } + FIO_ARRAY_TYPE_COPY((a + (count - 1))[0], FIO_ARRAY_TYPE_INVALID); + + if (FIO_ARRAY_IS_EMBEDDED(ary)) + goto embedded; + --ary->end; + return 0; + +embedded: + --ary->start; + return 0; + +invalid: + if (old) { + FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY_TYPE_INVALID); + } + return -1; +} + +/** + * Removes all occurrences of an object from the array (if any), MOVING all the + * existing objects to prevent "holes" in the data. + * + * Returns the number of items removed. + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + size_t c = 0; + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count; + if (!a) + return (uint32_t)c; + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + + size_t i = 0; + while ((i + c) < count) { + if (!(FIO_ARRAY_TYPE_CMP(a[i + c], data))) { + a[i] = a[i + c]; + ++i; + continue; + } + FIO_ARRAY_TYPE_DESTROY(a[i + c]); + ++c; + } + if (c && FIO_MEM_REALLOC_IS_SAFE_) { + /* keep memory zeroed out */ + FIO_MEMSET(a + i, 0, sizeof(*a) * c); + } + if (!FIO_ARRAY_IS_EMBEDDED_PTR(ary, a)) { + ary->end = (uint32_t)(ary->start + i); + return (uint32_t)c; + } + ary->start = (uint32_t)i; + return (uint32_t)c; +} + +/** Attempts to lower the array's memory consumption. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + FIO_ARRAY_TYPE *tmp = NULL; + + if (count <= FIO_ARRAY_EMBEDDED_CAPA) + goto re_embed; + + tmp = (FIO_ARRAY_TYPE *) + FIO_MEM_REALLOC_(NULL, 0, (ary->end - ary->start) * sizeof(*tmp), 0); + if (!tmp) + return; + FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*ary->ary)); + FIO_MEM_FREE_(ary->ary, ary->capa * sizeof(*ary->ary)); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = (ary->end - ary->start), + .capa = (ary->end - ary->start), + .ary = tmp, + }; + return; + +re_embed: + if (!FIO_ARRAY_IS_EMBEDDED(ary)) { + tmp = ary->ary; + uint32_t offset = ary->start; + size_t old_capa = ary->capa; + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (uint32_t)count, + }; + if (count) { + FIO_MEMCPY(FIO_ARRAY2EMBEDDED(ary)->embedded, + tmp + offset, + count * sizeof(*tmp)); + } + if (tmp) { + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); + FIO_MEM_FREE_(tmp, sizeof(*tmp) * old_capa); + (void)old_capa; /* if unused */ + } + } + return; +} +/** + * Pushes an object to the end of the Array. Returns NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->capa) { + if (!ary->start) { + if (FIO_NAME(FIO_ARRAY_NAME, + reserve)(ary_, (uint32_t)FIO_ARRAY_ADD2CAPA(ary->capa)) == + ary->end) + goto invalid; + } else { + const uint32_t new_start = (ary->start >> 2); + const uint32_t count = ary->end - ary->start; + if (count) + FIO_MEMMOVE(ary->ary + new_start, + ary->ary + ary->start, + count * sizeof(*ary->ary)); + ary->end = count + new_start; + ary->start = new_start; + } + } + FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); + return ary->ary + (ary->end++); + + case 1: + if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) + goto needs_memory_embedded; + FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start], data); + return FIO_ARRAY2EMBEDDED(ary)->embedded + (ary->start++); + } +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + return NULL; + +needs_memory_embedded: + if (FIO_NAME(FIO_ARRAY_NAME, + reserve)(ary_, FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA)) == + FIO_ARRAY_EMBEDDED_CAPA) + goto invalid; + FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); + return ary->ary + (ary->end++); +} + +/** + * Removes an object from the end of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE *old) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->start) + return -1; + --ary->end; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->end]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); + } + return 0; + case 1: + if (!ary->start) + return -1; + --ary->start; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); + } + FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, + 0, + sizeof(*ary->ary)); + return 0; + } + if (old) + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + return -1; +} + +/** + * Unshifts an object to the beginning of the Array. Returns -1 on error. + * + * This could be expensive, causing `memmove`. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (!ary->start) { + if (ary->end == ary->capa) { + FIO_NAME(FIO_ARRAY_NAME, reserve) + (ary_, (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(ary->capa))); + if (!ary->start) + goto invalid; + } else { + const uint32_t new_end = + (uint32_t)(ary->capa - ((ary->capa - ary->end) >> 2)); + const uint32_t count = (uint32_t)(ary->end - ary->start); + const uint32_t new_start = (uint32_t)(new_end - count); + if (count) + FIO_MEMMOVE(ary->ary + new_start, + ary->ary + ary->start, + count * sizeof(*ary->ary)); + ary->end = new_end; + ary->start = new_start; + } + } + FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); + return ary->ary + ary->start; + + case 1: + if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) + goto needs_memory_embed; + if (ary->start) + FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded + 1, + FIO_ARRAY2EMBEDDED(ary)->embedded, + sizeof(*ary->ary) * ary->start); + ++ary->start; + FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[0], data); + return FIO_ARRAY2EMBEDDED(ary)->embedded; + } +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + return NULL; + +needs_memory_embed: + if (FIO_NAME(FIO_ARRAY_NAME, reserve)( + ary_, + (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA))) == + FIO_ARRAY_EMBEDDED_CAPA) + goto invalid; + FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); + return ary->ary + ary->start; +} + +/** + * Removes an object from the beginning of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE *old) { + + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->start) + return -1; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->start]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); + } + ++ary->start; + return 0; + case 1: + if (!ary->start) + return -1; + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY2EMBEDDED(ary)->embedded[0]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); + } + --ary->start; + if (ary->start) + FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded, + FIO_ARRAY2EMBEDDED(ary)->embedded + + FIO_ARRAY2EMBEDDED(ary)->start, + FIO_ARRAY2EMBEDDED(ary)->start * + sizeof(*FIO_ARRAY2EMBEDDED(ary)->embedded)); + FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, + 0, + sizeof(*ary->ary)); + return 0; + } + if (old) + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + return -1; +} + +/** + * Iteration using a callback for each entry in the array. + * + * The callback task function must accept an the entry data as well as an opaque + * user pointer. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, + each)(FIO_ARRAY_PTR ary_, + int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * + info), + void *udata, + int64_t start_at) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + if (!a) + return (uint32_t)-1; + + uint32_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + + if (start_at < 0) { + start_at = count - start_at; + if (start_at < 0) + start_at = 0; + } + + if (!a || !task) + return (uint32_t)-1; + + if ((uint32_t)start_at >= count) + return (uint32_t)count; + + FIO_NAME(FIO_ARRAY_NAME, each_s) + e = { + .parent = ary_, + .index = (uint64_t)start_at, + .task = task, + .udata = udata, + }; + + while ((uint32_t)e.index < FIO_NAME(FIO_ARRAY_NAME, count)(ary_)) { + a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + e.value = a[e.index]; + int r = e.task(&e); + ++e.index; + if (r == -1) { + return (uint32_t)(e.index); + } + } + return (uint32_t)e.index; +} + +/* ***************************************************************************** +Dynamic Arrays - test +***************************************************************************** */ +#ifdef FIO_TEST_ALL + +/* make suer the functions are defined for the testing */ +#ifdef FIO_REF_CONSTRUCTOR_ONLY +IFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); +IFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +#define FIO_ARRAY_TEST_OBJ_SET(dest, val) \ + FIO_MEMSET(&(dest), (int)(val), sizeof(FIO_ARRAY_TYPE)) +#define FIO_ARRAY_TEST_OBJ_IS(val) \ + (!FIO_MEMCMP(&o, \ + FIO_MEMSET(&v, (int)(val), sizeof(v)), \ + sizeof(FIO_ARRAY_TYPE))) + +FIO_SFUNC int FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task))( + FIO_NAME(FIO_ARRAY_NAME, each_s) * i) { + struct data_s { + int i; + int va[]; + } *d = (struct data_s *)i->udata; + FIO_ARRAY_TYPE v; + + FIO_ARRAY_TEST_OBJ_SET(v, d->va[d->i]); + ++d->i; + if (d->va[d->i + 1]) + return 0; + return -1; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_ARRAY_NAME)(void) { + FIO_ARRAY_TYPE o; + FIO_ARRAY_TYPE v; + FIO_NAME(FIO_ARRAY_NAME, s) a_on_stack = FIO_ARRAY_INIT; + FIO_ARRAY_PTR a_array[2]; + a_array[0] = (FIO_ARRAY_PTR)FIO_PTR_TAG((&a_on_stack)); + a_array[1] = FIO_NAME(FIO_ARRAY_NAME, new)(); + FIO_ASSERT_ALLOC(a_array[1]); + /* perform test twice, once for an array on the stack and once for allocate */ + for (int selector = 0; selector < 2; ++selector) { + FIO_ARRAY_PTR a = a_array[selector]; + fprintf(stderr, + "* Testing dynamic arrays on the %s (" FIO_MACRO2STR( + FIO_NAME(FIO_ARRAY_NAME, + s)) ").\n" + " This type supports %zu embedded items\n", + (selector ? "heap" : "stack"), + FIO_ARRAY_EMBEDDED_CAPA); + /* Test start here */ + + /* test push */ + for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + o = *FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push-get cycle failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "get with -1 returned wrong result (%d)", + i); + } + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 3, + "push didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); + + /* test pop */ + for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { + FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), + "pop value error failed (%d)", + i); + } + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "pop didn't pop all elements?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o), + "pop for empty array should return an error."); + + /* test compact with zero elements */ + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "compact zero elementes didn't make array embedded?"); + + /* test unshift */ + for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + o = *FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "shift failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "unshift-get cycle failed (%d)", + i); + int64_t negative_index = 0 - (((int)(FIO_ARRAY_EMBEDDED_CAPA) + 3) - i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, negative_index); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "get with %d returned wrong result.", + negative_index); + } + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 3, + "unshift didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); + + /* test shift */ + for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { + FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), + "shift value error failed (%d)", + i); + } + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "shift didn't shift all elements?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o), + "shift for empty array should return an error."); + + /* test set from embedded? array */ + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "compact zero elementes didn't make array embedded (2)?"); + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + if (FIO_ARRAY_EMBEDDED_CAPA) { + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow from embedded array should reset `old`"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 1, + "set didn't update count correctly from embedded " + "array (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)FIO_ARRAY_EMBEDDED_CAPA); + } + + /* test set from bigger array */ + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, set) + (a, ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), o, &o); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow should reset `old`"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "set capa should be above item count"); + if (FIO_ARRAY_EMBEDDED_CAPA) { + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); + FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), + "set overflow lost last item while growing."); + } + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, (FIO_ARRAY_EMBEDDED_CAPA + 1) * 2); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow should have memory in the middle set to invalid " + "objetcs."); + FIO_ARRAY_TEST_OBJ_SET(o, 2); + FIO_NAME(FIO_ARRAY_NAME, set)(a, 0, o, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), + "set should set `old` to previous value"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set item count error"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set capa should be above item count"); + + /* test find TODO: test with uninitialized array */ + FIO_ARRAY_TEST_OBJ_SET(o, 99); + if (FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID)) { + FIO_ARRAY_TEST_OBJ_SET(o, 100); + } + uint32_t found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 0); + FIO_ASSERT(found == (uint32_t)-1, + "seeking for an object that doesn't exist should fail."); + FIO_ARRAY_TEST_OBJ_SET(o, 1); + found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 1); + FIO_ASSERT(found == ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "seeking for an object returned the wrong index."); + FIO_ASSERT(found == FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -1), + "seeking for an object in reverse returned the wrong index."); + FIO_ARRAY_TEST_OBJ_SET(o, 2); + FIO_ASSERT( + !FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -2), + "seeking for an object in reverse (2) returned the wrong index."); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "find should have side-effects - count error"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "find should have side-effects - capa error"); + + /* test remove */ + FIO_NAME(FIO_ARRAY_NAME, remove)(a, found, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), "remove didn't copy old data?"); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(2), "remove removed more?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "remove with didn't update count correctly (%d != %s)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + + /* test remove2 */ + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); + FIO_ASSERT((found = FIO_NAME(FIO_ARRAY_NAME, remove2)(a, o)) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1, + "remove2 result error, %d != %d items.", + found, + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == 1, + "remove2 didn't update count correctly (%d != 1)", + FIO_NAME(FIO_ARRAY_NAME, count)(a)); + + /* hopefuly these will end... or crash on error. */ + while (!FIO_NAME(FIO_ARRAY_NAME, pop)(a, NULL)) { + ; + } + while (!FIO_NAME(FIO_ARRAY_NAME, shift)(a, NULL)) { + ; + } + + /* test push / unshift alternate */ + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + for (int i = 0; i < 4096; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) + 1 == + ((uint32_t)(i + 1) << 1), + "push-unshift[%d.5] cycle count arror (%d != %d)", + i, + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (((uint32_t)(i + 1) << 1)) - 1); + FIO_ARRAY_TEST_OBJ_SET(o, (i + 4097)); + FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == ((uint32_t)(i + 1) << 1), + "push-unshift[%d] cycle count arror (%d != %d)", + i, + FIO_NAME(FIO_ARRAY_NAME, count)(a), + ((uint32_t)(i + 1) << 1)); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 4097), + "unshift-push cycle failed (%d)", + i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "push-shift cycle failed (%d)", + i); + } + for (int i = 0; i < 4096; ++i) { + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((4096 * 2) - i), + "item value error at index %d", + i); + } + for (int i = 0; i < 4096; ++i) { + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i + 4096); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((1 + i)), + "item value error at index %d", + i + 4096); + } +#if DEBUG + for (int i = 0; i < 2; ++i) { + FIO_LOG_DEBUG2( + "\t- " FIO_MACRO2STR( + FIO_NAME(FIO_ARRAY_NAME, s)) " after push/unshit cycle%s:\n" + "\t\t- item count: %d items\n" + "\t\t- capacity: %d items\n" + "\t\t- memory: %d bytes\n", + (i ? " after compact" : ""), + FIO_NAME(FIO_ARRAY_NAME, count)(a), + FIO_NAME(FIO_ARRAY_NAME, capa)(a), + FIO_NAME(FIO_ARRAY_NAME, capa)(a) * sizeof(FIO_ARRAY_TYPE)); + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + } +#endif /* DEBUG */ + + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); +/* test set with NULL, hopefully a bug will cause a crash */ +#if FIO_ARRAY_TYPE_DESTROY_SIMPLE + for (int i = 0; i < 4096; ++i) { + FIO_NAME(FIO_ARRAY_NAME, set)(a, i, o, NULL); + } +#else + /* + * we need to clear the memory to make sure a cleanup actions don't get + * unexpected values. + */ + for (int i = 0; i < (4096 * 2); ++i) { + FIO_ARRAY_TYPE_COPY((FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)[i]), + FIO_ARRAY_TYPE_INVALID); + } + +#endif + + /* TODO: test concat */ + + /* test each */ + { + struct data_s { + int i; + int va[10]; + } d = {1, {1, 8, 2, 7, 3, 6, 4, 5}}; + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + for (int i = 0; d.va[i]; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, d.va[i]); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + } + + int index = FIO_NAME(FIO_ARRAY_NAME, each)( + a, + FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task)), + (void *)&d, + d.i); + FIO_ASSERT(index == d.i, + "index rerturned from each should match next object"); + FIO_ASSERT(*(char *)&d.va[d.i], + "array each error (didn't stop in time?)."); + FIO_ASSERT(!(*(char *)&d.va[d.i + 1]), + "array each error (didn't stop in time?)."); + } +#if FIO_ARRAY_TYPE_DESTROY_SIMPLE + { + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + size_t max_items = 63; + FIO_ARRAY_TYPE tmp[64]; + for (size_t i = 0; i < max_items; ++i) { + FIO_MEMSET(tmp + i, i + 1, sizeof(*tmp)); + } + for (size_t items = 0; items <= max_items; items = ((items << 1) | 1)) { + FIO_LOG_DEBUG2("* testing the FIO_ARRAY_EACH macro with %zu items.", + items); + size_t i = 0; + for (i = 0; i < items; ++i) + FIO_NAME(FIO_ARRAY_NAME, push)(a, tmp[i]); + i = 0; + FIO_ARRAY_EACH(FIO_ARRAY_NAME, a, pos) { + FIO_ASSERT(!memcmp(tmp + i, pos, sizeof(*pos)), + "FIO_ARRAY_EACH pos is at wrong index %zu != %zu", + (size_t)(pos - FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)), + i); + ++i; + } + FIO_ASSERT(i == items, + "FIO_ARRAY_EACH macro count error - didn't review all " + "items? %zu != %zu ", + i, + items); + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + } + } +#endif + /* test destroy */ + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "destroy didn't clear count."); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "destroy capa error."); + /* Test end here */ + } + FIO_NAME(FIO_ARRAY_NAME, free)(a_array[1]); +} +#undef FIO_ARRAY_TEST_OBJ_SET +#undef FIO_ARRAY_TEST_OBJ_IS + +#endif /* FIO_TEST_ALL */ +/* ***************************************************************************** +Dynamic Arrays - cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_ARRAY_NAME */ + +#undef FIO_ARRAY_NAME +#undef FIO_ARRAY_TYPE +#undef FIO_ARRAY_ENABLE_EMBEDDED +#undef FIO_ARRAY_TYPE_INVALID +#undef FIO_ARRAY_TYPE_INVALID_SIMPLE +#undef FIO_ARRAY_TYPE_COPY +#undef FIO_ARRAY_TYPE_COPY_SIMPLE +#undef FIO_ARRAY_TYPE_CONCAT_COPY +#undef FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE +#undef FIO_ARRAY_TYPE_DESTROY +#undef FIO_ARRAY_TYPE_DESTROY_SIMPLE +#undef FIO_ARRAY_DESTROY_AFTER_COPY +#undef FIO_ARRAY_TYPE_CMP +#undef FIO_ARRAY_TYPE_CMP_SIMPLE +#undef FIO_ARRAY_PADDING +#undef FIO_ARRAY_SIZE2WORDS +#undef FIO_ARRAY_POS2ABS +#undef FIO_ARRAY_AB_CT +#undef FIO_ARRAY_PTR +#undef FIO_ARRAY_EXPONENTIAL +#undef FIO_ARRAY_ADD2CAPA +#undef FIO_ARRAY_IS_EMBEDDED +#undef FIO_ARRAY_IS_EMBEDDED_PTR +#undef FIO_ARRAY_EMBEDDED_CAPA +#undef FIO_ARRAY2EMBEDDED +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MAP_NAME map /* Development inclusion - ignore line */ +#define FIO_MAP_KEY size_t /* Development inclusion - ignore line */ +// #define FIO_MAP_VALUE_BSTR /* Development inclusion - ignore line */ +// #define FIO_MAP_ORDERED /* Development inclusion - ignore line */ +#define FIO_MAP_TEST /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Unordered/Ordered Map Implementation + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MAP_NAME) +/* ***************************************************************************** +Map Settings - Sets have only keys (value == key) - Hash Maps have values +***************************************************************************** */ + +/* if FIO_MAP_KEY_KSTR is defined, use fio_keystr_s keys */ +#ifdef FIO_MAP_KEY_KSTR +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL fio_keystr_s +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP_NAME, __key_alloc)) +#define FIO_MAP_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) \ + fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP_NAME, __key_free)) +#define FIO_MAP_KEY_DISCARD(key) +FIO_SFUNC void *FIO_NAME(FIO_MAP_NAME, __key_alloc)(size_t len) { + return FIO_MEM_REALLOC_(NULL, 0, len, 0); +} +FIO_SFUNC void FIO_NAME(FIO_MAP_NAME, __key_free)(void *ptr, size_t len) { + FIO_MEM_FREE_(ptr, len); + (void)len; /* if unused */ +} +#undef FIO_MAP_KEY_KSTR + +/* if FIO_MAP_KEY is undefined, assume String keys (using `fio_bstr`). */ +#elif !defined(FIO_MAP_KEY) || defined(FIO_MAP_KEY_BSTR) +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_DISCARD(key) +#endif +#undef FIO_MAP_KEY_BSTR + +#ifndef FIO_MAP_KEY_INTERNAL +#define FIO_MAP_KEY_INTERNAL FIO_MAP_KEY +#endif + +#ifndef FIO_MAP_KEY_FROM_INTERNAL +#define FIO_MAP_KEY_FROM_INTERNAL(o) o +#endif + +#ifndef FIO_MAP_KEY_COPY +#define FIO_MAP_KEY_COPY(dest, src) ((dest) = (src)) +#endif + +#ifndef FIO_MAP_KEY_CMP +#define FIO_MAP_KEY_CMP(a, b) ((a) == (b)) +#endif + +#ifndef FIO_MAP_KEY_DESTROY +#define FIO_MAP_KEY_DESTROY(o) +#define FIO_MAP_KEY_DESTROY_SIMPLE 1 +#endif + +#ifndef FIO_MAP_KEY_DISCARD +#define FIO_MAP_KEY_DISCARD(o) +#endif + +/* FIO_MAP_HASH_FN(key) - used instead of providing a hash value. */ +#ifndef FIO_MAP_HASH_FN +#undef FIO_MAP_RECALC_HASH +#endif + +/* FIO_MAP_RECALC_HASH - if true, hash values won't be cached. */ +#ifndef FIO_MAP_RECALC_HASH +#define FIO_MAP_RECALC_HASH 0 +#endif + +#ifdef FIO_MAP_VALUE_BSTR +#define FIO_MAP_VALUE fio_str_info_s +#define FIO_MAP_VALUE_INTERNAL char * +#define FIO_MAP_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) +#define FIO_MAP_VALUE_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP_VALUE_DESTROY(v) fio_bstr_free((v)) +#define FIO_MAP_VALUE_DISCARD(v) +#endif + +#ifdef FIO_MAP_VALUE +#define FIO_MAP_GET_T FIO_MAP_VALUE +#else +#define FIO_MAP_GET_T FIO_MAP_KEY +#endif + +#ifndef FIO_MAP_VALUE_INTERNAL +#define FIO_MAP_VALUE_INTERNAL FIO_MAP_VALUE +#endif + +#ifndef FIO_MAP_VALUE_FROM_INTERNAL +#ifdef FIO_MAP_VALUE +#define FIO_MAP_VALUE_FROM_INTERNAL(o) o +#else +#define FIO_MAP_VALUE_FROM_INTERNAL(o) +#endif +#endif + +#ifndef FIO_MAP_VALUE_COPY +#ifdef FIO_MAP_VALUE +#define FIO_MAP_VALUE_COPY(dest, src) (dest) = (src) +#else +#define FIO_MAP_VALUE_COPY(dest, src) +#endif +#endif + +#ifndef FIO_MAP_VALUE_DESTROY +#define FIO_MAP_VALUE_DESTROY(o) +#define FIO_MAP_VALUE_DESTROY_SIMPLE 1 +#endif + +#ifndef FIO_MAP_VALUE_DISCARD +#define FIO_MAP_VALUE_DISCARD(o) +#endif + +#ifdef FIO_MAP_LRU +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 1 /* required for least recently used order */ +#endif + +/* test if FIO_MAP_ORDERED was defined as an empty macro */ +#if defined(FIO_MAP_ORDERED) && ((0 - FIO_MAP_ORDERED - 1) == 1) +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 1 /* assume developer's intention */ +#endif + +#ifndef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 0 +#endif + +/* ***************************************************************************** +Pointer Tagging Support +***************************************************************************** */ + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_MAP_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, s) * +#endif +#define FIO_MAP_T FIO_NAME(FIO_MAP_NAME, s) + +/* ***************************************************************************** +Map Types +***************************************************************************** */ + +#ifndef FIO_MAP_INIT +/* Initialization macro. */ +#define FIO_MAP_INIT \ + { 0 } +#endif + +/** internal object data representation */ +typedef struct FIO_NAME(FIO_MAP_NAME, node_s) FIO_NAME(FIO_MAP_NAME, node_s); + +/** A Hash Map / Set type */ +typedef struct FIO_NAME(FIO_MAP_NAME, s) { + uint32_t bits; + uint32_t count; + FIO_NAME(FIO_MAP_NAME, node_s) * map; +#if FIO_MAP_ORDERED + FIO_INDEXED_LIST32_HEAD head; +#endif +} FIO_NAME(FIO_MAP_NAME, s); + +/** internal object data representation */ +struct FIO_NAME(FIO_MAP_NAME, node_s) { +#if !FIO_MAP_RECALC_HASH + uint64_t hash; +#endif + FIO_MAP_KEY_INTERNAL key; +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL value; +#endif +#if FIO_MAP_ORDERED + FIO_INDEXED_LIST32_NODE node; +#endif +}; + +/** Map iterator type */ +typedef struct { + /** the node in the internal map */ + FIO_NAME(FIO_MAP_NAME, node_s) * node; + /** the key in the current position */ + FIO_MAP_KEY key; +#ifdef FIO_MAP_VALUE + /** the value in the current position */ + FIO_MAP_VALUE value; +#endif +#if !FIO_MAP_RECALC_HASH + /** the hash for the current position */ + uint64_t hash; +#endif + struct { /* internal usage, do not access */ + uint32_t index; /* the index in the internal map */ + uint32_t pos; /* the position in the ordering scheme */ + uintptr_t map_validator; /* map mutation guard */ + } private_; +} FIO_NAME(FIO_MAP_NAME, iterator_s); + +/* ***************************************************************************** +Construction / Deconstruction +***************************************************************************** */ + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void); + +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map); + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** Destroys the object, re-initializing its container. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map); + +/* ***************************************************************************** +Map State +***************************************************************************** */ + +/** Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map); + +/** The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map); + +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa); + +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * node); + +/** Returns the hash value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node); + +#ifdef FIO_MAP_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node); +#endif + +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node); + +#ifdef FIO_MAP_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node); +#endif + +/* ***************************************************************************** +Adding / Removing Elements from the Map +***************************************************************************** */ + +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key, +#if defined(FIO_MAP_VALUE) + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +); + +/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, + size_t number_of_elements); + +/** Removes all objects from the map, without releasing the map's resources. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map); + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map); + +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key); + +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE obj, + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY key +#endif +); + +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key +#ifdef FIO_MAP_VALUE + , + FIO_MAP_VALUE obj +#endif +); + +/** + * The core set function. + * + * This function returns `NULL` on error (errors are logged). + * + * If the map is a hash map, overwriting the value (while keeping the key) is + * possible. In this case the `old` pointer is optional, and if set than the old + * data will be copied to over during an overwrite. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE val, + FIO_MAP_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP_KEY key +#endif + ); + +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key); +/* ***************************************************************************** +Map Iteration and Traversal +***************************************************************************** */ + +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_MAP_NAME, each_s) { + /** The being iterated. Once set, cannot be safely changed. */ + FIO_MAP_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_MAP_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; +#ifdef FIO_MAP_VALUE + /** The object's value at the current index. */ + FIO_MAP_VALUE value; +#endif + /** The object's key the current index. */ + FIO_MAP_KEY key; +} FIO_NAME(FIO_MAP_NAME, each_s); + +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + each)(FIO_MAP_PTR map, + int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), + void *udata, + ssize_t start_at); + +/** + * Returns the next iterator object after `current_pos` or the first if `NULL`. + * + * Note that adding objects to the map or rehashing between iterations could + * incur performance penalties when re-setting and re-seeking the previous + * iterator position. + * + * Adding objects to, or rehashing, an unordered maps could invalidate the + * iterator object completely as the ordering may have changed and so the "next" + * object might be any object in the map. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_next)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); + +/** + * Returns the next iterator object after `current_pos` or the last if `NULL`. + * + * See notes in `get_next`. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_prev)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); + +/** Returns 1 if the iterator is out of bounds, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * + iterator); + +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, + iterator2node)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator); + +#ifndef FIO_MAP_EACH + +/** Iterates through the map using an iterator object. */ +#define FIO_MAP_EACH(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_next)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_next)(map_ptr, &i)) + +/** Iterates through the map using an iterator object. */ +#define FIO_MAP_EACH_REVERSED(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_prev)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_prev)(map_ptr, &i)) + +#endif /* FIO_MAP_EACH */ + +/* ***************************************************************************** +Optional Sorting Support - TODO? (convert to array, sort, rehash) +***************************************************************************** */ + +#if defined(FIO_MAP_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ + FIO_MAP_ORDERED +#undef FIO_SORT_NAME +#endif + +/* ***************************************************************************** +Map Implementation - inlined static functions +***************************************************************************** */ + +#ifndef FIO_MAP_CAPA_BITS_LIMIT +/* Note: cannot be more than 31 bits unless some of the code is rewritten. */ +#define FIO_MAP_CAPA_BITS_LIMIT 31 +#endif + +/* Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + FIO_MAP_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (o->map) + return (uint32_t)((size_t)1ULL << o->bits); + return 0; +} + +/* The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + return FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map)->count; +} + +/** Returns 1 if the iterator points to a valid object, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * + iterator) { + return (iterator && iterator->private_.map_validator); +} + +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + FIO_MAP_KEY r = (FIO_MAP_KEY){0}; + if (!node) + return r; + return FIO_MAP_KEY_FROM_INTERNAL(node->key); +} + +/** Returns the hash value associated with the node's pointer. */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node) { + uint32_t r = (uint32_t){0}; + if (!node) + return r; +#if FIO_MAP_RECALC_HASH + FIO_MAP_KEY k = FIO_MAP_KEY_FROM_INTERNAL(node->key); + uint64_t hash = FIO_MAP_HASH_FN(k); + hash += !hash; + return hash; +#else + return node->hash; +#endif +} + +#ifdef FIO_MAP_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + FIO_MAP_VALUE r = (FIO_MAP_VALUE){0}; + if (!node) + return r; + return FIO_MAP_VALUE_FROM_INTERNAL(node->value); +} +#else +/* If called for a node without a + * value, returns the key (simplifies + * stuff). */ +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + return FIO_NAME(FIO_MAP_NAME, node2key)(node); +} +#endif + +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->key); +} + +#ifdef FIO_MAP_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->value); +} +#else +/* If called for a node without a + * value, returns the key (simplifies + * stuff). */ +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { + return FIO_NAME(FIO_MAP_NAME, node2key_ptr)(node); +} +#endif + +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key) { + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, get_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, +#endif + key)); +} + +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE obj, + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY key +#endif +) { + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP_VALUE + , + obj, + old, + 1 +#endif + )); +} + +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key +#ifdef FIO_MAP_VALUE + , + FIO_MAP_VALUE obj +#endif +) { + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP_VALUE + , + obj, + NULL, + 0 +#endif + )); +} + +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, + iterator2node)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator) { + FIO_NAME(FIO_MAP_NAME, node_s) *node = NULL; + if (!iterator || !iterator->private_.map_validator) + return node; + FIO_PTR_TAG_VALID_OR_RETURN(map, node); + FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + node = o->map + iterator->private_.index; + return node; +} + +/* ***************************************************************************** +Map Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, destroy)) +/* ***************************************************************************** +Constructors +***************************************************************************** */ + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, s)) +/* Allocates a new object on the heap and initializes it's memory. */ +FIO_IFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void) { + FIO_NAME(FIO_MAP_NAME, s) *o = + (FIO_NAME(FIO_MAP_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); + if (!o) + return (FIO_MAP_PTR)NULL; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, s)); + *o = (FIO_NAME(FIO_MAP_NAME, s))FIO_MAP_INIT; + return (FIO_MAP_PTR)FIO_PTR_TAG(o); +} +/* Frees any internal data AND the object's container! */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, destroy)(map); + FIO_NAME(FIO_MAP_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP_NAME, s), map); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, s)); + FIO_MEM_FREE_(o, sizeof(*o)); +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* ***************************************************************************** + + + + +Internal Helpers (Core) + + + + +***************************************************************************** */ + +/** internal object data representation */ +struct FIO_NAME(FIO_MAP_NAME, __imap_s) { + uint8_t h[64]; +}; + +#ifndef FIO_MAP_ATTACK_LIMIT +#define FIO_MAP_ATTACK_LIMIT 16 +#endif +#ifndef FIO_MAP_MINIMAL_BITS +#define FIO_MAP_MINIMAL_BITS 1 +#endif +#ifndef FIO_MAP_CUCKOO_STEPS +/* Prime numbers are better */ +#define FIO_MAP_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ +#endif +#ifndef FIO_MAP_SEEK_LIMIT +#define FIO_MAP_SEEK_LIMIT 13U +#endif +#ifndef FIO_MAP_ARRAY_LOG_LIMIT +#define FIO_MAP_ARRAY_LOG_LIMIT 3 +#endif +#ifndef FIO_MAP_CAPA +#define FIO_MAP_CAPA(bits) ((size_t)1ULL << (bits)) +#endif + +#ifndef FIO_MAP_IS_SPARSE +#define FIO_MAP_IS_SPARSE(map) \ + (o->bits > FIO_MAP_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) +#endif + +/* Allocates resources for a new (clean) map. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __allocate_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t bits) { + if (bits < FIO_MAP_MINIMAL_BITS) + bits = FIO_MAP_MINIMAL_BITS; + if (bits > FIO_MAP_CAPA_BITS_LIMIT) + return -1; + size_t s = (sizeof(o->map[0]) + 1) << bits; + FIO_NAME(FIO_MAP_NAME, node_s) *n = + (FIO_NAME(FIO_MAP_NAME, node_s) *)FIO_MEM_REALLOC_(NULL, 0, s, 0); + if (!n) + return -1; + if (!FIO_MEM_REALLOC_IS_SAFE_) /* set only imap to zero */ + FIO_MEMSET((n + (1ULL << bits)), 0, (1ULL << bits)); + *o = (FIO_NAME(FIO_MAP_NAME, s)){.map = n, .bits = bits}; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, destroy)); + return 0; +} + +/* The number of objects in the map capacity. */ +FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP_NAME, + __imap)(FIO_NAME(FIO_MAP_NAME, s) const *o) { + return (uint8_t *)(o->map + FIO_MAP_CAPA(o->bits)); +} + +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, __byte_hash)(uint64_t hash) { + hash = (hash >> 48) ^ (hash >> 56); + hash &= 0xFF; + hash += !(hash); + hash -= (hash == 255); + return hash; +} + +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + __is_eq_hash)(FIO_NAME(FIO_MAP_NAME, node_s) * o, + uint64_t hash) { +#if FIO_MAP_RECALC_HASH && defined(FIO_MAP_HASH_FN) + uint64_t khash = FIO_MAP_HASH_FN(FIO_MAP_KEY_FROM_INTERNAL(o->key)); + khash += !khash; +#else + const uint64_t khash = o->hash; +#endif + return (khash == hash); +} + +typedef struct FIO_NAME(FIO_MAP_NAME, __each_node_s) { + FIO_NAME(FIO_MAP_NAME, s) * map; + FIO_NAME(FIO_MAP_NAME, node_s) * node; + int (*fn)(struct FIO_NAME(FIO_MAP_NAME, __each_node_s) *); + void *udata; +} FIO_NAME(FIO_MAP_NAME, __each_node_s); + +/* perform task for each node. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __each_node)(FIO_NAME(FIO_MAP_NAME, s) * o, + int (*fn)(FIO_NAME(FIO_MAP_NAME, + __each_node_s) *), + void *udata) { + FIO_NAME(FIO_MAP_NAME, __each_node_s) + each = {.map = o, .fn = fn, .udata = udata}; + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + size_t counter = o->count; + if (!counter) + return 0; +#if FIO_MAP_ORDERED + FIO_INDEXED_LIST_EACH(o->map, node, o->head, pos) { + each.node = o->map + pos; + if (each.fn(&each)) + return -1; + --counter; + if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) + return -1; + } +#else + const size_t len = FIO_MAP_CAPA(o->bits); + if (FIO_UNLIKELY(o->bits > 5 && (FIO_MAP_CAPA(o->bits) >> 2) > o->count)) + goto sparse_map; + for (size_t i = 0; counter; ++i) { + if (!imap[i] || imap[i] == 255) + continue; + each.node = o->map + i; + if (FIO_UNLIKELY(each.fn(&each))) + return -1; + --counter; + if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) + return -1; + } + FIO_ASSERT_DEBUG( + !counter, + "detected error while looping over all elements in map (%zu/%zu)", + counter, + (size_t)FIO_MAP_CAPA(o->bits)); + return 0; + +sparse_map: + for (size_t i = 0; counter && i < len; i += 64) { + uint64_t bitmap = 0; + for (size_t j = 0; j < 64; j += 8) { + uint64_t tmp = *((uint64_t *)(imap + i + j)); + uint64_t inv = ~tmp; + tmp = FIO_HAS_FULL_BYTE64(tmp); + inv = FIO_HAS_FULL_BYTE64(inv); + tmp |= inv; + FIO_HAS_BYTE2BITMAP(tmp, 7); + bitmap |= (tmp << j); + } + bitmap = ~bitmap; /* where 1 was a free slot, now it's an occupied one */ + for (size_t j = 0; bitmap; ++j) { + if ((bitmap & 1)) { + each.node = o->map + i + j; + if (each.fn(&each)) + return -1; + --counter; + if (imap != FIO_NAME(FIO_MAP_NAME, __imap)(o)) + return -1; + } + bitmap >>= 1; + } + } +#endif + FIO_ASSERT_DEBUG( + !counter, + "detected error while looping over all elements in map (%zu/%zu)", + counter, + (size_t)FIO_MAP_CAPA(o->bits)); + return 0; +} + +#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE +static int FIO_NAME(FIO_MAP_NAME, + __destroy_map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + FIO_MAP_KEY_DESTROY(e->node->key); + FIO_MAP_VALUE_DESTROY(e->node->value); + return 0; +} +#endif + +/* Destroys and exsiting map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __destroy_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + _Bool should_zero) { +#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE + FIO_NAME(FIO_MAP_NAME, __each_node) + (o, FIO_NAME(FIO_MAP_NAME, __destroy_map_task), NULL); +#endif + if (should_zero) /* set only imap to zero */ + FIO_MEMSET((o->map + (1ULL << o->bits)), 0, (1ULL << o->bits)); + o->count = 0; +} + +/* Destroys and exsiting map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, __free_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + _Bool should_destroy) { + if (!o->map) + return; + if (should_destroy) + FIO_NAME(FIO_MAP_NAME, __destroy_map)(o, 0); + FIO_MEM_FREE_(o->map, ((sizeof(o->map[0]) + 1) << o->bits)); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, destroy)); + *o = (FIO_NAME(FIO_MAP_NAME, s)){0}; +} + +#ifndef H___FIO_MAP_INDEX_TYPE___H +#define H___FIO_MAP_INDEX_TYPE___H +typedef struct { + uint32_t home; + uint32_t act; + uint32_t alt; + uint32_t bhash; +} fio___map_node_info_s; +#endif + +/** internal object data representation */ +typedef struct FIO_NAME(FIO_MAP_NAME, __o_node_s) { + uint64_t hash; + FIO_MAP_KEY key; +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE value; +#endif +} FIO_NAME(FIO_MAP_NAME, __o_node_s); + +/* seek a node for very small collections 8 item capacity at most */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_mini)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + if (!o->bits) + return r; + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const uint32_t capa = (uint32_t)FIO_MAP_CAPA(o->bits); + const uint32_t mask = capa - 1; + size_t pos = node->hash & 0xFF; + for (uint32_t i = 0; i < capa; ++i) { + pos = (pos + i) & mask; + if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash) && + FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = (uint32_t)pos; + return r; + } else if (!imap[pos]) { + r.alt = r.home = (uint32_t)pos; + return r; + } else if (imap[pos] == 255U) { /* "home" has been occupied before */ + r.alt = r.home = (uint32_t)pos; + } + } + return r; +} + +/* seek a node for medium sized collections, 16-512 item capacity. */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_med)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + static int guard_print = 0; + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const uint32_t mask = (uint32_t)(FIO_MAP_CAPA(o->bits) - 1); + uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; + uint32_t pos = r.home = (node->hash & mask); + uint32_t step = 2; + uint32_t attempts = (mask < 511) ? ((mask >> 2) | 8) : 127; + for (; r.alt == (uint32_t)-1 && attempts; --attempts) { + if (!imap[pos]) { + r.alt = pos; + return r; + } else if (imap[pos] == 255U) { /* "home" has been occupied before */ + r.alt = pos; + } else if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = pos; + return r; + } + if (!--guard) { + r.act = pos; + goto possible_attack; + } + } + pos = ((pos + (step++)) & mask); + } + for (; attempts; --attempts) { + if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = pos; + return r; + } + if (!--guard) { + r.act = pos; + goto possible_attack; + } + } + if (!imap[pos]) + return r; + pos = ((pos + (step++)) & mask); + } + if (r.alt == (uint32_t)-1) + r.home = r.alt; + return r; + +possible_attack: + if (!guard_print) { + guard_print = 1; + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); + } + return r; +} + +/* seek a node for larger collections, where 8 byte grouping is meaningless */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_full)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + static int guard_print = 0; + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + const uint32_t mask = (FIO_MAP_CAPA(o->bits) - 1) & (~(uint32_t)7ULL); + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const size_t attempt_limit = o->bits + 7; + const uint64_t mbyte64 = ~(UINT64_C(0x0101010101010101) * (uint64_t)r.bhash); + uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; + uint32_t pos = r.home = (node->hash & mask); + size_t attempt = 0; + for (; r.alt == (uint32_t)-1 && attempt < attempt_limit; ++attempt) { + uint64_t group = fio_buf2u64_le(imap + pos); + group ^= mbyte64; + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { + r.act = offset; + return r; + } + if (!--guard) { + r.act = offset; + goto possible_attack; + } + } + } + group = fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == 255U) { + r.alt = offset; + break; + } + } + group = ~fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (!imap[offset]) { + r.alt = offset; + return r; + } + } + pos += (attempt + 2) << 3; + pos &= mask; + } + for (; attempt < attempt_limit; ++attempt) { + uint64_t group = fio_buf2u64_le(imap + pos); + group ^= mbyte64; + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { + r.act = offset; + return r; + } + if (!--guard) { + r.act = offset; + goto possible_attack; + } + } + } + group = ~fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (!imap[offset]) { + return r; + } + } + pos += (attempt + 2) << 3; + pos &= mask; + } + if (r.alt == (uint32_t)-1) + r.home = r.alt; + return r; +possible_attack: + if (!guard_print) { + guard_print = 1; + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); + } + return r; +} + +FIO_IFUNC fio___map_node_info_s +FIO_NAME(FIO_MAP_NAME, __node_info)(FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { +#if defined(FIO_MAP_HASH_FN) + if (!node->hash) + node->hash = FIO_MAP_HASH_FN(node->key); +#endif + node->hash += !node->hash; + if (o->bits < 4) + return FIO_NAME(FIO_MAP_NAME, __node_info_mini)(o, node); + else if (o->bits < 9) + return FIO_NAME(FIO_MAP_NAME, __node_info_med)(o, node); + else + return FIO_NAME(FIO_MAP_NAME, __node_info_full)(o, node); +} + +#if FIO_MAP_ORDERED +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __update_order)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t i) { + if (o->count == 1) { + o->head = i; + o->map[i].node.next = o->map[i].node.prev = i; + } else { + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, i); + } +} +#define FIO___MAP_UPDATE_ORDER(map, at) \ + FIO_NAME(FIO_MAP_NAME, __update_order)(map, at) +#else +#define FIO___MAP_UPDATE_ORDER(map, at) +#endif + +static int FIO_NAME(FIO_MAP_NAME, + __move2map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + FIO_NAME(FIO_MAP_NAME, s) *dest = (FIO_NAME(FIO_MAP_NAME, s) *)e->udata; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + n = { + .key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key), +#if !FIO_MAP_RECALC_HASH + .hash = e->node->hash, +#endif + }; + fio___map_node_info_s i = FIO_NAME(FIO_MAP_NAME, __node_info)(dest, &n); + if (i.home == (uint32_t)-1) { + FIO_LOG_ERROR("move2map FAILED (%zu/%zu)!", + e->node - e->map->map, + FIO_MAP_CAPA(dest->bits)); + return -1; + } + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(dest); + ++dest->count; + /* insert at best position */ + imap[i.alt] = i.bhash; + dest->map[i.alt] = e->node[0]; + FIO___MAP_UPDATE_ORDER(dest, i.alt); + return 0; +} + +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __move2map)(FIO_NAME(FIO_MAP_NAME, s) * dest, + FIO_NAME(FIO_MAP_NAME, s) * src) { + return FIO_NAME( + FIO_MAP_NAME, + __each_node)(src, FIO_NAME(FIO_MAP_NAME, __move2map_task), dest); +} + +/* Inserts a node to the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_insert)(FIO_NAME(FIO_MAP_NAME, s) * o, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, +#endif + FIO_MAP_KEY key, +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE value, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old, +#else + FIO_MAP_KEY_INTERNAL *old, +#endif + _Bool overwrite) { + static FIO_NAME(FIO_MAP_NAME, s) *last_collision = NULL; + uint32_t r = -1; + FIO_NAME(FIO_MAP_NAME, s) tmp; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifdef FIO_MAP_VALUE + .value = value, +#endif +#ifndef FIO_MAP_HASH_FN + .hash = hash, +#endif + }; + fio___map_node_info_s info; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + if (info.act != r) + goto perform_overwrite; + if (info.home == r) + goto reallocate_map; + +insert: + ++o->count; + r = info.alt; + FIO_NAME(FIO_MAP_NAME, __imap)(o)[r] = info.bhash; +#if !FIO_MAP_RECALC_HASH + o->map[r].hash = node.hash, +#endif + FIO_MAP_KEY_COPY(o->map[r].key, node.key); + FIO_MAP_VALUE_COPY(o->map[r].value, node.value); + FIO___MAP_UPDATE_ORDER(o, r); + return r; + +perform_overwrite: + r = info.act; + FIO_MAP_KEY_DISCARD(node.key); + if (!overwrite) { + FIO_MAP_VALUE_DISCARD(value); + return r; + } +#ifdef FIO_MAP_VALUE + if (old) + *old = o->map[r].value; + else { + FIO_MAP_VALUE_DESTROY(o->map[r].value); + } + FIO_MAP_VALUE_COPY(o->map[r].value, node.value); +#else + (void)old; +#endif +#if FIO_MAP_ORDERED + if (o->head == r) + o->head = o->map[o->head].node.prev; + FIO_INDEXED_LIST_REMOVE(o->map, node, r); + FIO___MAP_UPDATE_ORDER(o, r); +#endif + return r; + +reallocate_map: + /* reallocate map */ + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, o->bits + 1)) + goto no_memory; + if (FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, o)) { + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto security_partial; + } + info = FIO_NAME(FIO_MAP_NAME, __node_info)(&tmp, &node); + if (info.home != r) { + FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); + *o = tmp; + goto insert; + } + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto security_partial; + +no_memory: + FIO_MAP_KEY_DISCARD(key); + FIO_MAP_VALUE_DISCARD(value); + FIO_LOG_ERROR( + "unknown error occurred trying to add an entry to the map (capa: %zu)", + (size_t)FIO_MAP_CAPA(o->bits)); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); + return r; + +security_partial: + if (last_collision != o) { + FIO_LOG_SECURITY( + "hash map " FIO_MACRO2STR(FIO_NAME( + FIO_MAP_NAME, + s)) " under attack? (partial/full collision guard) - capa: %zu.", + (size_t)FIO_MAP_CAPA(o->bits)); + last_collision = o; + } + return r; +} + +/* Inserts a node to the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_find)(FIO_NAME(FIO_MAP_NAME, s) * o, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, +#endif + FIO_MAP_KEY key) { + uint32_t r = -1; + fio___map_node_info_s info; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifndef FIO_MAP_HASH_FN + .hash = hash, +#endif + }; + if (!o->map) + return r; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + return (r = info.act); +} + +/* Deletes a known node from the map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __node_delete_at)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t at, +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +) { + --o->count; + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + imap[at] = 255U; /* mark as deleted */ + if (old) { +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY_DESTROY(o->map[at].key); + *old = o->map[at].value; +#else + *old = o->map[at].key; +#endif + } else { + FIO_MAP_KEY_DESTROY(o->map[at].key); + FIO_MAP_VALUE_DESTROY(o->map[at].value); + } + +#if FIO_MAP_ORDERED + if (o->head == at) { + o->head = (o->count ? o->map[o->head].node.next : 0); + } + FIO_INDEXED_LIST_REMOVE(o->map, node, at); +#endif +} + +/* Deletes a node from the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_delete)(FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_MAP_KEY key, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +) { + uint32_t r = (uint32_t)-1; + if (!o->map) + return r; + fio___map_node_info_s info; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifndef FIO_MAP_HASH_FN + .hash = hash, +#endif + }; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + if (info.act == r) + return r; + r = 0; + FIO_NAME(FIO_MAP_NAME, __node_delete_at)(o, info.act, old); + return r; +} + +/* ***************************************************************************** + + + + + + +Map API + + + + + + +***************************************************************************** */ + +/** Destroys the object, re-initializing its container. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map) { + // FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + FIO_NAME(FIO_MAP_NAME, __free_map)(m, 1); + *m = (FIO_NAME(FIO_MAP_NAME, s)){0}; +} + +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (capa <= FIO_MAP_CAPA(m->bits)) + return; + uint32_t bits = m->bits; + for (; capa > FIO_MAP_CAPA(bits); ++bits) + ; + FIO_NAME(FIO_MAP_NAME, s) tmp; + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, bits)) + goto no_memory; + if (m->count && FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, m)) { + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto no_memory; + } + FIO_NAME(FIO_MAP_NAME, __free_map)(m, 0); + *m = tmp; + return; +no_memory: + FIO_LOG_ERROR("unknown error occurred trying to rehash the map"); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); + return; +} + +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key, +#if defined(FIO_MAP_VALUE) + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +) { + FIO_PTR_TAG_VALID_OR_RETURN(map, -1); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (!m->count) + return -1; + return FIO_NAME(FIO_MAP_NAME, __node_delete)(m, + key, +#if !defined(FIO_MAP_HASH_FN) + hash, +#endif + old); +} + +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, + __evict_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + size_t *counter = (size_t *)e->udata; + FIO_NAME(FIO_MAP_NAME, __node_delete_at) + (e->map, (uint32_t)(e->node - e->map->map), NULL); + if ((counter[0] -= 1)) + return 0; + return -1; +} +/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, + size_t number_of_elements) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + if (!number_of_elements) + return; + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (m->count <= number_of_elements) { + FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); + return; + } + FIO_NAME(FIO_MAP_NAME, __each_node) + (m, FIO_NAME(FIO_MAP_NAME, __evict_task), &number_of_elements); +} + +/** Removes all objects from the map, without releasing the map's resources. + */ +SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (m->map) + FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); +} + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (!o->map || !o->count) + return; + FIO_NAME(FIO_MAP_NAME, s) cpy = {0}; + uint32_t bits = o->bits; + while (FIO_MAP_CAPA(bits >> 1) > o->count) + bits >>= 1; + ++bits; + if (bits >= o->bits) + return; + for (size_t i = 0; i < 2; ++i) { + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&cpy, bits)) + return; + if (!FIO_NAME(FIO_MAP_NAME, __move2map)(&cpy, o)) + goto finish; + FIO_NAME(FIO_MAP_NAME, __free_map)(&cpy, 0); + ++bits; + } + return; + +finish: + FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); + o[0] = cpy; + return; +} + +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE val, + FIO_MAP_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP_KEY key +#endif + ) { + FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_insert)(m, +#ifndef FIO_MAP_HASH_FN + hash, +#endif + key, +#ifdef FIO_MAP_VALUE + val, + old, + overwrite +#else + NULL, + 1 +#endif + ); + if (i == (uint32_t)-1) + return NULL; + return m->map + i; +} + +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key) { + FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_find)(m, +#ifndef FIO_MAP_HASH_FN + hash, +#endif + key); + if (i == (uint32_t)-1) + return NULL; + return m->map + i; +} + +/* ***************************************************************************** + + + +Map Iterators + + + +***************************************************************************** */ + +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_next)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); + if (!m->count) + return r; + if (!current_pos) + goto empty; + if (current_pos->private_.pos + 1 == m->count) + return r; + if (current_pos->private_.map_validator != (uintptr_t)(m->map)) + return r; /* mutation stops iteration */ +#if FIO_MAP_ORDERED + if (current_pos->node->node.next == m->head) + return r; + r.node = m->map + current_pos->node->node.next; +#else + for (size_t i = current_pos->node - m->map + 1; i < FIO_MAP_CAPA(m->bits); + ++i) { + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; + } + if (!r.node) + return r; +#endif + + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = current_pos->private_.pos + 1, + .map_validator = (uintptr_t)m->map}, + }; + return r; +empty: + +#if FIO_MAP_ORDERED + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = m->map + m->head, + .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[m->head].key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[m->head].value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = m->map[m->head].hash, +#endif + .private_ = {.index = m->head, + .pos = 0, + .map_validator = (uintptr_t)m->map}, + }; +#else + for (size_t i = 0; i < FIO_MAP_CAPA(m->bits); ++i) { + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; + } + + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = 0, + .map_validator = (uintptr_t)m->map}, + }; + +#endif + return r; + (void)imap; /* if unused */ +} + +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, get_prev)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * + current_pos) { // TODO! + FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); +#if FIO_MAP_ORDERED + uint32_t ipos; +#else + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); +#endif + if (!m->count) + return r; + if (!current_pos) + goto empty; + if (!current_pos->private_.pos) + return r; + if (current_pos->private_.map_validator != (uintptr_t)(m->map)) + return r; /* mutation stops iteration */ +#if FIO_MAP_ORDERED + if (current_pos->private_.index == m->head) + return r; + r.node = m->map + current_pos->node->node.prev; +#else + for (size_t i = current_pos->node - m->map; i;) { + --i; + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; + } +#endif + + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = current_pos->private_.pos - 1, + .map_validator = (uintptr_t)m->map}, + }; + return r; +empty: + +#if FIO_MAP_ORDERED + + ipos = m->map[m->head].node.prev; + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = m->map + ipos, .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[ipos].key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[ipos].value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = m->map[ipos].hash, +#endif + .private_ = {.index = ipos, + .pos = m->count - 1, + .map_validator = (uintptr_t)m->map}, + }; + +#else + for (size_t i = FIO_MAP_CAPA(m->bits); i;) { + --i; + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; + } + + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = m->count - 1, + .map_validator = (uintptr_t)m->map}, + }; + +#endif + + return r; +} + +/* ***************************************************************************** + + + +Map Each + + + +***************************************************************************** */ + +typedef struct { + FIO_NAME(FIO_MAP_NAME, each_s) each; + ssize_t start_at; +} FIO_NAME(FIO_MAP_NAME, __each_info_s); + +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, + __each_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { + int r; + FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = + (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; + info->each.key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key); +#ifdef FIO_MAP_VALUE + info->each.value = FIO_MAP_VALUE_FROM_INTERNAL(e->node->value); +#endif + r = info->each.task(&info->each); + ++info->each.index; + return r; +} + +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, __each_task_offset)( + FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { + FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = + (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; + if (FIO_LIKELY(info->each.index < (uint64_t)info->start_at)) { + ++info->each.index; + return 0; + } + return (e->fn = FIO_NAME(FIO_MAP_NAME, __each_task))(e); +} + +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + each)(FIO_MAP_PTR map, + int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), + void *udata, + ssize_t start_at) { + uint32_t r = (uint32_t)-1; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (start_at < 0) + start_at += m->count; + if (start_at < 0) + return m->count; + FIO_NAME(FIO_MAP_NAME, __each_info_s) + e = { + .each = + { + .parent = map, + .index = 0, + .task = task, + .udata = udata, + }, + .start_at = start_at, + }; + + FIO_NAME(FIO_MAP_NAME, __each_node) + (m, + !start_at ? FIO_NAME(FIO_MAP_NAME, __each_task) + : FIO_NAME(FIO_MAP_NAME, __each_task_offset), + &e); + return (uint32_t)e.each.index; +} + +/* ***************************************************************************** +Map Testing +***************************************************************************** */ +#ifdef FIO_MAP_TEST + +#ifdef FIO_MAP_HASH_FN +#define FIO___M_HASH(k) +#else +#define FIO___M_HASH(k) (k), +#endif +#ifdef FIO_MAP_VALUE +#define FIO___M_VAL(v) , (v) +#define FIO___M_OLD , NULL +#else +#define FIO___M_VAL(v) +#define FIO___M_OLD +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP_NAME)(void) { + /* testing only only works with integer external types */ + fprintf( + stderr, + "* Testing map " FIO_MACRO2STR(FIO_MAP_NAME) " with key " FIO_MACRO2STR( + FIO_MAP_KEY) " (=> " FIO_MACRO2STR(FIO_MAP_VALUE) ").\n"); + size_t test_len_limit = (1UL << (FIO_MAP_ARRAY_LOG_LIMIT + 15)); + { /* test set / get overwrite , FIO_MAP_EACH and evict */ + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_NAME(FIO_MAP_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, + "map `set` failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + i); + for (size_t j = ((i << 2) + 1); j < i; ++j) { /* effects LRU ordering */ + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && + FIO_NAME(FIO_MAP_NAME, node2val)( + FIO_NAME(FIO_MAP_NAME, + get_ptr)(&map, FIO___M_HASH(j) j)) == j, + "map `get` failed? %zu/%zu (%p)", + j, + i, + FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); + FIO_NAME(FIO_MAP_NAME, set) + (&map, FIO___M_HASH(j) j FIO___M_VAL(j) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, + "map `set` added an item that already exists? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + i); + } + } + /* test FIO_MAP_EACH and ordering */ + uint32_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); + uint32_t loop_test = 0; + FIO_MAP_EACH(FIO_MAP_NAME, &map, i) { + /* test ordering */ + ++loop_test; +#ifdef FIO_MAP_LRU + FIO_ASSERT(i.key == loop_test, + "map FIO_MAP_EACH LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(count - loop_test)); +#elif FIO_MAP_ORDERED + FIO_ASSERT(i.key == loop_test, + "map FIO_MAP_EACH ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(loop_test)); +#else + FIO_ASSERT(i.key < test_len_limit, + "map FIO_MAP_EACH invalid data? %zu !< %zu", + (size_t)(i.key), + (size_t)(test_len_limit)); +#endif + } + FIO_ASSERT(loop_test == count, + "FIO_MAP_EACH failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + loop_test = 0; + FIO_MAP_EACH_REVERSED(FIO_MAP_NAME, &map, i) { + /* test reversed ordering */ + ++loop_test; +#ifdef FIO_MAP_LRU + FIO_ASSERT(i.key == (count - (loop_test - 1)), + "map FIO_MAP_EACH_REVERSED LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(count - loop_test)); +#elif FIO_MAP_ORDERED + FIO_ASSERT(i.key == (count - (loop_test - 1)), + "map FIO_MAP_EACH_REVERSED ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(loop_test)); +#endif + } + FIO_ASSERT( + loop_test == count, + "FIO_MAP_EACH_REVERSED failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + /* test `evict` while we're here */ + FIO_NAME(FIO_MAP_NAME, evict)(&map, (count >> 1)); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == (count - (count >> 1)), + "map `evict` count error %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + (size_t)(count - (count >> 1))); + /* cleanup */ + FIO_NAME(FIO_MAP_NAME, destroy)(&map); + } +#if !FIO_MAP_RECALC_HASH + { /* test full collision guard and zero hash*/ + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; + fprintf( + stderr, + "* Testing full collision guard for " FIO_MACRO2STR( + FIO_NAME(FIO_MAP_NAME, s)) " - expect SECURITY log messages.\n"); + for (size_t i = 1; i < 4096; ++i) { + FIO_NAME(FIO_MAP_NAME, set) + (&map, FIO___M_HASH(0) i FIO___M_VAL(i) FIO___M_OLD); + } + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map), + "zero hash fails insertion?"); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) <= FIO_MAP_ATTACK_LIMIT, + "map attack guard failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + (size_t)FIO_MAP_ATTACK_LIMIT); + FIO_NAME(FIO_MAP_NAME, destroy)(&map); + } +#endif + { /* test reserve, remove */ + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; + FIO_NAME(FIO_MAP_NAME, reserve)(&map, 4096); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, capa)(&map) == 4096, + "map reserve error? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, capa)(&map), + 4096); + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_NAME(FIO_MAP_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, "insertion failed?"); + } + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), + "key missing?"); + size_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); + FIO_NAME(FIO_MAP_NAME, remove) + (&map, FIO___M_HASH(i) i, NULL); + FIO_ASSERT(!FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), + "map_remove error?"); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == count - 1, + "map count error after removal? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + count - 1); + /* see if removal produces errors while rehashing */ + FIO_NAME(FIO_MAP_NAME, compact)(&map); + } + FIO_NAME(FIO_MAP_NAME, destroy)(&map); + } +} +#undef FIO___M_HASH +#undef FIO___M_VAL +#undef FIO___M_OLD + +#endif /* FIO_MAP_TEST */ +/* ***************************************************************************** +Map Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ + +#undef FIO_MAP_ARRAY_LOG_LIMIT +#undef FIO_MAP_ATTACK_LIMIT +#undef FIO_MAP_CAPA +#undef FIO_MAP_CAPA_BITS_LIMIT +#undef FIO_MAP_CUCKOO_STEPS +#undef FIO_MAP_GET_T +#undef FIO_MAP_HASH_FN +#undef FIO_MAP_IS_SPARSE +#undef FIO_MAP_KEY +#undef FIO_MAP_KEY_CMP +#undef FIO_MAP_KEY_COPY +#undef FIO_MAP_KEY_DESTROY +#undef FIO_MAP_KEY_DESTROY_SIMPLE +#undef FIO_MAP_KEY_DISCARD +#undef FIO_MAP_KEY_FROM_INTERNAL +#undef FIO_MAP_KEY_INTERNAL +#undef FIO_MAP_KEY_IS_GREATER_THAN +#undef FIO_MAP_LRU +#undef FIO_MAP_NAME +#undef FIO_MAP_ORDERED +#undef FIO_MAP_PTR +#undef FIO_MAP_RECALC_HASH +#undef FIO_MAP_SEEK_LIMIT +#undef FIO_MAP_T +#undef FIO_MAP_TEST +#undef FIO_MAP_VALUE +#undef FIO_MAP_VALUE_BSTR +#undef FIO_MAP_VALUE_COPY +#undef FIO_MAP_VALUE_DESTROY +#undef FIO_MAP_VALUE_DESTROY_SIMPLE +#undef FIO_MAP_VALUE_DISCARD +#undef FIO_MAP_VALUE_FROM_INTERNAL +#undef FIO_MAP_VALUE_INTERNAL + +#undef FIO_MAP___MAKE_BITMAP +#undef FIO_MAP___STEP_POS +#undef FIO_MAP___TEST_MATCH +#undef FIO___MAP_UPDATE_ORDER +#undef FIO_MAP_ARRAY_LOG_LIMIT +#undef FIO_MAP_ATTACK_LIMIT +#undef FIO_MAP_CAPA +#undef FIO_MAP_CAPA_BITS_LIMIT +#undef FIO_MAP_CUCKOO_STEPS +#undef FIO_MAP_GET_T +#undef FIO_MAP_HASH_FN +#undef FIO_MAP_IS_SPARSE +#undef FIO_MAP_KEY +#undef FIO_MAP_KEY_BSTR +#undef FIO_MAP_KEY_CMP +#undef FIO_MAP_KEY_COPY +#undef FIO_MAP_KEY_DESTROY +#undef FIO_MAP_KEY_DESTROY_SIMPLE +#undef FIO_MAP_KEY_DISCARD +#undef FIO_MAP_KEY_FROM_INTERNAL +#undef FIO_MAP_KEY_INTERNAL +#undef FIO_MAP_KEY_IS_GREATER_THAN +#undef FIO_MAP_KEY_KSTR +#undef FIO_MAP_LRU +#undef FIO_MAP_MINIMAL_BITS +#undef FIO_MAP_NAME +#undef FIO_MAP_ORDERED +#undef FIO_MAP_PTR +#undef FIO_MAP_RECALC_HASH +#undef FIO_MAP_SEEK_LIMIT +#undef FIO_MAP_T +#undef FIO_MAP_TEST +#undef FIO_MAP_VALUE +#undef FIO_MAP_VALUE_BSTR +#undef FIO_MAP_VALUE_COPY +#undef FIO_MAP_VALUE_DESTROY +#undef FIO_MAP_VALUE_DESTROY_SIMPLE +#undef FIO_MAP_VALUE_DISCARD +#undef FIO_MAP_VALUE_FROM_INTERNAL +#undef FIO_MAP_VALUE_INTERNAL + +#undef FIO_OMAP_NAME +#undef FIO_UMAP_NAME + +#endif /* FIO_MAP_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MAP2_NAME map /* Development inclusion - ignore line */ +#define FIO_MAP2_TEST /* Development inclusion - ignore line */ +#define FIO_MAP2_KEY size_t /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Unordered/Ordered Map Implementation + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MAP2_NAME) +/* ***************************************************************************** +Map Settings - Sets have only keys (value == key) - Hash Maps have values +***************************************************************************** */ + +/* if FIO_MAP2_KEY_KSTR is defined, use fio_keystr_s keys */ +#ifdef FIO_MAP2_KEY_KSTR +#define FIO_MAP2_KEY fio_str_info_s +#define FIO_MAP2_KEY_INTERNAL fio_keystr_s +#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) +#define FIO_MAP2_KEY_COPY(dest, src) \ + (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP2_NAME, __key_alloc)) +#define FIO_MAP2_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) +#define FIO_MAP2_KEY_DESTROY(key) \ + fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP2_NAME, __key_free)) +#define FIO_MAP2_KEY_DISCARD(key) +FIO_SFUNC void *FIO_NAME(FIO_MAP2_NAME, __key_alloc)(size_t len) { + return FIO_MEM_REALLOC_(NULL, 0, len, 0); +} +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, __key_free)(void *ptr, size_t len) { + FIO_MEM_FREE_(ptr, len); + (void)len; /* if unused */ +} +#undef FIO_MAP2_KEY_KSTR + +/* if FIO_MAP2_KEY is undefined, assume String keys (using `fio_bstr`). */ +#elif !defined(FIO_MAP2_KEY) || defined(FIO_MAP2_KEY_BSTR) +#define FIO_MAP2_KEY fio_str_info_s +#define FIO_MAP2_KEY_INTERNAL char * +#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP2_KEY_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP2_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP2_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP2_KEY_DISCARD(key) +#endif +#undef FIO_MAP2_KEY_BSTR + +#ifndef FIO_MAP2_KEY_INTERNAL +#define FIO_MAP2_KEY_INTERNAL FIO_MAP2_KEY +#endif + +#ifndef FIO_MAP2_KEY_FROM_INTERNAL +#define FIO_MAP2_KEY_FROM_INTERNAL(o) o +#endif + +#ifndef FIO_MAP2_KEY_COPY +#define FIO_MAP2_KEY_COPY(dest, src) ((dest) = (src)) +#endif + +#ifndef FIO_MAP2_KEY_CMP +#define FIO_MAP2_KEY_CMP(a, b) ((a) == (b)) +#endif + +#ifndef FIO_MAP2_KEY_DESTROY +#define FIO_MAP2_KEY_DESTROY(o) +#define FIO_MAP2_KEY_DESTROY_SIMPLE 1 +#endif + +#ifndef FIO_MAP2_KEY_DISCARD +#define FIO_MAP2_KEY_DISCARD(o) +#endif + +/* FIO_MAP2_HASH_FN(key) - used instead of providing a hash value. */ +#ifndef FIO_MAP2_HASH_FN +#undef FIO_MAP2_RECALC_HASH +#endif + +/* FIO_MAP2_RECALC_HASH - if true, hash values won't be cached. */ +#ifndef FIO_MAP2_RECALC_HASH +#define FIO_MAP2_RECALC_HASH 0 +#endif + +#ifdef FIO_MAP2_VALUE_BSTR +#define FIO_MAP2_VALUE fio_str_info_s +#define FIO_MAP2_VALUE_INTERNAL char * +#define FIO_MAP2_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) +#define FIO_MAP2_VALUE_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP2_VALUE_DESTROY(v) fio_bstr_free((v)) +#define FIO_MAP2_VALUE_DISCARD(v) +#endif + +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_GET_T FIO_MAP2_VALUE +#else +#define FIO_MAP2_GET_T FIO_MAP2_KEY +#endif + +#ifndef FIO_MAP2_VALUE_INTERNAL +#define FIO_MAP2_VALUE_INTERNAL FIO_MAP2_VALUE +#endif + +#ifndef FIO_MAP2_VALUE_FROM_INTERNAL +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_VALUE_FROM_INTERNAL(o) o +#else +#define FIO_MAP2_VALUE_FROM_INTERNAL(o) +#endif +#endif + +#ifndef FIO_MAP2_VALUE_COPY +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_VALUE_COPY(dest, src) (dest) = (src) +#else +#define FIO_MAP2_VALUE_COPY(dest, src) +#endif +#endif + +#ifndef FIO_MAP2_VALUE_DESTROY +#define FIO_MAP2_VALUE_DESTROY(o) +#define FIO_MAP2_VALUE_DESTROY_SIMPLE 1 +#endif + +#ifndef FIO_MAP2_VALUE_DISCARD +#define FIO_MAP2_VALUE_DISCARD(o) +#endif + +#ifdef FIO_MAP2_LRU +#undef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 1 /* required for least recently used order */ +#endif + +/* test if FIO_MAP2_ORDERED was defined as an empty macro */ +#if defined(FIO_MAP2_ORDERED) && ((0 - FIO_MAP2_ORDERED - 1) == 1) +#undef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 1 /* assume developer's intention */ +#endif + +#ifndef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 0 +#endif + +/* ***************************************************************************** +Pointer Tagging Support +***************************************************************************** */ + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_MAP2_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, s) * +#endif +#define FIO_MAP2_T FIO_NAME(FIO_MAP2_NAME, s) + +/* ***************************************************************************** +Map Types +***************************************************************************** */ + +/** internal object data representation */ +typedef struct FIO_NAME(FIO_MAP2_NAME, node_s) FIO_NAME(FIO_MAP2_NAME, node_s); + +/** A Hash Map / Set type */ +typedef struct FIO_NAME(FIO_MAP2_NAME, s) { + uint32_t bits; + uint32_t count; + FIO_NAME(FIO_MAP2_NAME, node_s) * map; +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST32_HEAD head; +#endif +} FIO_NAME(FIO_MAP2_NAME, s); + +/** internal object data representation */ +struct FIO_NAME(FIO_MAP2_NAME, node_s) { +#if !FIO_MAP2_RECALC_HASH + uint64_t hash; +#endif + FIO_MAP2_KEY_INTERNAL key; +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL value; +#endif +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST32_NODE node; +#endif +}; + +/** Map iterator type */ +typedef struct { + /** the node in the internal map */ + FIO_NAME(FIO_MAP2_NAME, node_s) * node; + /** the key in the current position */ + FIO_MAP2_KEY key; +#ifdef FIO_MAP2_VALUE + /** the value in the current position */ + FIO_MAP2_VALUE value; +#endif +#if !FIO_MAP2_RECALC_HASH + /** the hash for the current position */ + uint64_t hash; +#endif + struct { /* internal usage, do not access */ + uint32_t index; /* the index in the internal map */ + uint32_t pos; /* the position in the ordering scheme */ + uintptr_t map_validator; /* map mutation guard */ + } private_; +} FIO_NAME(FIO_MAP2_NAME, iterator_s); + +#ifndef FIO_MAP2_INIT +/* Initialization macro. */ +#define FIO_MAP2_INIT \ + { 0 } +#endif + +/* ***************************************************************************** +Construction / Deconstruction +***************************************************************************** */ + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void); + +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map); + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** Destroys the object, reinitializing its container. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map); + +/* ***************************************************************************** +Map State +***************************************************************************** */ + +/** Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map); + +/** The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map); + +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa); + +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node); + +/** Returns the hash value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node); + +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node); +#endif + +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node); + +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node); +#endif + +/* ***************************************************************************** +Adding / Removing Elements from the Map +***************************************************************************** */ + +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key, +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY_INTERNAL *old +#endif +); + +/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, + size_t number_of_elements); + +/** Removes all objects from the map, without releasing the map's resources. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map); + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map); + +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key); + +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE obj, + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY key +#endif +); + +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set_if_missing)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key +#ifdef FIO_MAP2_VALUE + , + FIO_MAP2_VALUE obj +#endif +); + +/** + * The core set function. + * + * This function returns `NULL` on error (errors are logged). + * + * If the map is a hash map, overwriting the value (while keeping the key) is + * possible. In this case the `old` pointer is optional, and if set than the old + * data will be copied to over during an overwrite. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE val, + FIO_MAP2_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP2_KEY key +#endif + ); + +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key); +/* ***************************************************************************** +Map Iteration and Traversal +***************************************************************************** */ + +/** + * Returns the next iterator object after `current_pos` or the first if `NULL`. + * + * Note that adding objects to the map or rehashing between iterations could + * incur performance penalties when re-setting and re-seeking the previous + * iterator position. + * + * Adding objects to, or rehashing, an unordered maps could invalidate the + * iterator object completely as the ordering may have changed and so the "next" + * object might be any object in the map. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_next)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); + +/** + * Returns the next iterator object after `current_pos` or the last if `NULL`. + * + * See notes in `get_next`. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_prev)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); + +/** Returns 1 if the iterator is out of bounds, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * + iterator); + +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, + iterator2node)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator); + +#ifndef FIO_MAP2_EACH +/** Iterates through the map using an iterator object. */ +#define FIO_MAP2_EACH(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_next)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_next)(map_ptr, &i)) +/** Iterates through the map using an iterator object. */ +#define FIO_MAP2_EACH_REVERSED(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_prev)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_prev)(map_ptr, &i)) +#endif + +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_MAP2_NAME, each_s) { + /** The being iterated. Once set, cannot be safely changed. */ + FIO_MAP2_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_MAP2_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; +#ifdef FIO_MAP2_VALUE + /** The object's value at the current index. */ + FIO_MAP2_VALUE value; +#endif + /** The object's key the current index. */ + FIO_MAP2_KEY key; +} FIO_NAME(FIO_MAP2_NAME, each_s); + +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + each)(FIO_MAP2_PTR map, + int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), + void *udata, + ssize_t start_at); + +/* ***************************************************************************** +Optional Sorting Support - TODO? (convert to array, sort, rehash) +***************************************************************************** */ + +#if defined(FIO_MAP2_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ + FIO_MAP2_ORDERED +#undef FIO_SORT_NAME +#endif + +/* ***************************************************************************** +Map Implementation - inlined static functions +***************************************************************************** */ + +#ifndef FIO_MAP2_CAPA_BITS_LIMIT +/* Note: cannot be more than 31 bits unless some of the code is rewritten. */ +#define FIO_MAP2_CAPA_BITS_LIMIT 31 +#endif + +/* Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + FIO_MAP2_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (o->map) + return (uint32_t)((size_t)1ULL << o->bits); + return 0; +} + +/* The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + return ((FIO_NAME(FIO_MAP2_NAME, s) *)FIO_PTR_UNTAG(map))->count; +} + +/** Returns 1 if the iterator points to a valid object, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * + iterator) { + return (iterator && iterator->private_.map_validator); +} + +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + FIO_MAP2_KEY r = (FIO_MAP2_KEY){0}; + if (!node) + return r; + return FIO_MAP2_KEY_FROM_INTERNAL(node->key); +} + +/** Returns the hash value associated with the node's pointer. */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + uint32_t r = (uint32_t){0}; + if (!node) + return r; +#if FIO_MAP2_RECALC_HASH + FIO_MAP2_KEY k = FIO_MAP2_KEY_FROM_INTERNAL(node->key); + uint64_t hash = FIO_MAP2_HASH_FN(k); + hash += !hash; + return hash; +#else + return node->hash; +#endif +} + +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + FIO_MAP2_VALUE r = (FIO_MAP2_VALUE){0}; + if (!node) + return r; + return FIO_MAP2_VALUE_FROM_INTERNAL(node->value); +} +#else +/* If called for a node without a value, returns the key (simplifies stuff). */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + return FIO_NAME(FIO_MAP2_NAME, node2key)(node); +} +#endif + +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->key); +} + +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->value); +} +#else +/* If called for a node without a value, returns the key (simplifies stuff). */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + return FIO_NAME(FIO_MAP2_NAME, node2key_ptr)(node); +} +#endif + +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, get_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key)); +} + +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE obj, + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY key +#endif +) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP2_VALUE + , + obj, + old, + 1 +#endif + )); +} + +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set_if_missing)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key +#ifdef FIO_MAP2_VALUE + , + FIO_MAP2_VALUE obj +#endif +) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP2_VALUE + , + obj, + NULL, + 0 +#endif + )); +} + +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, + iterator2node)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator) { + FIO_NAME(FIO_MAP2_NAME, node_s) *node = NULL; + if (!iterator || !iterator->private_.map_validator) + return node; + FIO_PTR_TAG_VALID_OR_RETURN(map, node); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + node = o->map + iterator->private_.index; + return node; +} + +/* ***************************************************************************** +Map Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, destroy)) +/* ***************************************************************************** +Constructors +***************************************************************************** */ + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new object on the heap and initializes it's memory. */ +FIO_IFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void) { + FIO_NAME(FIO_MAP2_NAME, s) *o = + (FIO_NAME(FIO_MAP2_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); + if (!o) + return (FIO_MAP2_PTR)NULL; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, s)); + *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; + return (FIO_MAP2_PTR)FIO_PTR_TAG(o); +} +/* Frees any internal data AND the object's container! */ +FIO_IFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, destroy)(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, s)); + FIO_MEM_FREE_(o, sizeof(*o)); +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* ***************************************************************************** +Internal Helpers +***************************************************************************** */ + +#ifndef FIO_MAP2_ATTACK_LIMIT +#define FIO_MAP2_ATTACK_LIMIT 16 +#endif +#ifndef FIO_MAP2_CUCKOO_STEPS +/* Prime numbers are better */ +#define FIO_MAP2_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ +#endif +#ifndef FIO_MAP2_SEEK_LIMIT +#define FIO_MAP2_SEEK_LIMIT 13U +#endif +#ifndef FIO_MAP2_ARRAY_LOG_LIMIT +#define FIO_MAP2_ARRAY_LOG_LIMIT 3 +#endif +#ifndef FIO_MAP2_CAPA +#define FIO_MAP2_CAPA(bits) ((size_t)1ULL << (bits)) +#endif + +#ifndef FIO_MAP2_IS_SPARSE +#define FIO_MAP2_IS_SPARSE(map) \ + (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) +#endif + +/* The number of objects in the map capacity. */ +FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP2_NAME, + __imap)(FIO_NAME(FIO_MAP2_NAME, s) * o) { + // FIO_ASSERT(o && o->map, "shouldn't have been called."); + return (uint8_t *)(o->map + FIO_MAP2_CAPA(o->bits)); +} + +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + __byte_hash)(FIO_NAME(FIO_MAP2_NAME, s) * o, + uint64_t hash) { + hash = (hash >> o->bits); + hash &= 0xFF; + hash += !(hash); + hash -= (hash == 255); + return hash; +} + +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + __is_eq_hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * o, + uint64_t hash) { +#if FIO_MAP2_RECALC_HASH && defined(FIO_MAP2_HASH_FN) + uint64_t khash = FIO_MAP2_HASH_FN(FIO_MAP2_KEY_FROM_INTERNAL(o->key)); + khash += !khash; +#else + const uint64_t khash = o->hash; +#endif + return (khash == hash); +} + +FIO_SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + __index)(FIO_NAME(FIO_MAP2_NAME, s) * o, + FIO_MAP2_KEY key, + uint64_t hash) { + uint32_t r = (uint32_t)-1; + if (!o->map) + return r; + static int guard_print = 0; + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); + size_t bhash = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); + size_t guard = FIO_MAP2_ATTACK_LIMIT + 1; + if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { /* treat as map */ + uint64_t bhash64 = bhash | (bhash << 8); + bhash64 |= bhash64 << 16; + bhash64 |= bhash64 << 32; + bhash64 = ~bhash64; + const uintptr_t pos_mask = capa - 1; + const uint_fast8_t offsets[8] = {0, 3, 8, 17, 28, 41, 58, 60}; + for (uintptr_t pos = hash, c = 0; c < FIO_MAP2_SEEK_LIMIT; + (pos += FIO_MAP2_CUCKOO_STEPS), ++c) { + uint64_t comb = imap[(pos + offsets[0]) & pos_mask]; + comb |= ((uint64_t)imap[(pos + offsets[1]) & pos_mask]) << (1 * 8); + comb |= ((uint64_t)imap[(pos + offsets[2]) & pos_mask]) << (2 * 8); + comb |= ((uint64_t)imap[(pos + offsets[3]) & pos_mask]) << (3 * 8); + comb |= ((uint64_t)imap[(pos + offsets[4]) & pos_mask]) << (4 * 8); + comb |= ((uint64_t)imap[(pos + offsets[5]) & pos_mask]) << (5 * 8); + comb |= ((uint64_t)imap[(pos + offsets[6]) & pos_mask]) << (6 * 8); + comb |= ((uint64_t)imap[(pos + offsets[7]) & pos_mask]) << (7 * 8); + const uint64_t has_possible_match = + (((comb ^ bhash64) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + if (has_possible_match) { + /* there was a 7 bit match in one of the bytes in this 8 byte group */ + for (size_t i = 0; i < 8; ++i) { + const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); + if (imap[tmp] != bhash) + continue; + /* test key and hash equality */ + if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + tmp, hash)) { + if (FIO_MAP2_KEY_CMP(o->map[tmp].key, key)) { + guard_print = 0; + return (r = tmp); + } + if (!(--guard)) { + if (!guard_print) + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); + guard_print = 1; + return (r = tmp); + } + } + } + } + const uint64_t has_possible_full_byte = + (((comb)&0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + const uint64_t has_possible_empty_byte = + (((~comb) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + if (!(has_possible_full_byte | has_possible_empty_byte)) + continue; + /* there was a 7 bit match for a possible free space in this group */ + for (int i = 0; i < 8; ++i) { + const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); + if (!imap[tmp]) + return (r = tmp); /* empty slot always ends search */ + if (r > pos_mask && imap[tmp] == 255) + r = tmp; /* mark hole to be filled */ + } + } + return r; + } /* treat as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i]) + return (r = (uint32_t)i); + if (imap[i] == bhash) { + /* test key and hash equality */ + if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + i, hash)) { + if (FIO_MAP2_KEY_CMP(o->map[i].key, key)) { + guard_print = 0; + return (r = (uint32_t)i); + } + if (!(--guard)) { + if (!guard_print) + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); + guard_print = 1; + return (r = (uint32_t)i); + } + } + } + if (imap[i] == 0xFF) + r = (uint32_t)i; /* a free spot is available*/ + } + return r; +} +/* deallocate the map's memory. */ +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, + __dealloc_map)(FIO_NAME(FIO_MAP2_NAME, s) * o) { + if (!o->map) + return; + const size_t capa = FIO_MAP2_CAPA(o->bits); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, destroy)); + FIO_MEM_FREE_(o->map, (capa * sizeof(*o->map)) + capa); + (void)capa; +} + +/** duplicates an objects between two maps. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + __copy_obj)(FIO_NAME(FIO_MAP2_NAME, s) * dest, + FIO_NAME(FIO_MAP2_NAME, node_s) * o, + uint32_t internal) { + FIO_MAP2_KEY key = FIO_MAP2_KEY_FROM_INTERNAL(o->key); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(dest); +#if FIO_MAP2_RECALC_HASH + uint64_t ohash = FIO_MAP2_HASH_FN(key); + ohash += !ohash; +#else + const uint64_t ohash = o->hash; +#endif + uint32_t i = FIO_NAME(FIO_MAP2_NAME, __index)(dest, key, ohash); + if (i == (uint32_t)-1 || (imap[i] + 1) > 1) + return -1; + if (internal) { + dest->map[i] = *o; + imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); +#if FIO_MAP2_ORDERED + if (dest->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); + } else { /* set first order */ + dest->map[i].node.next = dest->map[i].node.prev = i; + dest->head = i; + } +#endif + ++dest->count; + return 0; + } + imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); + FIO_MAP2_KEY_COPY(dest->map[i].key, FIO_MAP2_KEY_FROM_INTERNAL(o->key)); + FIO_MAP2_VALUE_COPY(dest->map[i].value, + FIO_MAP2_VALUE_FROM_INTERNAL(o->value)); +#if !FIO_MAP2_RECALC_HASH + dest->map[i].hash = o->hash; +#endif +#if FIO_MAP2_ORDERED + if (dest->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); + } else { /* set first order */ + dest->map[i].node.next = dest->map[i].node.prev = i; + dest->head = i; + } +#endif + ++dest->count; + return 0; +} + +/** duplicates a map to a new copy (usually for rehashing / reserving space). */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, s) + FIO_NAME(FIO_MAP2_NAME, __duplicate)(FIO_NAME(FIO_MAP2_NAME, s) * o, + uint32_t bits, + uint32_t internal) { + FIO_NAME(FIO_MAP2_NAME, s) cpy = {0}; + if (bits > FIO_MAP2_CAPA_BITS_LIMIT) + return cpy; + size_t capa = FIO_MAP2_CAPA(bits); + cpy.map = (FIO_NAME(FIO_MAP2_NAME, node_s) *) + FIO_MEM_REALLOC_(NULL, 0, ((capa * sizeof(*cpy.map)) + capa), 0); + if (!cpy.map) + return cpy; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, destroy)); + if (!FIO_MEM_REALLOC_IS_SAFE_) { + /* set only the imap, the rest can be junk data */ + FIO_MEMSET((cpy.map + capa), 0, capa); + } + cpy.bits = bits; + if (!o->count) + return cpy; +#if FIO_MAP2_ORDERED + /* copy objects in order */ + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) + goto error; + } +#else + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + capa = FIO_MAP2_CAPA(o->bits); + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + for (size_t i = 0; i < capa; i += 8) { + uint64_t comb = *((uint64_t *)(imap + i)); + if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) + continue; + for (size_t j = 0; j < 8; ++j) { + const size_t tmp = j + i; + if (!imap[tmp] || imap[tmp] == 0xFF) + continue; + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + tmp, internal)) + goto error; + } + } + return cpy; + } /* review as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i] || imap[i] == 0xFF) + continue; + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) + goto error; + } +#endif + return cpy; +error: + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(&cpy); + cpy = (FIO_NAME(FIO_MAP2_NAME, s)){0}; + return cpy; +} + +/* destroys all objects in the map, without(!) resetting the `imap`. */ +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, + __destroy_objects)(FIO_NAME(FIO_MAP2_NAME, s) * o) { +#if FIO_MAP2_VALUE_DESTROY_SIMPLE && FIO_MAP2_KEY_DESTROY_SIMPLE + (void)o; + return; +#else + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + const size_t capa = FIO_MAP2_CAPA(o->bits); + if (FIO_MAP2_IS_SPARSE(o)) { + for (size_t i = 0; i < capa; i += 8) { + uint64_t comb = *((uint64_t *)(imap + i)); + if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) + continue; + for (size_t j = i; j < i + 8; ++j) { + FIO_MAP2_KEY_DESTROY(o->map[j].key); + FIO_MAP2_VALUE_DESTROY(o->map[j].value); + } + } + } else { /* review as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i] || imap[i] == 0xFF) + continue; + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); + } + } +#endif /* FIO_MAP2_VALUE_DESTROY_SIMPLE */ +} + +/* ***************************************************************************** +API implementation +***************************************************************************** */ + +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + capa += o->count; + if (FIO_MAP2_CAPA(o->bits) >= capa || (capa >> FIO_MAP2_CAPA_BITS_LIMIT)) + return; + uint_fast8_t bits = o->bits + 1; + while (FIO_MAP2_CAPA(bits) < capa) + ++bits; + FIO_NAME(FIO_MAP2_NAME, s) + cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); + if (!cpy.map) + return; + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = cpy; +} + +/* Removes all objects from the map, without releasing the map's resources. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->map || !o->count) + return; + FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + const size_t capa = FIO_MAP2_CAPA(o->bits); + FIO_MEMSET(imap, 0, capa); + o->count = 0; +#if FIO_MAP2_ORDERED + o->head = 0; +#endif +} + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->map || !o->count) + return; + uint32_t bits = o->bits; + while (FIO_MAP2_CAPA(bits >> 1) > o->count) + bits >>= 1; + ++bits; + for (;;) { + if (bits >= o->bits) + return; + FIO_NAME(FIO_MAP2_NAME, s) + cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); + if (!cpy.map) { + ++bits; + continue; + } + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = cpy; + return; + } +} + +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (o->map && o->count) + FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; + return; +} + +/** Evicts elements least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, + size_t number_of_elements) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return; + if (number_of_elements >= o->count) { + FIO_NAME(FIO_MAP2_NAME, clear)(map); + return; + } + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); +#ifdef FIO_MAP2_LRU /* remove last X elements from the list */ + FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, i); + imap[i] = 0xFF; + --o->count; + if (!(--number_of_elements)) + return; + } +#elif FIO_MAP2_ORDERED /* remove first X elements from the list */ + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, i); + imap[i] = 0xFF; + --o->count; + if (!(--number_of_elements)) { + o->head = o->map[i].node.next; + return; + } + } +#else /* remove whatever... */ + if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { + /* map is scattered */ + uint32_t pos_mask = (uint32_t)(FIO_MAP2_CAPA(o->bits) - 1); + uint32_t pos = *(uint32_t *)o->map; + for (int i = 0; i < 3; ++i) { + struct timespec t = {0}; + clock_gettime(CLOCK_MONOTONIC, &t); + pos *= t.tv_nsec ^ t.tv_sec ^ (uintptr_t)imap; + pos ^= pos >> 7; + } + for (;;) { /* a bit of non-random randomness... */ + uint32_t offset = ((pos << 3)) & pos_mask; + for (uint_fast8_t i = 0; i < 8; ++i) { /* ordering bias? vs performance */ + const uint32_t tmp = offset + i; + if (!imap[tmp] || imap[tmp] == 0xFF) + continue; + FIO_MAP2_KEY_DESTROY(o->map[tmp].key); + FIO_MAP2_VALUE_DESTROY(o->map[tmp].value); + imap[tmp] = 0xFF; + --o->count; + if (!(--number_of_elements)) + return; + } + pos += FIO_MAP2_CUCKOO_STEPS; + } + } + /* map is a simple array */ + while (number_of_elements--) { + FIO_MAP2_KEY_DESTROY(o->map[number_of_elements].key); + FIO_MAP2_VALUE_DESTROY(o->map[number_of_elements].value); + imap[number_of_elements] = 0xFF; + } +#endif /* FIO_MAP2_LRU / FIO_MAP2_ORDERED */ +} + +/* ***************************************************************************** +The Map set/get functions +***************************************************************************** */ + +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns the internal representation of objects. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key) { + FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; + uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) + return r; +#ifdef FIO_MAP2_LRU + if (o->head != pos) { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); + o->head = pos; + } +#endif + r = o->map + pos; + return r; +} + +/** sets / removes an object in the map, returning a pointer to the map data. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE val, + FIO_MAP2_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP2_KEY key +#endif + ) { + FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; +#ifdef FIO_MAP2_VALUE + if (old) + *old = (FIO_MAP2_VALUE_INTERNAL){0}; +#endif + FIO_NAME(FIO_MAP2_NAME, s) * o; +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash; +#endif + uint32_t pos; + uint8_t *imap = NULL; + + FIO_PTR_TAG_VALID_OR_GOTO(map, relinquish_attempt); + o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); +#if defined(FIO_MAP2_HASH_FN) + hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; /* hash is never zero */ + if (!o->bits) { /* minimal space is 8 objects... */ + *o = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, 3, 1); + } + /* find the object's (potential) position in the array */ + for (int i = 0;;) { + pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + if (pos != (uint32_t)-1) + break; + if (i == 2) + goto internal_error; + FIO_NAME(FIO_MAP2_NAME, s) + tmp = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, o->bits + (++i), 1); + if (!tmp.map) /* no memory? something bad? */ + goto internal_error; + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = tmp; + } + /* imap may have been reallocated, collect info now. */ + imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + /* set return value */ + r = o->map + pos; + + if (!imap[pos] || imap[pos] == 0xFF) { + /* insert new object */ + imap[pos] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); +#if !FIO_MAP2_RECALC_HASH + r->hash = hash; +#endif + FIO_MAP2_KEY_COPY(r->key, key); + FIO_MAP2_VALUE_COPY(r->value, val); +#if FIO_MAP2_ORDERED + if (o->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); +#ifdef FIO_MAP2_LRU + o->head = pos; /* update LRU head */ + if (FIO_MAP2_LRU == o->count) { /* limit reached - evict 1 LRU element */ + uint32_t to_evict = o->map[pos].node.prev; + FIO_MAP2_KEY_DESTROY(o->map[to_evict].key); + FIO_MAP2_VALUE_DESTROY(o->map[to_evict].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, to_evict); + imap[to_evict] = 0xFF; + --o->count; + } +#endif /* FIO_MAP2_LRU */ + } else { /* set first order */ + o->map[pos].node.next = o->map[pos].node.prev = pos; + o->head = pos; + } +#endif /* FIO_MAP2_ORDERED */ + ++o->count; + return r; + } + +#ifdef FIO_MAP2_LRU + /* update ordering (even if not overwriting) */ + if (o->head != pos) { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); + o->head = pos; + } +#endif + +#ifdef FIO_MAP2_VALUE + if (overwrite) { + /* overwrite existing object (only relevant for hash maps) */ + FIO_MAP2_KEY_DISCARD(key); + if (!old) { + FIO_MAP2_VALUE_DESTROY(o->map[pos].value); + FIO_MAP2_VALUE_COPY(o->map[pos].value, val); + return r; + } + *old = o->map[pos].value; + o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; + FIO_MAP2_VALUE_COPY(o->map[pos].value, val); + return r; + } +#endif +relinquish_attempt: + /* discard attempt */ + FIO_MAP2_KEY_DISCARD(key); + FIO_MAP2_VALUE_DISCARD(val); + return r; +internal_error: + FIO_MAP2_KEY_DISCARD(key); + FIO_MAP2_VALUE_DISCARD(val); + FIO_LOG_ERROR("unknown error occurred trying to add an entry to the map"); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen"); + return r; +} + +/* ***************************************************************************** +The Map remove function +***************************************************************************** */ + +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key, +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY_INTERNAL *old +#endif +) { +#ifdef FIO_MAP2_VALUE + if (old) + *old = (FIO_MAP2_VALUE_INTERNAL){0}; +#else + if (old) + *old = (FIO_MAP2_KEY_INTERNAL){0}; +#endif + + FIO_PTR_TAG_VALID_OR_RETURN(map, -1); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; /* hash is never zero */ + uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + + if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) + return -1; + + imap[pos] = 0xFF; /* mark hole and update count */ + --o->count; + +#if FIO_MAP2_ORDERED + /* update ordering */ + if (o->head == pos) + o->head = o->map[pos].node.next; + if (o->head == pos) + o->head = 0; + else { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + } +#endif + +/* destroy data, copy to `old` pointer if necessary. */ +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY_DESTROY(o->map[pos].key); + o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; + if (!old) { + FIO_MAP2_VALUE_DESTROY(o->map[pos].value); + } else { + *old = o->map[pos].value; + } + o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; +#else + if (!old) { + FIO_MAP2_KEY_DESTROY(o->map[pos].key); + } else { + *old = o->map[pos].key; + } + o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; +#endif +#if !FIO_MAP2_RECALC_HASH && defined(DEBUG) + o->map[pos].hash = 0; /* not necessary, but ... good for debugging? */ +#endif + return 0; +} + +/* ***************************************************************************** +Map Iteration +***************************************************************************** */ + +/** Returns the next iterator position after `current_pos`, first if `NULL`. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_next)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); + size_t pos_counter = 0; + if (!current_pos || !current_pos->private_.map_validator) { + goto find_pos; + } + if (current_pos->private_.pos + 1 == o->count) + return r; + r.private_.pos = current_pos->private_.pos + 1; + if (current_pos->private_.map_validator != (uintptr_t)o) { + goto refind_pos; + } + r.private_.index = current_pos->private_.index; + +#if !FIO_MAP2_RECALC_HASH +#define FIO_MAP2___EACH_COPY_HASH() r.hash = o->map[r.private_.index].hash +#else +#define FIO_MAP2___EACH_COPY_HASH() +#endif + +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2___EACH_COPY_DATA() \ + FIO_MAP2___EACH_COPY_HASH(); \ + r.private_.map_validator = (uintptr_t)o; \ + r.node = o->map + r.private_.index; \ + r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key); \ + r.value = FIO_MAP2_VALUE_FROM_INTERNAL(o->map[r.private_.index].value) +#else +#define FIO_MAP2___EACH_COPY_DATA() \ + FIO_MAP2___EACH_COPY_HASH(); \ + r.private_.map_validator = (uintptr_t)o; \ + r.node = o->map + r.private_.index; \ + r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key) +#endif + +/* start seeking at the position inherited from current_pos */ +#if FIO_MAP2_ORDERED + (void)imap; /* unused in ordered maps */ + (void)capa; /* unused in ordered maps */ + r.private_.index = o->map[r.private_.index].node.next; + if (r.private_.index == o->head) + goto not_found; + FIO_MAP2___EACH_COPY_DATA(); + return r; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while ((++r.private_.index) & 7) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + while (r.private_.index < capa) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index += 8; + continue; + } + for (int i = 0; i < 8; (++i), (++r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while ((++r.private_.index) < capa) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#endif /* FIO_MAP2_ORDERED */ + +refind_pos: + if (current_pos->private_.index) + goto not_found; +find_pos: +/* first seek... re-start seeking */ +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; + } + r.private_.index = (uint32_t)i; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while (r.private_.index < capa) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index += 8; + continue; + } + for (int i = 0; i < 8; (++i), (++r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while (r.private_.index < capa) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) { + ++r.private_.index; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; + } +#endif /* FIO_MAP2_ORDERED */ + +not_found: + return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); + FIO_ASSERT_DEBUG(0, "should this happen? ever?"); +} + +/** Returns the next iterator position after `current_pos`, first if `NULL`. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_prev)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; +#if !FIO_MAP2_ORDERED + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); +#endif + size_t pos_counter = o->count; + if (!current_pos || !current_pos->private_.map_validator) { + r.private_.map_validator = (uintptr_t)o; + r.private_.pos = o->count; + goto find_pos; + } + if (!current_pos->private_.pos) + return r; + r.private_.pos = current_pos->private_.pos - 1; + r.private_.map_validator = (uintptr_t)o; + if (current_pos->private_.map_validator != (uintptr_t)o) { + goto refind_pos; + } + r.private_.index = current_pos->private_.index; + +/* start seeking at the position inherited from current_pos */ +#if FIO_MAP2_ORDERED + if (r.private_.index == o->head) + goto not_found; + r.private_.index = o->map[r.private_.index].node.prev; + FIO_MAP2___EACH_COPY_DATA(); + return r; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while ((--r.private_.index) & 7) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + while (r.private_.index) { + uint64_t simd = *(uint64_t *)(imap + (r.private_.index - 8)); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index -= 8; + continue; + } + for (int i = 0; i < 8; ++i) { + --r.private_.index; + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while (r.private_.index--) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#endif /* FIO_MAP2_ORDERED */ + +refind_pos: + if (current_pos->private_.index) + goto not_found; +find_pos: +/* first seek... re-start seeking */ +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { + if (pos_counter != r.private_.pos) { + --pos_counter; + continue; + } + r.private_.index = (uint32_t)i; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#else + r.private_.index = (uint32_t)capa; + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while (r.private_.index) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index -= 8; + continue; + } + for (int i = 0; i < 8; (++i), (--r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while ((r.private_.index--)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } +#endif /* FIO_MAP2_ORDERED */ + +not_found: + return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); + FIO_ASSERT_DEBUG(0, "should this happen? ever?"); +} +#undef FIO_MAP2___EACH_COPY_HASH +#undef FIO_MAP2___EACH_COPY_DATA + +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + each)(FIO_MAP2_PTR map, + int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), + void *udata, + ssize_t start_at) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 1); + FIO_NAME(FIO_MAP2_NAME, each_s) + e = { + .parent = map, + .task = task, + .udata = udata, + }; + FIO_NAME(FIO_MAP2_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); + if (start_at < 0) { + start_at += o->count; + if (start_at < 0) + start_at = 0; + } else if (start_at > o->count) + return o->count; + FIO_NAME(FIO_MAP2_NAME, iterator_s) i = {.private_ = {.pos = 0}}; + for (;;) { + i = FIO_NAME(FIO_MAP2_NAME, get_next)(map, &i); + if (!FIO_NAME(FIO_MAP2_NAME, iterator_is_valid)(&i)) + return o->count; + e.index = i.private_.pos; + e.key = i.key; +#ifdef FIO_MAP2_VALUE + e.value = i.value; +#endif + if (e.task(&e)) + return (uint32_t)(e.index + 1); + } + return o->count; +} + +/* ***************************************************************************** +Speed Testing +***************************************************************************** */ + +/* ***************************************************************************** +Map Testing +***************************************************************************** */ +#ifdef FIO_MAP2_TEST + +#ifdef FIO_MAP2_HASH_FN +#define FIO___M_HASH(k) +#else +#define FIO___M_HASH(k) (k), +#endif +#ifdef FIO_MAP2_VALUE +#define FIO___M_VAL(v) , (v) +#define FIO___M_OLD , NULL +#else +#define FIO___M_VAL(v) +#define FIO___M_OLD +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP2_NAME)(void) { + /* testing only only works with integer external types */ + fprintf(stderr, + "* Testing maps with key " FIO_MACRO2STR( + FIO_MAP2_KEY) " (=> " FIO_MACRO2STR(FIO_MAP2_VALUE) ").\n"); + { /* test set / get overwrite , FIO_MAP2_EACH and evict */ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + for (size_t i = 1; i < (1UL << (FIO_MAP2_ARRAY_LOG_LIMIT + 5)); ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "map `set` failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + for (size_t j = ((i << 2) + 1); j < i; ++j) { /* effects LRU ordering */ + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && + FIO_NAME(FIO_MAP2_NAME, node2val)( + FIO_NAME(FIO_MAP2_NAME, + get_ptr)(&map, FIO___M_HASH(j) j)) == j, + "map `get` failed? %zu/%zu (%p)", + j, + i, + FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(j) j FIO___M_VAL(j) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "map `set` added an item that already exists? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + } + } + /* test FIO_MAP2_EACH and ordering */ + uint32_t count = FIO_NAME(FIO_MAP2_NAME, count)(&map); + uint32_t loop_test = 0; + FIO_MAP2_EACH(FIO_MAP2_NAME, &map, i) { + /* test ordering */ +#ifdef FIO_MAP2_LRU + FIO_ASSERT(i.key == (count - loop_test), + "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(count - loop_test)); + ++loop_test; +#elif FIO_MAP2_ORDERED + ++loop_test; + FIO_ASSERT(i.key == loop_test, + "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(loop_test)); +#else + ++loop_test; +#endif + } + FIO_ASSERT(loop_test == count, + "FIO_MAP2_EACH failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + loop_test = 0; + FIO_MAP2_EACH_REVERSED(FIO_MAP2_NAME, &map, i) { ++loop_test; } + FIO_ASSERT( + loop_test == count, + "FIO_MAP2_EACH_REVERSED failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + /* test `evict` while we're here */ + FIO_NAME(FIO_MAP2_NAME, evict)(&map, (count >> 1)); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == (count - (count >> 1)), + "map `evict` count error %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)(count - (count >> 1))); + /* cleanup */ + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } +#ifndef FIO_MAP2_HASH_FN + { /* test full collision guard and zero hash*/ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + fprintf( + stderr, + "* Testing full collision guard for " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " - expect SECURITY log messages.\n"); + for (size_t i = 1; i < 4096; ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(0) i FIO___M_VAL(i) FIO___M_OLD); + } + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map), + "zero hash fails insertion?"); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) <= FIO_MAP2_ATTACK_LIMIT, + "map attack guard failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)FIO_MAP2_ATTACK_LIMIT); + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } +#endif + { /* test reserve, remove */ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + FIO_NAME(FIO_MAP2_NAME, reserve)(&map, 4096); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, capa)(&map) == 4096, + "map reserve error? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, capa)(&map), + 4096); + for (size_t i = 1; i < 4096; ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "insertion failed?"); + } + for (size_t i = 1; i < 4096; ++i) { + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + "key missing?"); + FIO_NAME(FIO_MAP2_NAME, remove) + (&map, FIO___M_HASH(i) i, NULL); + FIO_ASSERT(!FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + "map_remove error?"); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == 4095 - i, + "map count error after removal? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + } + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } +} +#undef FIO___M_HASH +#undef FIO___M_VAL +#undef FIO___M_OLD + +#endif /* FIO_MAP2_TEST */ +/* ***************************************************************************** +Map Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ + +#undef FIO_MAP2_ARRAY_LOG_LIMIT +#undef FIO_MAP2_ATTACK_LIMIT +#undef FIO_MAP2_CAPA +#undef FIO_MAP2_CAPA_BITS_LIMIT +#undef FIO_MAP2_CUCKOO_STEPS +#undef FIO_MAP2_GET_T +#undef FIO_MAP2_HASH_FN +#undef FIO_MAP2_IS_SPARSE +#undef FIO_MAP2_KEY +#undef FIO_MAP2_KEY_CMP +#undef FIO_MAP2_KEY_COPY +#undef FIO_MAP2_KEY_DESTROY +#undef FIO_MAP2_KEY_DESTROY_SIMPLE +#undef FIO_MAP2_KEY_DISCARD +#undef FIO_MAP2_KEY_FROM_INTERNAL +#undef FIO_MAP2_KEY_INTERNAL +#undef FIO_MAP2_KEY_IS_GREATER_THAN +#undef FIO_MAP2_LRU +#undef FIO_MAP2_NAME +#undef FIO_MAP2_ORDERED +#undef FIO_MAP2_PTR +#undef FIO_MAP2_RECALC_HASH +#undef FIO_MAP2_SEEK_LIMIT +#undef FIO_MAP2_T +#undef FIO_MAP2_TEST +#undef FIO_MAP2_VALUE +#undef FIO_MAP2_VALUE_BSTR +#undef FIO_MAP2_VALUE_COPY +#undef FIO_MAP2_VALUE_DESTROY +#undef FIO_MAP2_VALUE_DESTROY_SIMPLE +#undef FIO_MAP2_VALUE_DISCARD +#undef FIO_MAP2_VALUE_FROM_INTERNAL +#undef FIO_MAP2_VALUE_INTERNAL +#undef FIO_OMAP_NAME +#undef FIO_UMAP_NAME + +#endif /* FIO_MAP2_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_REF_NAME long_ref /* Development inclusion - ignore line */ +#define FIO_REF_TYPE long /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Reference Counting / Wrapper + (must be placed after all type macros) + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef FIO_REF_NAME + +#ifndef FIO_REF_TYPE +#define FIO_REF_TYPE FIO_NAME(FIO_REF_NAME, s) +#endif + +#ifndef FIO_REF_INIT +#define FIO_REF_INIT(obj) \ + do { \ + if (!FIO_MEM_REALLOC_IS_SAFE_) \ + (obj) = (FIO_REF_TYPE){0}; \ + } while (0) +#endif + +#ifndef FIO_REF_DESTROY +#define FIO_REF_DESTROY(obj) +#endif + +#ifndef FIO_REF_METADATA_INIT +#ifdef FIO_REF_METADATA +#define FIO_REF_METADATA_INIT(meta) \ + do { \ + if (!FIO_MEM_REALLOC_IS_SAFE_) \ + (meta) = (FIO_REF_METADATA){0}; \ + } while (0) +#else +#define FIO_REF_METADATA_INIT(meta) +#endif +#endif + +#ifndef FIO_REF_METADATA_DESTROY +#define FIO_REF_METADATA_DESTROY(meta) +#endif + +/** + * FIO_REF_CONSTRUCTOR_ONLY allows the reference counter constructor (TYPE_new) + * to be the only constructor function. + * + * When set, the reference counting functions will use `X_new` and `X_free`. + * Otherwise (assuming `X_new` and `X_free` are already defined), the reference + * counter will define `X_new2` and `X_free2` instead. + */ +#ifdef FIO_REF_CONSTRUCTOR_ONLY +#define FIO_REF_CONSTRUCTOR new +#define FIO_REF_DESTRUCTOR free +#define FIO_REF_DUPNAME dup +#else +#define FIO_REF_CONSTRUCTOR new2 +#define FIO_REF_DESTRUCTOR free2 +#define FIO_REF_DUPNAME dup2 +#endif + +typedef struct { +#ifdef FIO_REF_FLEX_TYPE + volatile uint32_t ref; + uint32_t flx_size; +#else + volatile size_t ref; +#endif +#ifdef FIO_REF_METADATA + FIO_REF_METADATA metadata; +#endif +} FIO_NAME(FIO_REF_NAME, _wrapper_s); + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_REF_TYPE_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_REF_TYPE_PTR FIO_REF_TYPE * +#endif + +/* ***************************************************************************** +Reference Counter (Wrapper) API +***************************************************************************** */ + +/** Allocates a reference counted object. */ +#ifdef FIO_REF_FLEX_TYPE +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_CONSTRUCTOR)(size_t members); +#else +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void); +#endif /* FIO_REF_FLEX_TYPE */ + +/** Increases the reference count. */ +FIO_IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped); + +/** Frees a reference counted object (or decreases the reference count). */ +IFUNC void FIO_NAME(FIO_REF_NAME, FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped); + +#ifdef FIO_REF_METADATA +/** Returns a pointer to the object's metadata, if defined. */ +IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, + metadata)(FIO_REF_TYPE_PTR wrapped); +#endif + +#ifdef FIO_REF_FLEX_TYPE +/** The allocation length for the flex type */ +IFUNC uint32_t FIO_NAME(FIO_REF_NAME, + metadata_flex_len)(FIO_REF_TYPE_PTR wrapped); +#endif +/* ***************************************************************************** +Inline Implementation +***************************************************************************** */ +/** Increases the reference count. */ +FIO_IFUNC FIO_REF_TYPE_PTR +FIO_NAME(FIO_REF_NAME, FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + fio_atomic_add(&o->ref, 1); + return wrapped_; +} + +/** Debugging helper, do not use for data, as returned value is unstable. */ +FIO_IFUNC size_t FIO_NAME(FIO_REF_NAME, references)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return o->ref; +} + +#ifdef FIO_REF_FLEX_TYPE +/** The allocation length for the flex type */ +IFUNC uint32_t FIO_NAME(FIO_REF_NAME, + metadata_flex_len)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return o->flx_size; +} +#endif + +/* ***************************************************************************** +Reference Counter (Wrapper) Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(FIO_REF_NAME) + +/** Allocates a reference counted object. */ +#ifdef FIO_REF_FLEX_TYPE +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_CONSTRUCTOR)(size_t members) { + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + (FIO_NAME(FIO_REF_NAME, _wrapper_s) *)FIO_MEM_REALLOC_( + NULL, + 0, + sizeof(*o) + sizeof(FIO_REF_TYPE) + + (sizeof(FIO_REF_FLEX_TYPE) * members), + 0); +#else +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void) { + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = (FIO_NAME(FIO_REF_NAME, _wrapper_s) *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*o) + sizeof(FIO_REF_TYPE), 0); +#endif /* FIO_REF_FLEX_TYPE */ + if (!o) + return (FIO_REF_TYPE_PTR)(o); + FIO_LEAK_COUNTER_ON_ALLOC(FIO_REF_NAME); + o->ref = 1; +#ifdef FIO_REF_FLEX_TYPE + o->flx_size = (uint32_t)members; +#endif + FIO_REF_METADATA_INIT((o->metadata)); + FIO_REF_TYPE *ret = (FIO_REF_TYPE *)(o + 1); + FIO_REF_INIT((ret[0])); + return (FIO_REF_TYPE_PTR)(FIO_PTR_TAG(ret)); + (void)FIO_NAME(FIO_REF_NAME, references); +} + +/** Frees a reference counted object (or decreases the reference count). */ +IFUNC void FIO_NAME(FIO_REF_NAME, + FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return; + FIO_PTR_TAG_VALID_OR_RETURN_VOID(wrapped_); + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + if (!o) + return; + if (fio_atomic_sub_fetch(&o->ref, 1)) + return; + FIO_REF_DESTROY((wrapped[0])); + FIO_REF_METADATA_DESTROY((o->metadata)); + FIO_LEAK_COUNTER_ON_FREE(FIO_REF_NAME); + FIO_MEM_FREE_(o, sizeof(*o) + (o->flx_size * sizeof(FIO_REF_TYPE))); +} + +#ifdef FIO_REF_METADATA +/** Returns a pointer to the object's metadata, if defined. */ +IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, + metadata)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return &o->metadata; +} +#endif + +/* ***************************************************************************** +Reference Counter (Wrapper) Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_REF_NAME +#undef FIO_REF_FLEX_TYPE +#undef FIO_REF_TYPE +#undef FIO_REF_INIT +#undef FIO_REF_DESTROY +#undef FIO_REF_METADATA +#undef FIO_REF_METADATA_INIT +#undef FIO_REF_METADATA_DESTROY +#undef FIO_REF_TYPE_PTR +#undef FIO_REF_CONSTRUCTOR_ONLY +#undef FIO_REF_CONSTRUCTOR +#undef FIO_REF_DUPNAME +#undef FIO_REF_DESTRUCTOR +#endif +/* ***************************************************************************** +Pointer Tagging Cleanup +***************************************************************************** */ +#ifndef FIO___DEV___ +#undef FIO_PTR_TAG +#undef FIO_PTR_UNTAG +#undef FIO_PTR_TAG_TYPE +#undef FIO_PTR_TAG_VALIDATE +#undef FIO_PTR_TAG_VALID_OR_RETURN +#undef FIO_PTR_TAG_VALID_OR_RETURN_VOID +#undef FIO_PTR_TAG_VALID_OR_GOTO +#endif +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_FIOBJ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + + + + + FIOBJ - soft (dynamic) types + + + +FIOBJ - dynamic types + +These are dynamic types that use pointer tagging for fast type identification. + +Pointer tagging on 64 bit systems allows for 3 bits at the lower bits. On most +32 bit systems this is also true due to allocator alignment. When in doubt, use +the provided custom allocator. + +To keep the 64bit memory address alignment on 32bit systems, a 32bit metadata +integer is added when a virtual function table is missing. This doesn't effect +memory consumption on 64 bit systems and uses 4 bytes on 32 bit systems. + +Note: this code is placed at the end of the STL file, since it leverages most of +the SLT features and could be affected by their inclusion. + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_FIOBJ) && !defined(H___FIO_FIOBJ___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_FIOBJ___H +#define FIO___RECURSIVE_INCLUDE 99 /* 99 keeps EXTERN rules */ +/* ***************************************************************************** +FIOBJ compilation settings (type names and JSON nesting limits). + +Type Naming Macros for FIOBJ types. By default, results in: +- fiobj_true() (constant, cannot be changed except manually) +- fiobj_false() (constant, cannot be changed except manually) +- fiobj_null() +- fiobj_num_new() ... (etc') +- fiobj_float_new() ... (etc') +- fiobj_str_new() ... (etc') +- fiobj_array_new() ... (etc') +- fiobj_hash_new() ... (etc') +***************************************************************************** */ + +#ifndef FIOBJ___NAME_NULL +#define FIOBJ___NAME_NULL null +#endif +#ifndef FIOBJ___NAME_NUMBER +#define FIOBJ___NAME_NUMBER num +#endif +#ifndef FIOBJ___NAME_FLOAT +#define FIOBJ___NAME_FLOAT float +#endif +#ifndef FIOBJ___NAME_STRING +#define FIOBJ___NAME_STRING str +#endif +#ifndef FIOBJ___NAME_ARRAY +#define FIOBJ___NAME_ARRAY array +#endif +#ifndef FIOBJ___NAME_HASH +#define FIOBJ___NAME_HASH hash +#endif + +#ifndef FIOBJ_MAX_NESTING +/** + * Sets the limit on nesting level transversal by recursive functions. + * + * This effects JSON output / input and the `fiobj_each2` function since they + * are recursive. + * + * HOWEVER: this value will NOT effect the recursive `fiobj_free` which could + * (potentially) expload the stack if given melformed input such as cyclic data + * structures. + * + * Values should be less than 32K. + */ +#define FIOBJ_MAX_NESTING 512 +#endif + +/* make sure roundtrips work */ +#ifndef JSON_MAX_DEPTH +#define JSON_MAX_DEPTH FIOBJ_MAX_NESTING +#endif + +#ifndef FIOBJ_JSON_APPEND +#define FIOBJ_JSON_APPEND 1 +#endif +/* ***************************************************************************** +General Requirements / Macros +***************************************************************************** */ + +#ifdef __cplusplus /* C++ doesn't allow declarations for static variables */ +#define FIOBJ_EXTERN_OBJ extern "C" FIO_WEAK +#define FIOBJ_EXTERN_OBJ_IMP extern "C" FIO_WEAK +#elif defined(FIO_EXTERN) +#define FIOBJ_EXTERN_OBJ extern +#define FIOBJ_EXTERN_OBJ_IMP FIO_WEAK +#else +#define FIOBJ_EXTERN_OBJ static __attribute__((unused)) +#define FIOBJ_EXTERN_OBJ_IMP static __attribute__((unused)) +#endif +/* ***************************************************************************** +Debugging / Leak Detection +***************************************************************************** */ +#if defined(TEST) || defined(DEBUG) || defined(FIO_LEAK_COUNTER) +size_t FIO_WEAK FIOBJ_MARK_MEMORY_ALLOC_COUNTER; +size_t FIO_WEAK FIOBJ_MARK_MEMORY_FREE_COUNTER; +#define FIOBJ_MARK_MEMORY_ALLOC() \ + fio_atomic_add(&FIOBJ_MARK_MEMORY_ALLOC_COUNTER, 1) +#define FIOBJ_MARK_MEMORY_FREE() \ + fio_atomic_add(&FIOBJ_MARK_MEMORY_FREE_COUNTER, 1) +#define FIOBJ_MARK_MEMORY_PRINT() \ + FIO___LOG_PRINT_LEVEL( \ + ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ + ? 4 /* FIO_LOG_LEVEL_INFO */ \ + : 3 /* FIO_LOG_LEVEL_WARNING */), \ + ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ + ? "INFO: total remaining FIOBJ allocations: %zu (%zu - %zu)" \ + : "WARNING: LEAKED! FIOBJ allocations: %zu (%zu - %zu)"), \ + FIOBJ_MARK_MEMORY_ALLOC_COUNTER - FIOBJ_MARK_MEMORY_FREE_COUNTER, \ + FIOBJ_MARK_MEMORY_ALLOC_COUNTER, \ + FIOBJ_MARK_MEMORY_FREE_COUNTER) +#define FIOBJ_MARK_MEMORY_ENABLED 1 + +#else + +#define FIOBJ_MARK_MEMORY_ALLOC_COUNTER 0 /* when testing unmarked FIOBJ */ +#define FIOBJ_MARK_MEMORY_FREE_COUNTER 0 /* when testing unmarked FIOBJ */ +#define FIOBJ_MARK_MEMORY_ALLOC() +#define FIOBJ_MARK_MEMORY_FREE() +#define FIOBJ_MARK_MEMORY_PRINT() +#define FIOBJ_MARK_MEMORY_ENABLED 0 +#endif + +/* ***************************************************************************** +The FIOBJ Type +***************************************************************************** */ + +/** Use the FIOBJ type for dynamic types. */ +typedef struct FIOBJ_s { + struct FIOBJ_s *compiler_validation_type; +} * FIOBJ; + +/** FIOBJ type enum for common / primitive types. */ +typedef enum { + FIOBJ_T_NUMBER = 0x01, /* 0b001 3 bits taken for small numbers */ + FIOBJ_T_PRIMITIVE = 2, /* 0b010 a lonely second bit signifies a primitive */ + FIOBJ_T_STRING = 3, /* 0b011 */ + FIOBJ_T_ARRAY = 4, /* 0b100 */ + FIOBJ_T_HASH = 5, /* 0b101 */ + FIOBJ_T_FLOAT = 6, /* 0b110 */ + FIOBJ_T_OTHER = 7, /* 0b111 dynamic type - test content */ +} fiobj_class_en; + +#define FIOBJ_T_NULL 2 /* 0b010 a lonely second bit signifies a primitive */ +#define FIOBJ_T_TRUE 18 /* 0b010 010 - primitive value */ +#define FIOBJ_T_FALSE 34 /* 0b100 010 - primitive value */ + +/** Use the macros to avoid future API changes. */ +#define FIOBJ_TYPE(o) fiobj_type(o) +/** Use the macros to avoid future API changes. */ +#define FIOBJ_TYPE_IS(o, type) (fiobj_type(o) == type) +/** Identifies an invalid type identifier (returned from FIOBJ_TYPE(o) */ +#define FIOBJ_T_INVALID 0 +/** Identifies an invalid object */ +#define FIOBJ_INVALID 0 +/** Tests if the object is (probably) a valid FIOBJ */ +#define FIOBJ_IS_INVALID(o) (((uintptr_t)(o)&7UL) == 0) +#define FIOBJ_IS_NULL(o) (FIOBJ_IS_INVALID(o) || ((o) == FIOBJ_T_NULL)) +#define FIOBJ_TYPE_CLASS(o) ((fiobj_class_en)(((uintptr_t)(o)) & 7UL)) +#define FIOBJ_PTR_TAG(o, klass) ((uintptr_t)(((uintptr_t)(o)) | (klass))) +#define FIOBJ_PTR_UNTAG(o) ((uintptr_t)(((uintptr_t)(o)) & (~7ULL))) +/** Returns an objects type. This isn't limited to known types. */ +FIO_IFUNC size_t fiobj_type(FIOBJ o); + +/* ***************************************************************************** +FIOBJ Memory Management +***************************************************************************** */ + +/** Increases an object's reference count (or copies) and returns it. */ +FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o); + +/** Decreases an object's reference count or frees it. */ +FIO_IFUNC void fiobj_free(FIOBJ o); + +/* ***************************************************************************** +FIOBJ Data / Info +***************************************************************************** */ + +/** Compares two objects. */ +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b); + +/** Returns a temporary String representation for any FIOBJ object. */ +FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o); + +/** Returns an integer representation for any FIOBJ object. */ +FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o); + +/** Returns a float (double) representation for any FIOBJ object. */ +FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o); + +/** Calculates an object's hash value for a specific hash map object. */ +FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ object_key); + +/* ***************************************************************************** +FIOBJ Containers (iteration) +***************************************************************************** */ + +/** Iteration information structure passed to the callback. */ +typedef struct fiobj_each_s { + /** The being iterated. Once set, cannot be safely changed. */ + FIOBJ const parent; + /** The index to start at / the current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct fiobj_each_s *info); + /** The argument passed along to the task. */ + void *udata; + /** The value of the current object in the Array or Hash Map */ + FIOBJ value; + /* The key, if a Hash Map */ + FIOBJ key; +} fiobj_each_s; + +/** + * Performs a task for each element held by the FIOBJ object. + * + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the "stop" position - the number of elements processed + `start_at`. + */ +FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, + int (*task)(fiobj_each_s *info), + void *udata, + int32_t start_at); + +/** + * Performs a task for the object itself and each element held by the FIOBJ + * object or any of it's elements (a deep task). + * + * The order of performance is by order of appearance, as if all nesting levels + * were flattened. + * + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the number of elements processed. + */ +SFUNC uint32_t fiobj_each2(FIOBJ o, + int (*task)(fiobj_each_s *info), + void *udata); + +/* ***************************************************************************** +FIOBJ Primitives (NULL, True, False) +***************************************************************************** */ + +/** Returns the `true` primitive. */ +FIO_IFUNC FIOBJ fiobj_true(void) { return (FIOBJ)(FIOBJ_T_TRUE); } + +/** Returns the `false` primitive. */ +FIO_IFUNC FIOBJ fiobj_false(void) { return (FIOBJ)(FIOBJ_T_FALSE); } + +/** Returns the `nil` / `null` primitive. */ +FIO_IFUNC FIOBJ FIO_NAME(fiobj, FIOBJ___NAME_NULL)(void) { + return (FIOBJ)(FIOBJ_T_NULL); +} + +/* ***************************************************************************** +FIOBJ Type - Extensibility (FIOBJ_T_OTHER) +***************************************************************************** */ + +/** FIOBJ types can be extended using virtual function tables. */ +typedef struct { + /** + * MUST return a unique number to identify object type. + * + * Numbers (type IDs) under 100 are reserved. Numbers under 40 are illegal. + */ + size_t type_id; + /** Test for equality between two objects with the same `type_id` */ + unsigned char (*is_eq)(FIOBJ restrict a, FIOBJ restrict b); + /** Converts an object to a String */ + fio_str_info_s (*to_s)(FIOBJ o); + /** Converts an object to an integer */ + intptr_t (*to_i)(FIOBJ o); + /** Converts an object to a double */ + double (*to_f)(FIOBJ o); + /** Returns the number of exposed elements held by the object, if any. */ + uint32_t (*count)(FIOBJ o); + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + uint32_t (*each1)(FIOBJ o, + int (*task)(fiobj_each_s *e), + void *udata, + int32_t start_at); + /** + * Decreases the reference count and/or frees the object, calling `free2` for + * any nested objects. + */ + void (*free2)(FIOBJ o); +} FIOBJ_class_vtable_s; + +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL; + +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_NAME fiobj_object +#define FIO_REF_TYPE void * +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___OBJECT_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/* ***************************************************************************** +FIOBJ Integers +***************************************************************************** */ + +/** Creates a new Number object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(intptr_t i); + +/** Reads the number from a FIOBJ Number. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i); + +/** Reads the number from a FIOBJ Number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i); + +/** Returns a String representation of the number (in base 10). */ +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + cstr)(FIOBJ i); + +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i); + +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL; + +/* ***************************************************************************** +FIOBJ Floats +***************************************************************************** */ + +/** Creates a new Float (double) object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i); + +/** Reads the number from a FIOBJ Float rounding it to an integer. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i); + +/** Reads the value from a FIOBJ Float, as a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i); + +/** Returns a String representation of the float. */ +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), + cstr)(FIOBJ i); + +/** Frees a FIOBJ Float. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i); + +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL; + +/* ***************************************************************************** +FIOBJ Strings +***************************************************************************** */ + +#define FIO_STR_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) +#define FIO_STR_OPTIMIZE_EMBEDDED 1 +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(s) \ + do { \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&s, FIOBJ_T_STRING)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(s_) \ + do { \ + s_ = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s))FIO_STR_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) + +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t +#endif +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_STRING) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_STRING) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/* Creates a new FIOBJ string object, copying the data to the new string. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_cstr)(const char *ptr, size_t len) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, ptr, len); + return s; +} + +/* Creates a new FIOBJ string object with (at least) the requested capacity. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_buf)(size_t capa) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), reserve)(s, capa); + return s; +} + +/* Creates a new FIOBJ string object, copying the origin (`fiobj2cstr`). */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_copy)(FIOBJ original) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + fio_str_info_s i = FIO_NAME2(fiobj, cstr)(original); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, i.buf, i.len); + return s; +} + +/** Returns information about the string. Same as fiobj_str_info(). */ +FIO_IFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + cstr)(FIOBJ s) { + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(s); +} + +/** + * Creates a temporary FIOBJ String object on the stack. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR(str_name) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** + * Creates a temporary FIOBJ String object on the stack, initialized with a + * static string. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR_STATIC(str_name, buf_, len_) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, \ + __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT_STATIC2((buf_), (len_))}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** + * Creates a temporary FIOBJ String object on the stack, initialized with a + * static string. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR_EXISTING(str_name, buf_, len_, capa_) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, __auto_mem_tmp) = { \ + 0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT_EXISTING((buf_), (len_), (capa_))}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** Resets a temporary FIOBJ String, freeing and any resources allocated. */ +#define FIOBJ_STR_TEMP_DESTROY(str_name) \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy)(str_name); + +/* ***************************************************************************** +FIOBJ Arrays +***************************************************************************** */ + +#define FIO_ARRAY_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(a) \ + do { \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&a, FIOBJ_T_ARRAY)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(a) \ + do { \ + a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), s))FIO_ARRAY_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t +#endif +#define FIO_ARRAY_TYPE FIOBJ +#define FIO_ARRAY_TYPE_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) +#define FIO_ARRAY_TYPE_DESTROY(o) fiobj_free(o) +#define FIO_ARRAY_TYPE_CONCAT_COPY(dest, obj) \ + do { \ + dest = fiobj_dup(obj); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_ARRAY) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_ARRAY) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/* ***************************************************************************** +FIOBJ Hash Maps +***************************************************************************** */ + +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(a) \ + do { \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&(a), FIOBJ_T_HASH)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(a) \ + do { \ + a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), s))FIO_MAP_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t +#endif +#define FIO_MAP_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) +#define FIO_MAP_ORDERED 1 +#define FIO_MAP_KEY FIOBJ +#define FIO_MAP_KEY_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) +#define FIO_MAP_KEY_COPY(dest, o) (dest = fiobj_dup(o)) +#define FIO_MAP_KEY_DESTROY(o) fiobj_free(o) +#define FIO_MAP_VALUE FIOBJ +#define FIO_MAP_HASH_FN(o) FIO_NAME2(fiobj, hash)(o) +#define FIO_MAP_VALUE_DESTROY(o) fiobj_free(o) +#define FIO_MAP_VALUE_DISCARD(o) fiobj_free(o) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_HASH) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_HASH) +#define FIO_PTR_TAG_TYPE FIOBJ +/* TODO! auto-hash object value */ +#include FIO_INCLUDE_FILE +/** + * Sets a value in a hash map, allocating the key String and automatically + * calculating the hash value. + */ +FIO_IFUNC +FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + set2)(FIOBJ hash, const char *key, size_t len, FIOBJ value); + +/** + * Finds a value in the hash map, using a temporary String and automatically + * calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + get2)(FIOBJ hash, const char *buf, size_t len); + +/** + * Removes a value in a hash map, using a temporary String and automatically + * calculating the hash value. + */ +FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + remove2)(FIOBJ hash, + const char *buf, + size_t len, + FIOBJ *old); + +/* ***************************************************************************** +FIOBJ JSON support +***************************************************************************** */ + +/** + * Returns a JSON valid FIOBJ String, representing the object. + * + * If `dest` is an existing String, the formatted JSON data will be appended to + * the existing string. + */ +FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify); + +/** + * Updates a Hash using JSON data. + * + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. + * + * Conflicting Hash data is overwritten (preferring the new over the old). + * + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. + */ +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str); + +/** Helper function, calls `fiobj_hash_update_json` with string information */ +FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json2)(FIOBJ hash, char *ptr, size_t len); + +/** + * Parses a C string for JSON data. If `consumed` is not NULL, the `size_t` + * variable will contain the number of bytes consumed before the parser stopped + * (due to either error or end of a valid JSON data segment). + * + * Returns a FIOBJ object matching the JSON valid C string `str`. + * + * If the parsing failed (no complete valid JSON data) `FIOBJ_INVALID` is + * returned. + */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed); + +/** Helper macro, calls `fiobj_json_parse` with string information */ +#define fiobj_json_parse2(data_, len_, consumed) \ + fiobj_json_parse(FIO_STR_INFO2(data_, len_), consumed) + +/** + * Uses JavaScript style notation to find data in an object structure. + * + * For example, "[0].name" will return the "name" property of the first object + * in an array object. + * + * Returns a temporary reference to the object or FIOBJ_INVALID on an error. + * + * Use `fiobj_dup` to collect an actual reference to the returned object. + */ +SFUNC FIOBJ fiobj_json_find(FIOBJ object, fio_str_info_s notation); +/** + * Uses JavaScript style notation to find data in an object structure. + * + * For example, "[0].name" will return the "name" property of the first object + * in an array object. + * + * Returns a temporary reference to the object or FIOBJ_INVALID on an error. + * + * Use `fiobj_dup` to collect an actual reference to the returned object. + */ +#define fiobj_json_find2(object, str, length) \ + fiobj_json_find(object, FIO_STR_INFO2(str, length)) + +/* ***************************************************************************** +FIOBJ Mustache support +***************************************************************************** */ + +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` + * if nothing was written. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx); + +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). + * + * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was + * written and `dest` was empty. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, FIOBJ dest, FIOBJ ctx); + +/* ***************************************************************************** + + + + + + + +FIOBJ - Implementation - Inline / Macro like fucntions + + + + + + + +***************************************************************************** */ + +/* ***************************************************************************** +The FIOBJ Type +***************************************************************************** */ + +/** Returns an objects type. This isn't limited to known types. */ +FIO_IFUNC size_t fiobj_type(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return FIOBJ_T_NULL; + case FIOBJ_T_TRUE: return FIOBJ_T_TRUE; + case FIOBJ_T_FALSE: return FIOBJ_T_FALSE; + }; + return FIOBJ_T_INVALID; + case FIOBJ_T_NUMBER: return FIOBJ_T_NUMBER; + case FIOBJ_T_FLOAT: return FIOBJ_T_FLOAT; + case FIOBJ_T_STRING: return FIOBJ_T_STRING; + case FIOBJ_T_ARRAY: return FIOBJ_T_ARRAY; + case FIOBJ_T_HASH: return FIOBJ_T_HASH; + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->type_id; + } + if (!o) + return FIOBJ_T_NULL; + return FIOBJ_T_INVALID; +} + +/* ***************************************************************************** +FIOBJ Memory Management +***************************************************************************** */ + +/** Increases an object's reference count (or copies) and returns it. */ +FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ return o; + case FIOBJ_T_STRING: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), dup)(o); + break; + case FIOBJ_T_ARRAY: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), dup)(o); + break; + case FIOBJ_T_HASH: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), dup)(o); + break; + case FIOBJ_T_OTHER: /* fall through */ fiobj_object_dup(o); + } + return o; +} + +/** Decreases an object's reference count or frees it. */ +FIO_IFUNC void fiobj_free(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: return; + case FIOBJ_T_STRING: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), free)(o); + return; + case FIOBJ_T_ARRAY: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), free)(o); + return; + case FIOBJ_T_HASH: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), free)(o); + return; + case FIOBJ_T_OTHER: (*fiobj_object_metadata(o))->free2(o); return; + } +} + +/* ***************************************************************************** +FIOBJ Data / Info +***************************************************************************** */ + +/** Internal: compares two nestable objects. */ +SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, + FIOBJ restrict b, + size_t nesting); + +/** Compares two objects. */ +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b) { + if (a == b) + return 1; + if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) + return 0; + switch (FIOBJ_TYPE_CLASS(a)) { + case FIOBJ_T_PRIMITIVE: + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ return a == b; + case FIOBJ_T_STRING: + return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); + case FIOBJ_T_ARRAY: return fiobj___test_eq_nested(a, b, 0); + case FIOBJ_T_HASH: return fiobj___test_eq_nested(a, b, 0); + case FIOBJ_T_OTHER: + if ((*fiobj_object_metadata(a))->count(a) || + (*fiobj_object_metadata(b))->count(b)) { + if ((*fiobj_object_metadata(a))->count(a) != + (*fiobj_object_metadata(b))->count(b)) + return 0; + return fiobj___test_eq_nested(a, b, 0); + } + return (*fiobj_object_metadata(a))->type_id == + (*fiobj_object_metadata(b))->type_id && + (*fiobj_object_metadata(a))->is_eq(a, b); + } + return 0; +} + +/** Returns a temporary String representation for any FIOBJ object. */ +FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return FIO_STR_INFO2((char *)"null", 4); + case FIOBJ_T_TRUE: return FIO_STR_INFO2((char *)"true", 4); + case FIOBJ_T_FALSE: return FIO_STR_INFO2((char *)"false", 5); + }; + return (fio_str_info_s){.buf = (char *)""}; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr)(o); + case FIOBJ_T_STRING: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + case FIOBJ_T_ARRAY: /* fall through */ + return FIO_STR_INFO2((char *)"[...]", 5); + case FIOBJ_T_HASH: { + return FIO_STR_INFO2((char *)"{...}", 5); + } + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_s(o); + } + /* a non-explicit NULL is an empty string. */ + return (fio_str_info_s){.buf = (char *)""}; +} + +/** Returns an integer representation for any FIOBJ object. */ +FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o) { + fio_str_info_s tmp; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return 0; + case FIOBJ_T_TRUE: return 1; + case FIOBJ_T_FALSE: return 0; + }; + return -1; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(o); + case FIOBJ_T_STRING: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + if (!tmp.len) + return 0; + return fio_atol(&tmp.buf); + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_i(o); + } + if (!o) + return 0; + return -1; +} + +/** Returns a float (double) representation for any FIOBJ object. */ +FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o) { + fio_str_info_s tmp; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_FALSE: /* fall through */ + case FIOBJ_T_NULL: return 0.0; + case FIOBJ_T_TRUE: return 1.0; + }; + return -1.0; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o); + case FIOBJ_T_STRING: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + if (!tmp.len) + return 0; + return (double)fio_atof(&tmp.buf); + case FIOBJ_T_ARRAY: + return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_f(o); + } + if (!o) + return 0.0; + return -1.0; +} + +/* ***************************************************************************** +FIOBJ Integers +***************************************************************************** */ + +#define FIO_REF_NAME fiobj___bignum +#define FIO_REF_TYPE intptr_t +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___NUMBER_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/* Places a 61 or 29 bit signed integer in the leftmost bits of a word. */ +#define FIO_NUMBER_ENCODE(i) (((uintptr_t)(i) << 3) | FIOBJ_T_NUMBER) +/* Reads a 61 or 29 bit signed integer from the leftmost bits of a word. */ +#define FIO_NUMBER_DECODE(i) \ + ((intptr_t)(((uintptr_t)(i) >> 3) | \ + ((uintptr_t)0 - \ + (((uintptr_t)(i) >> 3) & \ + ((uintptr_t)1 << ((sizeof(uintptr_t) * 8) - 4)))))) + +/** Creates a new Number object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + new)(intptr_t i) { + FIOBJ o = (FIOBJ)FIO_NUMBER_ENCODE(i); + if (FIO_NUMBER_DECODE(o) == i) + return o; + o = fiobj___bignum_new2(); + + FIO_PTR_MATH_RMASK(intptr_t, o, 3)[0] = i; + return o; +} + +/** Reads the number from a FIOBJ number. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_NUMBER) + return FIO_NUMBER_DECODE(i); + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + return FIO_PTR_MATH_RMASK(intptr_t, i, 3)[0]; + return 0; +} + +/** Reads the number from a FIOBJ number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i) { + return (double)FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i); +} + +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + fiobj___bignum_free2(i); +} + +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj___num, eq)(FIOBJ restrict a, + FIOBJ restrict b) { + /* it should be safe to assume that FIOBJ_TYPE_CLASS(i) != FIOBJ_T_NUMBER */ + return FIO_PTR_MATH_RMASK(intptr_t, a, 3)[0] == + FIO_PTR_MATH_RMASK(intptr_t, b, 3)[0]; + // return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(a) == + // FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(b); +} + +#undef FIO_NUMBER_ENCODE +#undef FIO_NUMBER_DECODE + +/* ***************************************************************************** +FIOBJ Floats +***************************************************************************** */ + +#define FIO_REF_NAME fiobj___bigfloat +#define FIO_REF_TYPE double +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___FLOAT_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/** Creates a new Float object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i) { + FIOBJ ui; + if (sizeof(double) <= sizeof(FIOBJ)) { + union { + double d; + uintptr_t i; + } punned; + punned.i = 0; /* dead code, but leave it, just in case */ + punned.d = i; + if ((punned.i & 7) == 0) { + return (FIOBJ)(punned.i | FIOBJ_T_FLOAT); + } + } + ui = fiobj___bigfloat_new2(); + + FIO_PTR_MATH_RMASK(double, ui, 3)[0] = i; + return ui; +} + +/** Reads the integer part from a FIOBJ Float. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i) { + return (intptr_t)floor(FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i)); +} + +/** Reads the number from a FIOBJ number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i) { + if (sizeof(double) <= sizeof(FIOBJ) && FIOBJ_TYPE_CLASS(i) == FIOBJ_T_FLOAT) { + union { + double d; + uint64_t i; + } punned; + punned.d = 0; /* dead code, but leave it, just in case */ + punned.i = (uint64_t)(uintptr_t)i; + punned.i = ((uint64_t)(uintptr_t)i & (~(uintptr_t)7ULL)); + return punned.d; + } + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + return FIO_PTR_MATH_RMASK(double, i, 3)[0]; + return 0.0; +} + +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + fiobj___bigfloat_free2(i); + return; +} + +/* ***************************************************************************** +FIOBJ Basic Iteration +***************************************************************************** */ + +/** + * Performs a task for each element held by the FIOBJ object. + * + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the "stop" position - the number of elements processed + `start_at`. + */ +FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, + int (*task)(fiobj_each_s *e), + void *udata, + int32_t start_at) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: return 0; + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each)( + o, + (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each_s *)))task, + udata, + start_at); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each)( + o, + (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s *)))task, + udata, + start_at); + case FIOBJ_T_OTHER: + return (*fiobj_object_metadata(o))->each1(o, task, udata, start_at); + } + return 0; +} + +/* ***************************************************************************** +FIOBJ Hash Maps +***************************************************************************** */ + +/** Calculates an object's hash value for a specific hash map object. */ +FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ o) { + uint64_t seed = (uint64_t)(uintptr_t)&FIO_NAME2(fiobj, hash); + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + return fio_risky_hash(&o, sizeof(o), seed + (uintptr_t)o); + case FIOBJ_T_NUMBER: { + uintptr_t tmp = FIO_NAME2(fiobj, i)(o); + return fio_risky_hash(&tmp, sizeof(tmp), seed); + } + case FIOBJ_T_FLOAT: { + double tmp = FIO_NAME2(fiobj, f)(o); + return fio_risky_hash(&tmp, sizeof(tmp), seed); + } + case FIOBJ_T_STRING: /* fall through */ + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), hash)(o, seed); + case FIOBJ_T_ARRAY: { + uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_ARRAY); + { + FIOBJ *a = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), ptr)(o); + const size_t count = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + if (a) { + for (size_t i = 0; i < count; ++i) { + h += FIO_NAME2(fiobj, hash)(a[i]); + } + } + } + return h; + } + case FIOBJ_T_HASH: { + uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_HASH); + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, i) { + h += i.hash; + h += FIO_NAME2(fiobj, hash)(i.value); + } + return h; + } + case FIOBJ_T_OTHER: { + /* TODO: can we avoid "stringifying" the object? */ + fio_str_info_s tmp = (*fiobj_object_metadata(o))->to_s(o); + return fio_risky_hash(tmp.buf, tmp.len, seed); + } + } + return 0; +} + +/** + * Sets a String value in a hash map, allocating the String and automatically + * calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + set2)(FIOBJ hash, + const char *key, + size_t len, + FIOBJ value) { + FIOBJ tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(tmp, (char *)key, len); + FIOBJ v = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set)(hash, tmp, value, NULL); + fiobj_free(tmp); + return v; +} + +/** + * Finds a String value in a hash map, using a temporary String and + * automatically calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + get2)(FIOBJ hash, const char *buf, size_t len) { + if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) + return FIOBJ_INVALID; + FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); + FIOBJ v = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(hash, tmp); + return v; +} + +/** + * Removes a String value in a hash map, using a temporary String and + * automatically calculating the hash value. + */ +FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + remove2)(FIOBJ hash, + const char *buf, + size_t len, + FIOBJ *old) { + FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); + int r = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(hash, tmp, old); + FIOBJ_STR_TEMP_DESTROY(tmp); + return r; +} + +/** Updates a hash using information from another Hash. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update)(FIOBJ dest, + FIOBJ src) { + if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_HASH || + FIOBJ_TYPE_CLASS(src) != FIOBJ_T_HASH) + return; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), src, i) { + if (i.key == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(i.key) == FIOBJ_T_NULL) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(dest, i.key, NULL); + continue; + } + register FIOBJ tmp; + switch (FIOBJ_TYPE_CLASS(i.value)) { + case FIOBJ_T_ARRAY: + /* TODO? decide if we should merge elements or overwrite...? */ + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); + if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_ARRAY) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), concat) + (tmp, i.value); + continue; + } + break; + case FIOBJ_T_HASH: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); + if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_HASH) + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update) + (dest, i.value); + else break; + continue; + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ + case FIOBJ_T_OTHER: break; + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) + (dest, i.key, fiobj_dup(i.value), NULL); + } +} + +/* ***************************************************************************** +FIOBJ JSON support (inline functions) +***************************************************************************** */ + +typedef struct { + FIOBJ json; + size_t level; + uint8_t beautify; +} fiobj___json_format_internal__s; + +/* internal helper function for recursive JSON formatting. */ +SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *, + FIOBJ); + +/** Helper function, calls `fiobj_hash_update_json` with string information */ +FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json2)(FIOBJ hash, char *ptr, size_t len) { + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(hash, FIO_STR_INFO2(ptr, len)); +} + +/** + * Returns a JSON valid FIOBJ String, representing the object. + * + * If `dest` is an existing String, the formatted JSON data will be appended to + * the existing string. + */ +FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify) { + fiobj___json_format_internal__s args = + (fiobj___json_format_internal__s){.json = dest, .beautify = beautify}; + if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_STRING) + args.json = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + fiobj___json_format_internal__(&args, o); + return args.json; +} + +#undef FIO___RECURSIVE_INCLUDE /* from now on, type helpers are internal */ + +/* ***************************************************************************** +FIOBJ Mustache support - inline implementation +***************************************************************************** */ + +/* callback should write `txt` to output and return updated `udata.` */ +FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt); +/* same as `write_text`, but should also HTML escape (sanitize) data. */ +FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *udata, + fio_buf_info_s raw); +/* callback should return a new context pointer with the value of `name`. */ +FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name); +/* if context is an Array, should return its length. */ +FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx); +/* if context is an Array, should return a context pointer @ index. */ +FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index); +/* should return the String value of context `var` as a `fio_buf_info_s`. */ +FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var); +/* should return non-zero if the context pointer refers to a valid value. */ +FIO_SFUNC int fiobj___mustache_var_is_truthful(void *ctx); + +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` + * if nothing was written. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx) { + return (FIOBJ)fio_mustache_build( + m, + .write_text = fiobj___mustache_write_text, + .write_text_escaped = fiobj___mustache_write_text_escaped, + .get_var = fiobj___mustache_get_var, + .array_length = fiobj___mustache_array_length, + .get_var_index = fiobj___mustache_get_var_index, + .var2str = fiobj___mustache_var2str, + .var_is_truthful = fiobj___mustache_var_is_truthful, + .ctx = ctx, + .udata = NULL); +} + +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). + * + * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was + * written and `dest` was empty. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, + FIOBJ dest, + FIOBJ ctx) { + dest = (FIOBJ)fio_mustache_build( + m, + .write_text = fiobj___mustache_write_text, + .write_text_escaped = fiobj___mustache_write_text_escaped, + .get_var = fiobj___mustache_get_var, + .array_length = fiobj___mustache_array_length, + .get_var_index = fiobj___mustache_get_var_index, + .var2str = fiobj___mustache_var2str, + .var_is_truthful = fiobj___mustache_var_is_truthful, + .ctx = ctx, + .udata = dest); + return dest; +} + +/* callback should write `txt` to output and return updated `udata.` */ +FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt) { + FIOBJ d = (FIOBJ)udata; + if (!d) + d = fiobj_str_new_buf(txt.len + 32); + fiobj_str_write(d, txt.buf, txt.len); + return (void *)d; +} +/* same as `write_text`, but should also HTML escape (sanitize) data. */ +FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *ud, + fio_buf_info_s raw) { + FIOBJ d = (FIOBJ)ud; + if (!d) + d = fiobj_str_new_buf(raw.len + 32); + fiobj_str_write_html_escape(d, raw.buf, raw.len); + return (void *)d; +} +/* callback should return a new context pointer with the value of `name`. */ +FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name) { + if (!ctx) + return NULL; + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_HASH)) + return NULL; + return fiobj_hash_get2((FIOBJ)ctx, name.buf, name.len); +} +/* if context is an Array, should return its length. */ +FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx) { + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY)) + return 0; + return fiobj_array_count((FIOBJ)ctx); +} +/* if context is an Array, should return a context pointer @ index. */ +FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index) { + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY) || index > 0xFFFFFFFFUL) + return NULL; + return fiobj_array_get((FIOBJ)ctx, (uint32_t)index); +} +/* should return the String value of context `var` as a `fio_buf_info_s`. */ +FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var) { + fio_buf_info_s r = {0}; + if (!var || var == fiobj_null()) + return r; + fio_str_info_s tmp = fiobj2cstr((FIOBJ)var); + r = FIO_STR2BUF_INFO(tmp); + return r; +} +/* should return non-zero if the context pointer refers to a valid value. */ +FIO_SFUNC int fiobj___mustache_var_is_truthful(void *v) { + return v && (FIOBJ)v != fiobj_null() && (FIOBJ)v != fiobj_false() && + (!FIOBJ_TYPE_IS((FIOBJ)v, FIOBJ_T_ARRAY) || + fiobj_array_count((FIOBJ)v)); +} + +/* ***************************************************************************** + + +FIOBJ - Externed Implementation + + +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +FIOBJ Basic Object vtable +***************************************************************************** */ + +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL = { + .type_id = 99, /* type IDs below 100 are reserved. */ +}; + +/* ***************************************************************************** +FIOBJ Complex Iteration +***************************************************************************** */ +typedef struct { + FIOBJ obj; + size_t pos; +} fiobj___stack_element_s; + +#define FIO_ARRAY_NAME fiobj___active_stack +#define FIO_ARRAY_TYPE fiobj___stack_element_s +#define FIO_ARRAY_COPY(dest, src) \ + do { \ + (dest).obj = fiobj_dup((src).obj); \ + (dest).pos = (src).pos; \ + } while (0) +#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj +#define FIO_ARRAY_DESTROY(o) fiobj_free(o) +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj +#define FIO_ARRAY_NAME fiobj___stack +#define FIO_ARRAY_TYPE fiobj___stack_element_s +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +typedef struct { + int (*task)(fiobj_each_s *info); + void *arg; + FIOBJ next; + size_t count; + fiobj___stack_s stack; + uint32_t end; + uint8_t stop; +} fiobj_____each2_data_s; + +FIO_SFUNC uint32_t fiobj____each2_element_count(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: return 0; + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: /* fall through */ + return (*fiobj_object_metadata(o))->count(o); + } + return 0; +} +FIO_SFUNC int fiobj____each2_wrapper_task(fiobj_each_s *e) { + fiobj_____each2_data_s *d = (fiobj_____each2_data_s *)e->udata; + e->task = d->task; + e->udata = d->arg; + d->stop = (d->task(e) == -1); + d->task = e->task; + d->arg = e->udata; + e->task = fiobj____each2_wrapper_task; + e->udata = d; + ++d->count; + if (d->stop) + return -1; + uint32_t c = fiobj____each2_element_count(e->value); + if (c) { + d->next = e->value; + d->end = c; + return -1; + } + return 0; +} + +/** + * Performs a task for the object itself and each element held by the FIOBJ + * object or any of it's elements (a deep task). + * + * The order of performance is by order of appearance, as if all nesting levels + * were flattened. + * + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the number of elements processed. + */ +SFUNC uint32_t fiobj_each2(FIOBJ o, int (*task)(fiobj_each_s *), void *udata) { + /* TODO - move to recursion with nesting limiter? */ + fiobj_____each2_data_s d = { + .task = task, + .arg = udata, + .next = FIOBJ_INVALID, + .stack = FIO_ARRAY_INIT, + }; + struct FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s) e_tmp = { + + .parent = FIOBJ_INVALID, + .task = (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + each_s) *))fiobj____each2_wrapper_task, + .udata = &d, + .value = o, + }; + fiobj___stack_element_s i = {.obj = o, .pos = 0}; + uint32_t end = fiobj____each2_element_count(o); + fiobj____each2_wrapper_task((fiobj_each_s *)&e_tmp); + while (!d.stop && i.obj && i.pos < end) { + i.pos = + fiobj_each1(i.obj, fiobj____each2_wrapper_task, &d, (uint32_t)i.pos); + if (d.next != FIOBJ_INVALID) { + if (fiobj___stack_count(&d.stack) + 1 > FIOBJ_MAX_NESTING) { + FIO_LOG_ERROR("FIOBJ nesting level too deep (%u)." + "`fiobj_each2` stopping loop early.", + (unsigned int)fiobj___stack_count(&d.stack)); + d.stop = 1; + continue; + } + fiobj___stack_push(&d.stack, i); + i.pos = 0; + i.obj = d.next; + d.next = FIOBJ_INVALID; + end = d.end; + } else { + /* re-collect end position to accommodate for changes */ + end = fiobj____each2_element_count(i.obj); + } + while (i.pos >= end && fiobj___stack_count(&d.stack)) { + fiobj___stack_pop(&d.stack, &i); + end = fiobj____each2_element_count(i.obj); + } + }; + fiobj___stack_destroy(&d.stack); + return (uint32_t)d.count; +} + +/* ***************************************************************************** +FIOBJ Hash / Array / Other (enumerable) Equality test. +***************************************************************************** */ + +/** Internal: compares two nestable objects. */ +SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, + FIOBJ restrict b, + size_t nesting) { + if (a == b) + return 1; + if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) + return 0; + if (fiobj____each2_element_count(a) != fiobj____each2_element_count(b)) + return 0; + if (nesting >= FIOBJ_MAX_NESTING) + return 0; + + ++nesting; + + switch (FIOBJ_TYPE_CLASS(a)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: return a == b; + case FIOBJ_T_STRING: + return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); + + case FIOBJ_T_ARRAY: + if (!fiobj____each2_element_count(a)) + return 1; + /* test each array member with matching index */ + { + const size_t count = fiobj____each2_element_count(a); + for (size_t i = 0; i < count; ++i) { + if (!fiobj___test_eq_nested( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(a, + (int32_t)i), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(b, + (int32_t)i), + nesting)) + return 0; + } + } + return 1; + + case FIOBJ_T_HASH: + if (!fiobj____each2_element_count(a)) + return 1; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), a, pos) { + FIOBJ val = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(b, pos.key); + if (!fiobj___test_eq_nested(val, pos.value, nesting)) + return 0; + } + return 1; + case FIOBJ_T_OTHER: + if (!fiobj____each2_element_count(a) && + (*fiobj_object_metadata(a))->is_eq(a, b)) + return 1; + /* TODO: iterate through objects and test equality within nesting */ + return (*fiobj_object_metadata(a))->is_eq(a, b); + return 1; + } + return 0; +} + +/* ***************************************************************************** +FIOBJ general helpers +***************************************************************************** */ + +FIO_SFUNC uint32_t fiobj___count_noop(FIOBJ o) { + return 0; + (void)o; +} + +/* ***************************************************************************** +FIOBJ Integers (bigger numbers) +***************************************************************************** */ + +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + cstr)(FIOBJ i) { + static char buf[32 * 128]; + static uint8_t pos = 0; + size_t at = fio_atomic_add(&pos, 1); + fio_str_info_s s = {.buf = buf + ((at & 127) << 5), .capa = 31}; + fio_string_write_i(&s, + NULL, + FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i)); + return s; +} + +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL = { + /** + * MUST return a unique number to identify object type. + * + * Numbers (IDs) under 100 are reserved. + */ + .type_id = FIOBJ_T_NUMBER, + /** Test for equality between two objects with the same `type_id` */ + .is_eq = FIO_NAME_BL(fiobj___num, eq), + /** Converts an object to a String */ + .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr), + /** Converts and object to an integer */ + .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i), + /** Converts and object to a float */ + .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f), + /** Returns the number of exposed elements held by the object, if any. */ + .count = fiobj___count_noop, + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + .each1 = NULL, + /** Deallocates the element (but NOT any of it's exposed elements). */ + .free2 = fiobj___bignum_free2, +}; + +/* ***************************************************************************** +FIOBJ Floats (bigger / smaller doubles) +***************************************************************************** */ + +FIO_SFUNC unsigned char FIO_NAME_BL(fiobj___float, eq)(FIOBJ restrict a, + FIOBJ restrict b) { + unsigned char r = 0; + union { + uint64_t u; + double f; + } da, db; + da.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(a); + db.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(b); + /* regular equality? */ + r |= da.f == db.f; + /* test for small rounding errors (4 bit difference) on normalize floats */ + r |= !((da.u ^ db.u) & UINT64_C(0xFFFFFFFFFFFFFFF0)) && + (da.u & UINT64_C(0x7FF0000000000000)); + /* test for small ULP: */ + r |= (((da.u > db.u) ? da.u - db.u : db.u - da.u) < 2); + /* test for +-0 */ + r |= !((da.u | db.u) & UINT64_C(0x7FFFFFFFFFFFFFFF)); + return r; +} + +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), + cstr)(FIOBJ i) { + static char buf[32 * 128]; + static uint8_t pos = 0; + size_t at = fio_atomic_add(&pos, 1); + char *tmp = buf + ((at & 127) << 5); + size_t len = + fio_ftoa(tmp, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i), 10); + tmp[len] = 0; + return FIO_STR_INFO2(tmp, len); +} + +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL = { + /** + * MUST return a unique number to identify object type. + * + * Numbers (IDs) under 100 are reserved. + */ + .type_id = FIOBJ_T_FLOAT, + /** Test for equality between two objects with the same `type_id` */ + .is_eq = FIO_NAME_BL(fiobj___float, eq), + /** Converts an object to a String */ + .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr), + /** Converts and object to an integer */ + .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i), + /** Converts and object to a float */ + .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f), + /** Returns the number of exposed elements held by the object, if any. */ + .count = fiobj___count_noop, + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + .each1 = NULL, + /** Deallocates the element (but NOT any of it's exposed elements). */ + .free2 = fiobj___bigfloat_free2, +}; + +/* ***************************************************************************** +FIOBJ JSON support - output +***************************************************************************** */ + +FIO_IFUNC void fiobj___json_format_internal_beauty_pad(FIOBJ json, + size_t level) { + size_t pos = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(json); + fio_str_info_s tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + resize)(json, (level << 1) + pos + 2); + tmp.buf[pos++] = '\r'; + tmp.buf[pos++] = '\n'; + for (size_t i = 0; i < level; ++i) { + tmp.buf[pos++] = ' '; + tmp.buf[pos++] = ' '; + } +} + +SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *args, + FIOBJ o) { + switch (FIOBJ_TYPE(o)) { + case FIOBJ_T_TRUE: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "true", 4); + return; + case FIOBJ_T_FALSE: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "false", 5); + return; + case FIOBJ_T_NULL: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "null", 4); + return; + case FIOBJ_T_NUMBER: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i) + (args->json, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o)); + return; + case FIOBJ_T_FLOAT: { + char tmp_buf[256]; + size_t len = fio_ftoa(tmp_buf, + FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o), + 10); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, tmp_buf, len); + return; + } + case FIOBJ_T_STRING: /* fall through */ + default: { + fio_str_info_s info = FIO_NAME2(fiobj, cstr)(o); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) + (args->json, info.buf, info.len); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); + return; + } + case FIOBJ_T_ARRAY: + if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o)) + goto empty_array; + if (args->level == FIOBJ_MAX_NESTING) + goto err_array_nesting; + { + ++args->level; + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "[", 1); + const uint32_t len = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + fiobj___json_format_internal__( + args, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, 0)); + if (args->beautify) { + for (size_t i = 1; i < len; ++i) { + FIOBJ child = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); + fiobj___json_format_internal_beauty_pad(args->json, args->level); + fiobj___json_format_internal__(args, child); + } + } else { + for (size_t i = 1; i < len; ++i) { + FIOBJ child = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); + fiobj___json_format_internal__(args, child); + } + } + --args->level; + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "]", 1); + } + return; + case FIOBJ_T_HASH: + if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o)) + goto empty_hash; + if (args->level == FIOBJ_MAX_NESTING) + goto err_hash_nesting; + { + size_t i = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{", 1); + ++args->level; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, couplet) { + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + fio_str_info_s info = FIO_NAME2(fiobj, cstr)(couplet.key); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "\"", 1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) + (args->json, info.buf, info.len); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "\":", 2); + fiobj___json_format_internal__(args, couplet.value); + if (--i) + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); + } + --args->level; + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "}", 1); + } + return; + } +empty_hash: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{}", 2); + return; +empty_array: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "[]", 2); + return; +err_array_nesting: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "[ ]", 3); + goto log_nesting_error; +err_hash_nesting: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{ }", 3); +log_nesting_error: + FIO_LOG_ERROR("JSON formatting truncated - nesting level too deep."); +} + +/* ***************************************************************************** +FIOBJ JSON parsing +***************************************************************************** */ +#if 1 + +FIO_SFUNC void *fiobj___json_on_null(void) { + return FIO_NAME(fiobj, FIOBJ___NAME_NULL)(); +} +FIO_SFUNC void *fiobj___json_on_true(void) { return fiobj_true(); } +FIO_SFUNC void *fiobj___json_on_false(void) { return fiobj_false(); } +FIO_SFUNC void *fiobj___json_on_number(int64_t i) { + return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_NUMBER, new))(i); +} +FIO_SFUNC void *fiobj___json_on_float(double f) { + return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_FLOAT, new))(f); +} +FIO_SFUNC void *fiobj___json_on_string(const void *start, size_t len) { + FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write_unescape)) + (str, (const char *)start, len); + return str; +} +FIO_SFUNC void *fiobj___json_on_string_simple(const void *start, size_t len) { + FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write)) + (str, (const char *)start, len); + return str; +} +FIO_SFUNC void *fiobj___json_on_map(void *ctx, void *at) { + FIOBJ m = FIOBJ_INVALID; + if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, + (FIOBJ)at); + if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, new))(); + return m; +} +FIO_SFUNC void *fiobj___json_on_array(void *ctx, void *at) { + FIOBJ m = FIOBJ_INVALID; + if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, + (FIOBJ)at); + if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, new))(); + return m; +} +FIO_SFUNC int fiobj___json_map_push(void *ctx, void *key, void *value) { + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, set)) + ((FIOBJ)ctx, (FIOBJ)key, (FIOBJ)value, NULL); + fiobj_free((FIOBJ)key); + return 0; +} +FIO_SFUNC int fiobj___json_array_push(void *ctx, void *value) { + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, push))((FIOBJ)ctx, (FIOBJ)value); + return 0; +} +FIO_SFUNC void fiobj___json_free_unused_object(void *ctx) { + fiobj_free((FIOBJ)ctx); +} +FIO_SFUNC void *fiobj___json_on_error(void *ctx) { + fiobj_free((FIOBJ)ctx); + return FIOBJ_INVALID; +} +static fio_json_parser_callbacks_s FIOBJ_JSON_PARSER_CALLBACKS = { + .on_null = fiobj___json_on_null, + .on_true = fiobj___json_on_true, + .on_false = fiobj___json_on_false, + .on_number = fiobj___json_on_number, + .on_float = fiobj___json_on_float, + .on_string = fiobj___json_on_string, + .on_string_simple = fiobj___json_on_string_simple, + .on_map = fiobj___json_on_map, + .on_array = fiobj___json_on_array, + .map_push = fiobj___json_map_push, + .array_push = fiobj___json_array_push, + .free_unused_object = fiobj___json_free_unused_object, + .on_error = fiobj___json_on_error, +}; + +/** Returns a JSON valid FIOBJ String, representing the object. */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { + fio_json_result_s result = + fio_json_parse(&FIOBJ_JSON_PARSER_CALLBACKS, str.buf, str.len); + if (consumed_p) + *consumed_p = result.stop_pos; + if (result.err) { +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free((FIOBJ)result.ctx); + result.ctx = FIOBJ_INVALID; + } + return (FIOBJ)result.ctx; +} + +/** + * Updates a Hash using JSON data. + * + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. + * + * Conflicting Hash data is overwritten (preferring the new over the old). + * + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. + */ +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str) { + /* TODO! FIXME! this will leak memory on NULL hash and break on Arrays */ + fio_json_result_s result = fio_json_parse_update(&FIOBJ_JSON_PARSER_CALLBACKS, + hash, + str.buf, + str.len); + // if (consumed_p) + // *consumed_p = result.stop_pos; + if (result.err) { +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free((FIOBJ)result.ctx); + result.ctx = FIOBJ_INVALID; + } + return result.stop_pos; + FIO_LOG_ERROR("fiobj_hash_update_json note yet implemented"); + return 0; + (void)str; +} + +#else +#define FIO_JSON +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* FIOBJ JSON parser */ +typedef struct { + fio_json_parser_s p; + size_t so; /* stack offset */ + FIOBJ key; + FIOBJ top; + FIOBJ target; + FIOBJ stack[JSON_MAX_DEPTH + 1]; +} fiobj_json_parser_s; + +static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) { + if (p->top) { + if (FIOBJ_TYPE_CLASS(p->top) == FIOBJ_T_HASH) { + if (p->key) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) + (p->top, p->key, o, NULL); + fiobj_free(p->key); + p->key = FIOBJ_INVALID; + } else { + p->key = o; + } + } else { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(p->top, o); + } + } else { + p->top = o; + } +} + +/** a NULL object was detected */ +static inline void fio_json_on_null(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(fiobj, FIOBJ___NAME_NULL)()); +} +/** a TRUE object was detected */ +static inline void fio_json_on_true(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true()); +} +/** a FALSE object was detected */ +static inline void fio_json_on_false(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false()); +} +/** a Numeral was detected (long long). */ +static inline void fio_json_on_number(fio_json_parser_s *p, long long i) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(i)); +} +/** a Float was detected (double). */ +static inline void fio_json_on_float(fio_json_parser_s *p, double f) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(f)); +} +/** a String was detected (int / float). update `pos` to point at ending */ +static inline void fio_json_on_string(fio_json_parser_s *p, + const void *start, + size_t len) { + FIOBJ str = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_unescape) + (str, start, len); + fiobj_json_add2parser((fiobj_json_parser_s *)p, str); +} +/** a dictionary object was detected */ +static inline int fio_json_on_start_object(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + if (pr->target) { + /* push NULL, don't free the objects */ + pr->stack[pr->so++] = FIOBJ_INVALID; + pr->top = pr->target; + pr->target = FIOBJ_INVALID; + } else { + FIOBJ hash; +#if FIOBJ_JSON_APPEND + hash = FIOBJ_INVALID; + if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { + hash = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); + } + if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) { + hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + fiobj_json_add2parser(pr, hash); + } else { + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } +#else + hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + fiobj_json_add2parser(pr, hash); +#endif + pr->stack[pr->so++] = pr->top; + pr->top = hash; + } + return 0; +} +/** a dictionary object closure detected */ +static inline void fio_json_on_end_object(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + if (pr->key) { + FIO_LOG_WARNING("(JSON parsing) malformed JSON, " + "ignoring dangling Hash key."); + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } + pr->top = FIOBJ_INVALID; + if (pr->so) + pr->top = pr->stack[--pr->so]; +} +/** an array object was detected */ +static int fio_json_on_start_array(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + FIOBJ ary = FIOBJ_INVALID; + if (pr->target != FIOBJ_INVALID) { + if (FIOBJ_TYPE_CLASS(pr->target) != FIOBJ_T_ARRAY) + return -1; + ary = pr->target; + pr->target = FIOBJ_INVALID; + } +#if FIOBJ_JSON_APPEND + if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { + ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); + } + if (FIOBJ_TYPE_CLASS(ary) != FIOBJ_T_ARRAY) { + ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + fiobj_json_add2parser(pr, ary); + } else { + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } +#else + FIOBJ ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + fiobj_json_add2parser(pr, ary); +#endif + + pr->stack[pr->so++] = pr->top; + pr->top = ary; + return 0; +} +/** an array closure was detected */ +static inline void fio_json_on_end_array(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + pr->top = FIOBJ_INVALID; + if (pr->so) + pr->top = pr->stack[--pr->so]; +} +/** the JSON parsing is complete */ +static void fio_json_on_json(fio_json_parser_s *p) { + (void)p; /* nothing special... right? */ +} +/** the JSON parsing is complete */ +static inline void fio_json_on_error(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + fiobj_free(pr->stack[0]); + fiobj_free(pr->key); + *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID}; + FIO_LOG_DEBUG("JSON on_error callback called."); +} + +/** + * Updates a Hash using JSON data. + * + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. + * + * Conflicting Hash data is overwritten (preferring the new over the old). + * + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. + */ +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str) { + if (hash == FIOBJ_INVALID) + return 0; + fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash}; + size_t consumed = fio_json_parse(&p.p, str.buf, str.len); + fiobj_free(p.key); + if (p.top != hash) + fiobj_free(p.top); + return consumed; +} + +/** Returns a JSON valid FIOBJ String, representing the object. */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { + fiobj_json_parser_s p = {.top = FIOBJ_INVALID}; + register const size_t consumed = fio_json_parse(&p.p, str.buf, str.len); + if (consumed_p) { + *consumed_p = consumed; + } + if (!consumed || p.p.depth) { + if (p.top) { + FIO_LOG_DEBUG("WARNING - JSON failed secondary validation, no on_error"); + } +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, p.top, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free(p.stack[0]); + p.top = FIOBJ_INVALID; + } + fiobj_free(p.key); + return p.top; +} +#endif + +/** Uses JSON (JavaScript) notation to find data in an object structure. Returns + * a temporary object. */ +SFUNC FIOBJ fiobj_json_find(FIOBJ o, fio_str_info_s n) { + for (;;) { + top: + if (!n.len || (n.len == 1 && n.buf[0] == '.')) + return o; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_ARRAY: { + if (n.len <= 2 || n.buf[0] != '[' || n.buf[1] < '0' || n.buf[1] > '9') + return FIOBJ_INVALID; + size_t i = 0; + ++n.buf; + --n.len; + while (n.len && fio_c2i(n.buf[0]) < 10) { + i = (i * 10) + fio_c2i(n.buf[0]); + ++n.buf; + --n.len; + } + if (!n.len || n.buf[0] != ']' || i > 0xFFFFFFFFU) + return FIOBJ_INVALID; + o = fiobj_array_get(o, (uint32_t)i); + ++n.buf; + --n.len; + if (n.len) { + if (n.buf[0] == '.') { + ++n.buf; + --n.len; + } else if (n.buf[0] != '[') { + return FIOBJ_INVALID; + } + continue; + } + return o; + } + case FIOBJ_T_HASH: { + FIOBJ tmp = fiobj_hash_get2(o, n.buf, n.len); + if (tmp != FIOBJ_INVALID) + return tmp; + char *end = n.buf + n.len - 1; + while (end > n.buf) { + while (end > n.buf && end[0] != '.' && end[0] != '[') + --end; + if (end == n.buf) + return FIOBJ_INVALID; + const size_t t_len = end - n.buf; + tmp = fiobj_hash_get2(o, n.buf, t_len); + if (tmp != FIOBJ_INVALID) { + o = tmp; + n.len -= t_len + (end[0] == '.'); + n.buf = end + (end[0] == '.'); + goto top; + } + --end; + } + } /* fall through */ + default: return FIOBJ_INVALID; + } + } +} +/* ***************************************************************************** +FIOBJ cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIOBJ_EXTERN_OBJ +#undef FIOBJ_EXTERN_OBJ_IMP +#undef FIO_FIOBJ +#endif /* FIO_FIOBJ */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + IO Reactor - an Evented IO Reactor, Single-Threaded + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO___H) +#define H___FIO_IO___H +/* ***************************************************************************** +IO Reactor Settings + +At this point, define any MACROs and customizable settings available to the +developer. +***************************************************************************** */ + +#ifndef FIO_IO_BUFFER_PER_WRITE +/** Control the size of the on-stack buffer used for `write` events. */ +#define FIO_IO_BUFFER_PER_WRITE 65536U +#endif + +#ifndef FIO_IO_THROTTLE_LIMIT +/** IO will be throttled (no `on_data` events) if outgoing buffer is large. */ +#define FIO_IO_THROTTLE_LIMIT 2097152U +#endif + +#ifndef FIO_IO_TIMEOUT_MAX +/** Controls the maximum and default timeout in milliseconds (5 minutes). */ +#define FIO_IO_TIMEOUT_MAX 300000 +#endif + +#ifndef FIO_IO_SHUTDOWN_TIMEOUT +/* Sets the hard timeout (in milliseconds) for the reactor's shutdown loop. */ +#define FIO_IO_SHUTDOWN_TIMEOUT 10000 +#endif + +#ifndef FIO_IO_COUNT_STORAGE +#ifdef DEBUG +#define FIO_IO_COUNT_STORAGE 1 +#else +#define FIO_IO_COUNT_STORAGE 0 +#endif +#endif +/* ***************************************************************************** +IO Types +***************************************************************************** */ + +/** The main protocol object type. See `struct fio_io_protocol_s`. */ +typedef struct fio_io_protocol_s fio_io_protocol_s; + +/** The IO functions used by the protocol object. */ +typedef struct fio_io_functions_s fio_io_functions_s; + +/** The main IO object type. Should be treated as an opaque pointer. */ +typedef struct fio_io_s fio_io_s; + +/** An opaque type used for the SSL/TLS helper functions. */ +typedef struct fio_io_tls_s fio_io_tls_s; + +/** Message structure, as received by the `on_message` subscription callback. */ +typedef struct fio_msg_s fio_msg_s; + +/** The IO Async Queue type. */ +typedef struct fio_io_async_s fio_io_async_s; + +/* ***************************************************************************** +Starting / Stopping the IO Reactor +***************************************************************************** */ + +/** Stopping the IO reactor. */ +SFUNC void fio_io_stop(void); + +/** Adds `workers` amount of workers to the root IO reactor process. */ +SFUNC void fio_io_add_workers(int workers); + +/** Starts the IO reactor, using optional `workers` processes. Will BLOCK! */ +SFUNC void fio_io_start(int workers); + +/* ***************************************************************************** +The IO Reactor's State +***************************************************************************** */ + +/** Returns true if IO reactor running and 0 if stopped or shutting down. */ +SFUNC int fio_io_is_running(void); + +/** Returns true if the current process is the IO reactor's master process. */ +SFUNC int fio_io_is_master(void); + +/** Returns true if the current process is an IO reactor's worker process. */ +SFUNC int fio_io_is_worker(void); + +/** Returns the number or workers the IO reactor will actually run. */ +SFUNC uint16_t fio_io_workers(int workers_requested); + +/** Returns current process id. */ +SFUNC int fio_io_pid(void); + +/** Returns the root / master process id. */ +SFUNC int fio_io_root_pid(void); + +/** Returns the last millisecond when the IO reactor polled for events. */ +SFUNC int64_t fio_io_last_tick(void); + +/* ***************************************************************************** +Listening to Incoming Connections +***************************************************************************** */ + +/** Arguments for the fio_io_listen function */ +typedef struct fio_io_listen_args { + /** + * The binding address in URL format. Defaults to: tcp://0.0.0.0:3000 + * + * Note: `.url` accept an optional query for building a TLS context. + * + * Possible query values include: + * + * - `tls` or `ssl` (no value): sets TLS as active, possibly self-signed. + * - `tls=` or `ssl=`: value is a prefix for "key.pem" and "cert.pem". + * - `key=` and `cert=`: file paths for ".pem" files. + * + * i.e.: + * + * fio_io_listen(.url = "0.0.0.0:3000/?tls", ...); + * fio_io_listen(.url = "0.0.0.0:3000/?tls=./", ...); + * // same as: + * fio_io_listen(.url = "0.0.0.0:3000/" + * "?key=./key.pem" + * "&cert=./cert.pem", ...); + */ + const char *url; + /** The `fio_io_protocol_s` that will be assigned to incoming + * connections. */ + fio_io_protocol_s *protocol; + /** The default `udata` set for (new) incoming connections. */ + void *udata; + /** TLS object used for incoming connections (ownership moved to listener). */ + fio_io_tls_s *tls; + /** + * Called when the a listening socket starts to listen. + * + * May be called multiple times (i.e., if the IO reactor stops and restarts). + */ + void (*on_start)(fio_io_protocol_s *protocol, void *udata); + /** + * Called during listener cleanup. + * + * This will be called separately for every process before exiting. + */ + void (*on_stop)(fio_io_protocol_s *protocol, void *udata); + /** + * Selects a queue that will be used to schedule a pre-accept task. + * May be used to test user thread stress levels before accepting connections. + */ + fio_io_async_s *queue_for_accept; + /** When forking the IO reactor - limits `listen` to the root process. */ + uint8_t on_root; + /** Hides "started/stopped listening" messages from log (if set). */ + uint8_t hide_from_log; +} fio_io_listen_args; + +/** + * Sets up a network service on a listening socket. + * + * Returns a self-destructible listener handle on success or NULL on error. + */ +SFUNC void *fio_io_listen(fio_io_listen_args args); +#define fio_io_listen(...) fio_io_listen((fio_io_listen_args){__VA_ARGS__}) + +/** Notifies a listener to stop listening. */ +SFUNC void fio_io_listen_stop(void *listener); + +/** Returns the URL on which the listener is listening. */ +SFUNC fio_buf_info_s fio_io_listener_url(void *listener); + +/** Returns true if the listener protocol has an attached TLS context. */ +SFUNC int fio_io_listener_is_tls(void *listener); + +/* ***************************************************************************** +Connecting as a Client +***************************************************************************** */ + +/** Named arguments for fio_io_connect */ +typedef struct { + /** The URL to connect to (may contain TLS hints in query / `tls` scheme). */ + const char *url; + /** Connection protocol (once connection established). */ + fio_io_protocol_s *protocol; + /** Called in case of a failed connection, use for cleanup. */ + void (*on_failed)(fio_io_protocol_s *protocol, void *udata); + /** Opaque user data (set only once connection was established). */ + void *udata; + /** TLS builder object for TLS connections. */ + fio_io_tls_s *tls; + /** Connection timeout in milliseconds (defaults to 30 seconds). */ + uint32_t timeout; +} fio_io_connect_args_s; + +/** Connects to a specific URL, returning the `fio_io_s` IO object or `NULL`. */ +SFUNC fio_io_s *fio_io_connect(fio_io_connect_args_s args); + +#define fio_io_connect(url_, ...) \ + fio_io_connect((fio_io_connect_args_s){.url = url_, __VA_ARGS__}) + +/* ***************************************************************************** +IO Operations +***************************************************************************** */ + +/** + * Attaches the socket in `fd` to the facio.io engine (reactor). + * + * * `fd` should point to a valid socket. + * + * * `protocol` may be the existing protocol or NULL (for partial hijack). + * + * * `udata` is opaque user data and may be any value, including NULL. + * + * * `tls` is a context for Transport Layer (Security) and can be used to + * redirect read/write operations, as set by the protocol. + * + * Returns NULL on error. the `fio_io_s` pointer must NOT be used except + * within proper callbacks. + */ +SFUNC fio_io_s *fio_io_attach_fd(int fd, + fio_io_protocol_s *protocol, + void *udata, + void *tls); + +/** Sets a new protocol object. `NULL` is a valid "only-write" protocol. */ +SFUNC fio_io_protocol_s *fio_io_protocol_set(fio_io_s *io, + fio_io_protocol_s *protocol); + +/** + * Returns a pointer to the current protocol object. + * + * If `protocol` wasn't properly set, the pointer might be NULL or invalid. + * + * If `protocol` wasn't attached yet, may return the previous protocol. + */ +IFUNC fio_io_protocol_s *fio_io_protocol(fio_io_s *io); + +/** Returns the a pointer to the memory buffer required by the protocol. */ +IFUNC void *fio_io_buffer(fio_io_s *io); + +/** Returns the length of the `buf` buffer. */ +IFUNC size_t fio_io_buffer_len(fio_io_s *io); + +/** Associates a new `udata` pointer with the IO, returning the old `udata` */ +IFUNC void *fio_io_udata_set(fio_io_s *io, void *udata); + +/** Returns the `udata` pointer associated with the IO. */ +IFUNC void *fio_io_udata(fio_io_s *io); + +/** Associates a new `tls` pointer with the IO, returning the old `tls` */ +IFUNC void *fio_io_tls_set(fio_io_s *io, void *tls); + +/** Returns the `tls` pointer associated with the IO. */ +IFUNC void *fio_io_tls(fio_io_s *io); + +/** Returns the socket file descriptor (fd) associated with the IO. */ +IFUNC int fio_io_fd(fio_io_s *io); + +/** Resets a socket's timeout counter. */ +SFUNC void fio_io_touch(fio_io_s *io); + +/** + * Reads data to the buffer, if any data exists. Returns the number of bytes + * read. + * + * NOTE: zero (`0`) is a valid return value meaning no data was available. + */ +SFUNC size_t fio_io_read(fio_io_s *io, void *buf, size_t len); + +typedef struct { + /** The buffer with the data to send (if no file descriptor) */ + void *buf; + /** The file descriptor to send (if no buffer) */ + intptr_t fd; + /** The length of the data to be sent. On files, 0 = the whole file. */ + size_t len; + /** The length of the data to be sent. On files, 0 = the whole file. */ + size_t offset; + /** + * If this is a buffer, the de-allocation function used to free it. + * + * If NULL, the buffer will NOT be de-allocated. + */ + void (*dealloc)(void *); + /** If non-zero, makes a copy of the buffer or keeps a file open. */ + uint8_t copy; +} fio_io_write_args_s; + +/** + * Writes data to the outgoing buffer and schedules the buffer to be sent. + */ +SFUNC void fio_io_write2(fio_io_s *io, fio_io_write_args_s args); +#define fio_io_write2(io, ...) \ + fio_io_write2(io, (fio_io_write_args_s){__VA_ARGS__}) + +/** Helper macro for a common fio_io_write2 (copies the buffer). */ +#define fio_io_write(io, buf_, len_) \ + fio_io_write2(io, .buf = (buf_), .len = (len_), .copy = 1) + +/** + * Sends data from a file as if it were a single atomic packet (sends up to + * length bytes or until EOF is reached). + * + * Once the file was sent, the `source_fd` will be closed using `close`. + * + * The file will be buffered to the socket chunk by chunk, so that memory + * consumption is capped. + * + * `offset` dictates the starting point for the data to be sent and length sets + * the maximum amount of data to be sent. + * + * Closes the file on error. + */ +#define fio_io_sendfile(io, source_fd, offset_, bytes) \ + fio_io_write2((io), \ + .fd = (source_fd), \ + .offset = (size_t)(offset_), \ + .len = (bytes)) + +/** Marks the IO for closure as soon as scheduled data was sent. */ +SFUNC void fio_io_close(fio_io_s *io); + +/** Marks the IO for immediate closure. */ +SFUNC void fio_io_close_now(fio_io_s *io); + +/** + * Increases a IO's reference count, so it won't be automatically destroyed + * when all tasks have completed. + * + * Use this function in order to use the IO outside of a scheduled task. + * + * This function is thread-safe. + */ +SFUNC fio_io_s *fio_io_dup(fio_io_s *io); + +/** + * Decreases a IO's reference count, so it could be automatically destroyed + * when all other tasks have completed. + * + * Use this function once finished with a IO that was `dup`-ed. + * + * This function is thread-safe. + */ +SFUNC void fio_io_free(fio_io_s *io); + +/** Suspends future `on_data` events for the IO. */ +SFUNC void fio_io_suspend(fio_io_s *io); + +/** Listens for future `on_data` events related to the IO. */ +SFUNC void fio_io_unsuspend(fio_io_s *io); + +/** Returns 1 if the IO handle was suspended. */ +SFUNC int fio_io_is_suspended(fio_io_s *io); + +/** Returns 1 if the IO handle is marked as open. */ +SFUNC int fio_io_is_open(fio_io_s *io); + +/** Returns the approximate number of bytes in the outgoing buffer. */ +SFUNC size_t fio_io_backlog(fio_io_s *io); + +/* ***************************************************************************** +Task Scheduling +***************************************************************************** */ + +/** Schedules a task for delayed execution. This function is thread-safe. */ +SFUNC void fio_io_defer(void (*task)(void *, void *), + void *udata1, + void *udata2); + +/** Schedules a timer bound task, see `fio_timer_schedule`. */ +SFUNC void fio_io_run_every(fio_timer_schedule_args_s args); +/** + * Schedules a timer bound task, see `fio_timer_schedule`. + * + * Possible "named arguments" (fio_timer_schedule_args_s members) include: + * + * * The timer function. If it returns a non-zero value, the timer stops: + * int (*fn)(void *, void *) + * * Opaque user data: + * void *udata1 + * * Opaque user data: + * void *udata2 + * * Called when the timer is done (finished): + * void (*on_stop)(void *, void *) + * * Timer interval, in milliseconds: + * uint32_t every + * * The number of times the timer should be performed. -1 == infinity: + * int32_t repetitions + */ +#define fio_io_run_every(...) \ + fio_io_run_every((fio_timer_schedule_args_s){__VA_ARGS__}) + +/** Returns a pointer for the IO reactor's queue. */ +SFUNC fio_queue_s *fio_io_queue(void); + +/**************************************************************************/ /** +Protocol IO Functions +============ + +The Protocol struct uses IO callbacks to allow an easy way to override the +system's IO functions. + +This defines Transport Layer callbacks that facil.io will treat as non-blocking +system calls and allows any protocol to easily add a secure (SSL/TLS) flavor if +desired. +*/ +struct fio_io_functions_s { + /** Helper that converts a `fio_io_tls_s` into the implementation's context. + */ + void *(*build_context)(fio_io_tls_s *tls, uint8_t is_client); + /** Helper to free the context built by build_context. */ + void (*free_context)(void *context); + /** called when a new IO is first attached to a valid protocol. */ + void (*start)(fio_io_s *io); + /** Called to perform a non-blocking `read`, same as the system call. */ + ssize_t (*read)(int fd, void *buf, size_t len, void *context); + /** Called to perform a non-blocking `write`, same as the system call. */ + ssize_t (*write)(int fd, const void *buf, size_t len, void *context); + /** Sends any unsent internal data. Returns 0 only if all data was sent. */ + int (*flush)(int fd, void *context); + /** Called when the IO object finished sending all data before closure. */ + void (*finish)(int fd, void *context); + /** Called after the IO object is closed, used to cleanup its `tls` object. */ + void (*cleanup)(void *context); +}; + +/**************************************************************************/ /** +The Protocol +============ + +The Protocol struct defines the callbacks used for a family of connections and +sets their behavior. The Protocol struct is part of facil.io's core design. + +Protocols are usually global objects and the same protocol can be assigned to +multiple IO handles. + +All the callbacks receive a IO handle, which is used instead of the system's +file descriptor and protects callbacks and IO operations from sending data to +incorrect clients (possible `fd` "recycling"). +*/ +struct fio_io_protocol_s { + /** + * Reserved / private data - used by facil.io internally. + * MUST be initialized to zero. + */ + struct { + /* A linked list of currently attached IOs (ordered) - do NOT alter. */ + FIO_LIST_HEAD ios; + /* A linked list of other protocols used by IO core - do NOT alter. */ + FIO_LIST_NODE protocols; + /* internal flags - do NOT alter after initial initialization to zero. */ + uintptr_t flags; + } reserved; + /** Called when an IO is attached to the protocol. */ + void (*on_attach)(fio_io_s *io); + /** Called when a data is available. */ + void (*on_data)(fio_io_s *io); + /** called once all pending `fio_write` calls are finished. */ + void (*on_ready)(fio_io_s *io); + + /** + * Called when the IO reactor is shutting down, immediately before closing the + * connection. + * + * After the `on_shutdown` callback returns, the socket is marked for closure. + * + * Once the socket was marked for closure, facil.io will allow a limited + * amount of time for data to be sent, after which the socket might be closed + * even if the client did not consume all buffered data. + */ + void (*on_shutdown)(fio_io_s *io); + /** + * Called when a connection's timeout was reached + * + * Can be set to `fio_io_touch` if timeout is irrelevant (i.e., UDP). + */ + void (*on_timeout)(fio_io_s *io); + /** Used as a default `on_message` when an IO object subscribes. */ + + /** Called after the connection was closed (once per IO). */ + void (*on_close)(void *iobuf, void *udata); + + void (*on_pubsub)(struct fio_msg_s *msg); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user1)(fio_io_s *io, void *user_data); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user2)(fio_io_s *io, void *user_data); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user3)(fio_io_s *io, void *user_data); + /** Reserved for future protocol agnostic callbacks. */ + void (*on_reserved)(fio_io_s *io, void *user_data); + /** + * Defines Transport Layer callbacks that facil.io will treat as non-blocking + * system calls. + */ + fio_io_functions_s io_functions; + /** + * The timeout value in milliseconds for all connections using this protocol. + * + * Limited to FIO_IO_TIMEOUT_MAX seconds. Zero (0) == FIO_IO_TIMEOUT_MAX + */ + uint32_t timeout; + /** The number of bytes to allocate for the fio_io_buf buffer. */ + uint32_t buffer_size; +}; + +/** Performs a task for each IO in the stated protocol. */ +SFUNC size_t fio_io_protocol_each(fio_io_protocol_s *protocol, + void (*task)(fio_io_s *, void *udata2), + void *udata2); + +/* ***************************************************************************** +Connection Object Links / Environment +***************************************************************************** */ + +/** Named arguments for the `fio_io_env_set` function. */ +typedef struct { + /** A numerical type filter. Defaults to 0. Negative values are reserved. */ + intptr_t type; + /** The name for the link. The name and type uniquely identify the object. */ + fio_buf_info_s name; + /** The object being linked to the connection. */ + void *udata; + /** A callback that will be called once the connection is closed. */ + void (*on_close)(void *data); + /** Set to true (1) if the name string's life lives as long as the `env` . */ + uint8_t const_name; +} fio_io_env_set_args_s; + +/** Named arguments for the `fio_io_env_unset` function. */ +typedef struct { + /** A numerical type filter. Should be the same as used with + * `fio_io_env_set` */ + intptr_t type; + /** The name of the object. Should be the same as used with `fio_io_env_set` + */ + fio_buf_info_s name; +} fio_io_env_get_args_s; + +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +SFUNC void *fio_io_env_get(fio_io_s *io, fio_io_env_get_args_s); + +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +#define fio_io_env_get(io, ...) \ + fio_io_env_get(io, (fio_io_env_get_args_s){__VA_ARGS__}) + +/** + * Links an object to a connection's lifetime / environment. + * + * The `on_close` callback will be called once the connection has died. + * + * If the `io` is NULL, the value will be set for the global environment. + */ +SFUNC void fio_io_env_set(fio_io_s *io, fio_io_env_set_args_s); + +/** + * Links an object to a connection's lifetime, calling the `on_close` callback + * once the connection has died. + * + * If the `io` is NULL, the value will be set for the global environment, in + * which case the `on_close` callback will only be called once the process + * exits. + * + * This is a helper MACRO that allows the function to be called using named + * arguments. + */ +#define fio_io_env_set(io, ...) \ + fio_io_env_set(io, (fio_io_env_set_args_s){__VA_ARGS__}) + +/** + * Un-links an object from the connection's lifetime, so it's `on_close` + * callback will NOT be called. + * + * Returns 0 on success and -1 if the object couldn't be found. + */ +SFUNC int fio_io_env_unset(fio_io_s *io, fio_io_env_get_args_s); + +/** + * Un-links an object from the connection's lifetime, so it's `on_close` + * callback will NOT be called. + * + * Returns 0 on success and -1 if the object couldn't be found. + * + * This is a helper MACRO that allows the function to be called using named + * arguments. + */ +#define fio_io_env_unset(io, ...) \ + fio_io_env_unset(io, (fio_io_env_get_args_s){__VA_ARGS__}) + +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + */ +SFUNC int fio_io_env_remove(fio_io_s *io, fio_io_env_get_args_s); + +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + * + * This is a helper MACRO that allows the function to be called using named + * arguments. + */ +#define fio_io_env_remove(io, ...) \ + fio_io_env_remove(io, (fio_io_env_get_args_s){__VA_ARGS__}) + +/* ***************************************************************************** +TLS Context Helper Types +***************************************************************************** */ + +/** Performs a `new` operation, returning a new `fio_io_tls_s` context. */ +SFUNC fio_io_tls_s *fio_io_tls_new(void); + +/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ +SFUNC fio_io_tls_s *fio_io_tls_from_url(fio_io_tls_s *target_or_null, + fio_url_s url); + +/** Performs a `dup` operation, increasing the object's reference count. */ +SFUNC fio_io_tls_s *fio_io_tls_dup(fio_io_tls_s *); + +/** Performs a `free` operation, reducing the reference count and freeing. */ +SFUNC void fio_io_tls_free(fio_io_tls_s *); + +/** + * Adds a certificate a new SSL/TLS context / settings object (SNI support). + * + * fio_io_tls_cert_add(tls, "www.example.com", + * "public_key.pem", + * "private_key.pem", NULL ); + * + * NOTE: Except for the `tls` and `server_name` arguments, all arguments might + * be `NULL`, which a context builder (`fio_io_functions_s`) should + * treat as a request for a self-signed certificate. It may be silently ignored. + */ +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); + +/** + * Adds an ALPN protocol callback to the SSL/TLS context. + * + * The first protocol added will act as the default protocol to be selected. + * + * A `NULL` protocol name will be silently ignored. + * + * A `NULL` callback (`on_selected`) will be silently replaced with a no-op. + */ +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *tls, + const char *protocol_name, + void (*on_selected)(fio_io_s *)); + +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *tls, + const char *protocol_name, + size_t name_length, + fio_io_s *); + +/** + * Adds a certificate to the "trust" list, which automatically adds a peer + * verification requirement. + * + * If `public_cert_file` is `NULL`, implementation is expected to add the + * system's default trust registry. + * + * Note: when the `fio_io_tls_s` object is used for server connections, this + * should limit connections to clients that connect using a trusted certificate. + * + * fio_io_tls_trust_add(tls, "google-ca.pem" ); + */ +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *, + const char *public_cert_file); + +/** + * Returns the number of `fio_io_tls_cert_add` instructions. + * + * This could be used when deciding if to add a NULL instruction (self-signed). + * + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls); + +/** + * Returns the number of registered ALPN protocol names. + * + * This could be used when deciding if protocol selection should be delegated to + * the ALPN mechanism, or whether a protocol should be immediately assigned. + * + * If no ALPN protocols are registered, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls); + +/** + * Returns the number of `fio_io_tls_trust_add` instructions. + * + * This could be used when deciding if to disable peer verification or not. + * + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls); + +/** Arguments (and info) for `fio_io_tls_each`. */ +typedef struct fio_io_tls_each_s { + fio_io_tls_s *tls; + void *udata; + void *udata2; + int (*each_cert)(struct fio_io_tls_each_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); + int (*each_alpn)(struct fio_io_tls_each_s *, + const char *protocol_name, + void (*on_selected)(fio_io_s *)); + int (*each_trust)(struct fio_io_tls_each_s *, const char *public_cert_file); +} fio_io_tls_each_s; + +/** Calls callbacks for certificate, trust certificate and ALPN added. */ +SFUNC int fio_io_tls_each(fio_io_tls_each_s); + +/** `fio_io_tls_each` helper macro, see `fio_io_tls_each_s` for named + * arguments. */ +#define fio_io_tls_each(tls_, ...) \ + fio_io_tls_each(((fio_io_tls_each_s){.tls = tls_, __VA_ARGS__})) + +/** If `NULL` returns current default, otherwise sets it. */ +SFUNC fio_io_functions_s fio_io_tls_default_functions(fio_io_functions_s *); + +/* ***************************************************************************** +IO Async Queue - Worker Threads for non-IO tasks +***************************************************************************** */ + +/** The IO Async Queue type. */ +struct fio_io_async_s { + fio_queue_s *q; + uint32_t count; + fio_queue_s queue; + FIO_LIST_NODE node; +}; + +/** + * Initializes an IO Async Queue (multi-threaded task queue). + * + * The queue automatically spawns threads and shuts down as the IO reactor + * starts or stops. + * + * It is recommended that the `fio_io_async_s` be allocated as a static + * variable, as its memory must remain valid throughout the lifetime of the + * IO reactor's app. + */ +#define FIO_IO_ASYN_INIT ((fio_io_async_s){0}) + +/** Returns the current task queue associated with the IO Async Queue. */ +FIO_IFUNC fio_queue_s *fio_io_async_queue(fio_io_async_s *q) { return q->q; } + +/** + * Attaches an IO Async Queue for use in multi-threaded (non IO) tasks. + * + * This function can be called multiple times for the same (or other) queue, as + * long as the async queue (`fio_io_async_s`) was previously initialized using + * `FIO_IO_ASYN_INIT` or zeroed out. i.e.: + * + * static fio_io_async_s SLOW_HTTP_TASKS = FIO_IO_ASYN_INIT; + * fio_io_async_attach(&SLOW_HTTP_TASKS, 32); + */ +SFUNC void fio_io_async_attach(fio_io_async_s *q, uint32_t threads); + +/** Pushes a task to an IO Async Queue (macro helper). */ +#define fio_io_async(q_, ...) fio_queue_push((q_)->q, __VA_ARGS__) + +/* ***************************************************************************** +IO API Finish +***************************************************************************** */ +#endif /* FIO_IO */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + IO Reactor - an Evented IO Reactor, Single-Threaded + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO_TYPES___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +#define H___FIO_IO_TYPES___H + +#define FIO___IO_GET_TIME_MILLI() fio_time2milli(fio_time_real()) + +/* ***************************************************************************** +IO environment support (`env`) +***************************************************************************** */ + +/** An object that can be linked to any facil.io connection (fio_s). */ +typedef struct { + void (*on_close)(void *data); + void *udata; +} fio___io_env_obj_s; + +/* unordered `env` dictionary style map */ +#define FIO_UMAP_NAME fio___io_env +#define FIO_MAP_KEY_KSTR +#define FIO_MAP_VALUE fio___io_env_obj_s +#define FIO_MAP_VALUE_DESTROY(o) \ + do { \ + if ((o).on_close) \ + (o).on_close((o).udata); \ + } while (0) +#define FIO_MAP_DESTROY_AFTER_COPY 0 + +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +typedef struct { + fio_thread_mutex_t lock; + fio___io_env_s env; +} fio___io_env_safe_s; + +#define FIO___IO_ENV_SAFE_INIT \ + { .lock = FIO_THREAD_MUTEX_INIT, .env = FIO_MAP_INIT } + +FIO_IFUNC void *fio___io_env_safe_get(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + void *r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_get(&e->env, hash, key).udata; + fio_thread_mutex_unlock(&e->lock); + return r; +} + +FIO_IFUNC void fio___io_env_safe_set(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_, + fio___io_env_obj_s val, + uint8_t key_is_const) { + fio_str_info_s key = FIO_STR_INFO3(key_, len, !key_is_const); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + fio___io_env_set(&e->env, hash, key, val, NULL); + fio_thread_mutex_unlock(&e->lock); +} + +FIO_IFUNC int fio___io_env_safe_unset(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + int r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio___io_env_obj_s old; + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_remove(&e->env, hash, key, &old); + fio_thread_mutex_unlock(&e->lock); + return r; +} + +FIO_IFUNC int fio___io_env_safe_remove(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + int r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_remove(&e->env, hash, key, NULL); + fio_thread_mutex_unlock(&e->lock); + return r; +} + +FIO_IFUNC void fio___io_env_safe_destroy(fio___io_env_safe_s *e) { + fio___io_env_destroy(&e->env); /* no need to lock, performed in IO thread. */ + fio_thread_mutex_destroy(&e->lock); + *e = (fio___io_env_safe_s)FIO___IO_ENV_SAFE_INIT; +} + +/* ***************************************************************************** +Protocol Type Initialization +***************************************************************************** */ + +static void fio___io_on_ev_mock_sus(fio_io_s *io) { fio_io_suspend(io); } +static void fio___io_on_ev_mock(fio_io_s *io) { (void)(io); } +static void fio___io_on_ev_pubsub_mock(struct fio_msg_s *msg) { (void)(msg); } +static void fio___io_on_user_mock(fio_io_s *io, void *i_) { + (void)io, (void)i_; +} +static void fio___io_on_close_mock(void *p1, void *p2) { (void)p1, (void)p2; } +static void fio___io_on_ev_on_timeout(fio_io_s *io) { fio_io_close_now(io); } + +/* Called to perform a non-blocking `read`, same as the system call. */ +static ssize_t fio___io_func_default_read(int fd, + void *buf, + size_t len, + void *tls) { + return fio_sock_read(fd, buf, len); + (void)tls; +} +/** Called to perform a non-blocking `write`, same as the system call. */ +static ssize_t fio___io_func_default_write(int fd, + const void *buf, + size_t len, + void *tls) { + return fio_sock_write(fd, buf, len); + (void)tls; +} +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +static int fio___io_func_default_flush(int fd, void *tls) { + return 0; + (void)fd, (void)tls; +} +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +static void fio___io_func_default_finish(int fd, void *tls) { + (void)fd, (void)tls; +} +static void fio___io_func_default_cleanup(void *p1) { (void)p1; } + +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void *fio___io_func_default_build_context(fio_io_tls_s *tls, + uint8_t is_client) { + if (!tls) + return NULL; + FIO_ASSERT(0, + "SSL/TLS `build_context` was called, but no SSL/TLS " + "implementation found."); + return NULL; + (void)tls, (void)is_client; +} +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void fio___io_func_default_free_context(void *context) { + if (!context) + return; + FIO_ASSERT(0, + "SSL/TLS `free_context` was called, but no SSL/TLS " + "implementation found."); + (void)context; +} + +static void fio___io_func_free_context_caller_task(void *fn_ptr, + void *context) { + union { + void (*free_context)(void *context); + void *fn_ptr; + } u = {.fn_ptr = fn_ptr}; + u.free_context(context); +} + +static void fio___io_func_free_context_caller(void (*free_context)(void *), + void *context) { + union { + void (*free_context)(void *context); + void *fn_ptr; + } u = {.free_context = free_context}; + fio_queue_push(fio_io_queue(), + fio___io_func_free_context_caller_task, + u.fn_ptr, + context); +} + +FIO_SFUNC void fio___io_init_protocol(fio_io_protocol_s *pr, _Bool has_tls) { + pr->reserved.protocols = FIO_LIST_INIT(pr->reserved.protocols); + pr->reserved.ios = FIO_LIST_INIT(pr->reserved.ios); + fio_io_functions_s io_fn = { + .build_context = fio___io_func_default_build_context, + .free_context = fio___io_func_default_free_context, + .start = fio___io_on_ev_mock, + .read = fio___io_func_default_read, + .write = fio___io_func_default_write, + .flush = fio___io_func_default_flush, + .finish = fio___io_func_default_finish, + .cleanup = fio___io_func_default_cleanup, + }; + if (has_tls) + io_fn = fio_io_tls_default_functions(NULL); + if (!pr->on_attach) + pr->on_attach = fio___io_on_ev_mock; + if (!pr->on_data) + pr->on_data = fio___io_on_ev_mock_sus; + if (!pr->on_ready) + pr->on_ready = fio___io_on_ev_mock; + if (!pr->on_close) + pr->on_close = fio___io_on_close_mock; + if (!pr->on_shutdown) + pr->on_shutdown = fio___io_on_ev_mock; + if (!pr->on_timeout) + pr->on_timeout = fio___io_on_ev_on_timeout; + if (!pr->on_pubsub) + pr->on_pubsub = fio___io_on_ev_pubsub_mock; + if (!pr->on_user1) + pr->on_user1 = fio___io_on_user_mock; + if (!pr->on_user2) + pr->on_user2 = fio___io_on_user_mock; + if (!pr->on_user3) + pr->on_user3 = fio___io_on_user_mock; + if (!pr->on_reserved) + pr->on_reserved = fio___io_on_user_mock; + if (!pr->io_functions.build_context) + pr->io_functions.build_context = io_fn.build_context; + if (!pr->io_functions.free_context) + pr->io_functions.free_context = io_fn.free_context; + if (!pr->io_functions.start) + pr->io_functions.start = io_fn.start; + if (!pr->io_functions.read) + pr->io_functions.read = io_fn.read; + if (!pr->io_functions.write) + pr->io_functions.write = io_fn.write; + if (!pr->io_functions.flush) + pr->io_functions.flush = io_fn.flush; + if (!pr->io_functions.finish) + pr->io_functions.finish = io_fn.finish; + if (!pr->io_functions.cleanup) + pr->io_functions.cleanup = io_fn.cleanup; + if (!pr->timeout) + pr->timeout = FIO_IO_TIMEOUT_MAX; + /* round up to nearest 16 byte size */ + pr->buffer_size = ((pr->buffer_size + 15ULL) & (~15ULL)); +} + +/* the FIO___MOCK_PROTOCOL is used to manage hijacked / zombie connections. */ +static fio_io_protocol_s FIO___IO_MOCK_PROTOCOL; + +FIO_IFUNC void fio___io_init_protocol_test(fio_io_protocol_s *pr, + _Bool has_tls) { + if (!fio_atomic_or(&pr->reserved.flags, 1)) + fio___io_init_protocol(pr, has_tls); +} + +/* ***************************************************************************** +IO Reactor State Machine +***************************************************************************** */ + +#define FIO___IO_FLAG_WAKEUP (1U) + +SFUNC struct FIO___IO { + fio_poll_s poll; + int64_t tick; + fio_queue_s queue; + uint32_t flags; + uint16_t workers; + uint8_t is_worker; + volatile uint8_t stop; + fio_timer_queue_s timer; + int wakeup_fd; + fio_thread_pid_t root_pid; + fio_thread_pid_t pid; + fio___io_env_safe_s env; + FIO_LIST_NODE protocols; + FIO_LIST_NODE async; + fio_io_s *wakeup; +} FIO___IO = { + .tick = 0, + .wakeup_fd = -1, + .stop = 1, +}; + +/** Stopping the IO reactor. */ +SFUNC void fio_io_stop(void) { FIO___IO.stop = 1; } + +/** Returns current process id. */ +SFUNC int fio_io_pid(void) { return FIO___IO.pid; } + +/** Returns the root / master process id. */ +SFUNC int fio_io_root_pid(void) { return FIO___IO.root_pid; } + +/** Returns true if running and 0 if stopped or shutting down. */ +SFUNC int fio_io_is_running(void) { return !FIO___IO.stop; } + +/** Returns true if the current process is the master process. */ +SFUNC int fio_io_is_master(void) { return FIO___IO.root_pid == FIO___IO.pid; } + +/** Returns true if the current process is a worker process. */ +SFUNC int fio_io_is_worker(void) { return FIO___IO.is_worker; } + +/** Returns the last millisecond when the polled for IO events. */ +SFUNC int64_t fio_io_last_tick(void) { return FIO___IO.tick; } + +FIO_SFUNC void fio___io_wakeup(void); +void fio_io_defer___(void); +/** Schedules a task for delayed execution. This function is thread-safe. */ +SFUNC void fio_io_defer FIO_NOOP(void (*task)(void *, void *), + void *udata1, + void *udata2) { + fio_queue_push(&FIO___IO.queue, task, udata1, udata2); + fio___io_wakeup(); +} + +FIO_IFUNC void fio___io_defer_no_wakeup(void (*task)(void *, void *), + void *udata1, + void *udata2) { + fio_queue_push(&FIO___IO.queue, task, udata1, udata2); +} + +void fio_io_run_every___(void); +/** Schedules a timer bound task, see `fio_timer_schedule`. */ +SFUNC void fio_io_run_every FIO_NOOP(fio_timer_schedule_args_s args) { + args.start_at = FIO___IO.tick; + fio_timer_schedule FIO_NOOP(&FIO___IO.timer, args); +} + +/** Returns a pointer for the IO reactor's queue. */ +SFUNC fio_queue_s *fio_io_queue(void) { return &FIO___IO.queue; } + +/* ***************************************************************************** +IO Type +***************************************************************************** */ + +#define FIO___IO_FLAG_OPEN ((uint32_t)1U) +#define FIO___IO_FLAG_SUSPENDED ((uint32_t)2U) +#define FIO___IO_FLAG_THROTTLED ((uint32_t)4U) +#define FIO___IO_FLAG_CLOSE ((uint32_t)8U) +#define FIO___IO_FLAG_CLOSE_REMOTE ((uint32_t)16U) +#define FIO___IO_FLAG_CLOSE_ERROR ((uint32_t)32U) +#define FIO___IO_FLAG_TOUCH ((uint32_t)64U) +#define FIO___IO_FLAG_WRITE_SCHD ((uint32_t)128U) +#define FIO___IO_FLAG_POLLIN_SET ((uint32_t)256U) +#define FIO___IO_FLAG_POLLOUT_SET ((uint32_t)512U) + +#define FIO___IO_FLAG_PREVENT_ON_DATA \ + (FIO___IO_FLAG_SUSPENDED | FIO___IO_FLAG_THROTTLED | FIO___IO_FLAG_CLOSE | \ + FIO___IO_FLAG_CLOSE_REMOTE | FIO___IO_FLAG_CLOSE_ERROR) + +#define FIO___IO_FLAG_POLL_SET \ + (FIO___IO_FLAG_POLLIN_SET | FIO___IO_FLAG_POLLOUT_SET) + +#define FIO___IO_FLAG_SET(io, flag_to_set) \ + fio_atomic_or(&(io)->flags, flag_to_set) +#define FIO___IO_FLAG_UNSET(io, flag_to_unset) \ + fio_atomic_and(&(io)->flags, ~(flag_to_unset)) + +static void fio___io_poll_on_data_schd(void *io); +static void fio___io_poll_on_ready_schd(void *io); +static void fio___io_poll_on_close_schd(void *io); + +/** The main IO object type. Should be treated as an opaque pointer. */ +struct fio_io_s { + int fd; + uint32_t flags; + FIO_LIST_NODE node; + void *udata; + void *tls; + fio_io_protocol_s *pr; + fio_stream_s out; + fio___io_env_safe_s env; +#if FIO_IO_COUNT_STORAGE + size_t total_sent; + size_t total_recieved; +#endif + int64_t active; +}; + +FIO_IFUNC void fio___io_monitor_in(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Input for %d (called)", + fio_io_pid(), + io->fd); + if (io->flags & FIO___IO_FLAG_PREVENT_ON_DATA) + return; + if ((FIO___IO_FLAG_SET(io, FIO___IO_FLAG_POLLIN_SET) & + FIO___IO_FLAG_POLLIN_SET)) { + return; + } + fio_poll_monitor(&FIO___IO.poll, io->fd, (void *)io, POLLIN); + FIO_LOG_DDEBUG2("(%d) IO monitoring Input for %d", fio_io_pid(), io->fd); +} +FIO_IFUNC void fio___io_monitor_out(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Output for %d (called)", + fio_io_pid(), + io->fd); + if (io->flags & FIO___IO_FLAG_WRITE_SCHD) + return; + if ((FIO___IO_FLAG_SET(io, FIO___IO_FLAG_POLLOUT_SET) & + FIO___IO_FLAG_POLLOUT_SET)) + return; + fio_poll_monitor(&FIO___IO.poll, io->fd, (void *)io, POLLOUT); + FIO_LOG_DDEBUG2("(%d) IO monitoring Output for %d", fio_io_pid(), io->fd); +} + +FIO_IFUNC void fio___io_monitor_forget(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Removed for %d (called)", + fio_io_pid(), + io->fd); + if (!(FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_POLL_SET) & + FIO___IO_FLAG_POLL_SET)) + return; + fio_poll_forget(&FIO___IO.poll, io->fd); + FIO_LOG_DDEBUG2("(%d) IO monitoring Removed for %d", fio_io_pid(), io->fd); +} + +FIO_SFUNC void fio___io_destroy(fio_io_s *io) { + fio_io_protocol_s *pr = io->pr; + FIO_LIST_REMOVE(&io->node); +#if FIO_IO_COUNT_STORAGE + FIO_LOG_DDEBUG2( + "(%d) detaching and destroying %p (fd %d): %zu/%zu bytes received/sent", + FIO___IO.pid, + (void *)io, + io->fd, + io->total_recieved, + io->total_sent); +#else + FIO_LOG_DDEBUG2("(%d) detaching and destroying %p (fd %d).", + FIO___IO.pid, + (void *)io, + io->fd); +#endif + /* store info, as it might be freed if the protocol is freed. */ + if (FIO_LIST_IS_EMPTY(&io->pr->reserved.ios)) + FIO_LIST_REMOVE_RESET(&io->pr->reserved.protocols); + /* call on_stop / free callbacks . */ + pr->io_functions.cleanup(io->tls); + pr->on_close((void *)(io + 1), io->udata); + fio___io_env_safe_destroy(&io->env); + fio_sock_close(io->fd); + fio_stream_destroy(&io->out); + fio___io_monitor_forget(io); + FIO_LOG_DDEBUG2("(%d) IO closed and destroyed for fd %d", + fio_io_pid(), + io->fd); +} + +#define FIO_REF_NAME fio___io +#define FIO_REF_TYPE fio_io_s +#define FIO_REF_FLEX_TYPE uint8_t +#define FIO_REF_DESTROY(io) fio___io_destroy(&io) +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +FIO_SFUNC void fio___io_protocol_set(void *io_, void *pr_) { + fio_io_s *io = (fio_io_s *)io_; + fio_io_protocol_s *pr = (fio_io_protocol_s *)pr_; + fio_io_protocol_s *old = io->pr; + if (!pr) + pr = &FIO___IO_MOCK_PROTOCOL; + fio___io_init_protocol_test(pr, (io->tls != NULL)); + FIO_LIST_REMOVE(&io->node); + if (FIO_LIST_IS_EMPTY(&old->reserved.ios)) + FIO_LIST_REMOVE_RESET(&old->reserved.protocols); + if (FIO_LIST_IS_EMPTY(&pr->reserved.ios)) + FIO_LIST_PUSH(&FIO___IO.protocols, &pr->reserved.protocols); + FIO_LIST_PUSH(&pr->reserved.ios, &io->node); + io->pr = pr; + FIO_LOG_DDEBUG2("(%d) protocol set for IO with fd %d", + fio_io_pid(), + fio_io_fd(io)); + pr->on_attach(io); + /* avoid calling `start` and setting `on_ready` more than once */ + if (old == &FIO___IO_MOCK_PROTOCOL) { + pr->io_functions.start(io); + fio___io_monitor_out(io); + } + fio___io_monitor_in(io); + fio___io_free2(io); +} + +/** Performs a task for each IO in the stated protocol. */ +SFUNC size_t fio_io_protocol_each(fio_io_protocol_s *protocol, + void (*task)(fio_io_s *, void *udata2), + void *udata2) { + size_t count = 0; + if (!protocol || !protocol->reserved.protocols.next) + return count; + FIO_LIST_EACH(fio_io_s, node, &protocol->reserved.ios, io) { + task(io, udata2); + ++count; + } + return count; +} + +/* Attaches the socket in `fd` to the facio.io engine (reactor). */ +SFUNC fio_io_s *fio_io_attach_fd(int fd, + fio_io_protocol_s *pr, + void *udata, + void *tls) { + fio_io_s *io = NULL; + if (fd == -1) + goto error; + io = fio___io_new2(pr->buffer_size); + *io = (fio_io_s){ + .fd = fd, + .flags = FIO___IO_FLAG_OPEN, + .pr = &FIO___IO_MOCK_PROTOCOL, + .node = FIO_LIST_INIT(io->node), + .udata = udata, + .tls = tls, + .active = FIO___IO.tick, + }; + fio_sock_set_non_block(fd); + FIO_LOG_DDEBUG2("(%d) attaching fd %d to IO object %p (%zu bytes buffer)", + fio_io_pid(), + fd, + (void *)io, + fio_io_buffer_len(io)); + fio_io_defer(fio___io_protocol_set, (void *)fio___io_dup2(io), (void *)pr); + return io; + +error: + pr->on_close(NULL, udata); + pr->io_functions.cleanup(tls); + return io; +} + +/** Sets a new protocol object. `NULL` is a valid "only-write" protocol. */ +SFUNC fio_io_protocol_s *fio_io_protocol_set(fio_io_s *io, + fio_io_protocol_s *pr) { + fio_io_defer(fio___io_protocol_set, (void *)fio___io_dup2(io), (void *)pr); + return pr; +} + +/** + * Returns a pointer to the current protocol object. + * + * If `protocol` wasn't properly set, the pointer might be NULL or invalid. + * + * If `protocol` wasn't attached yet, may return the previous protocol. + */ +IFUNC fio_io_protocol_s *fio_io_protocol(fio_io_s *io) { return io->pr; } + +/** Returns the a pointer to the memory buffer required by the protocol. */ +IFUNC void *fio_io_buffer(fio_io_s *io) { return (void *)(io + 1); } + +/** Returns the length of the `buffer` buffer. */ +IFUNC size_t fio_io_buffer_len(fio_io_s *io) { + return fio___io_metadata_flex_len(io); +} + +/** Associates a new `udata` pointer with the IO, returning the old `udata` */ +IFUNC void *fio_io_udata_set(fio_io_s *io, void *udata) { + void *old = io->udata; + io->udata = udata; + return old; +} + +/** Returns the `udata` pointer associated with the IO. */ +IFUNC void *fio_io_udata(fio_io_s *io) { return io->udata; } + +/** Associates a new `tls` pointer with the IO, returning the old `tls` */ +IFUNC void *fio_io_tls_set(fio_io_s *io, void *tls) { + void *old = io->tls; + io->tls = tls; + return old; +} + +/** Returns the `tls` pointer associated with the IO. */ +IFUNC void *fio_io_tls(fio_io_s *io) { return io->tls; } + +/** Returns the socket file descriptor (fd) associated with the IO. */ +IFUNC int fio_io_fd(fio_io_s *io) { return io->fd; } + +FIO_SFUNC void fio___io_touch(void *io_, void *ignr_) { + fio_io_s *io = (fio_io_s *)io_; + fio_atomic_and(&io->flags, ~FIO___IO_FLAG_TOUCH); + io->active = FIO___IO.tick; + FIO_LIST_REMOVE(&io->node); /* timeout IO ordering */ + FIO_LIST_PUSH(&io->pr->reserved.ios, &io->node); + fio___io_free2(io); + (void)ignr_; +} + +/* Resets a socket's timeout counter. */ +SFUNC void fio_io_touch(fio_io_s *io) { + if (!(fio_atomic_or(&io->flags, FIO___IO_FLAG_TOUCH) & FIO___IO_FLAG_TOUCH)) + fio_queue_push_urgent(&FIO___IO.queue, fio___io_touch, fio___io_dup2(io)); +} + +/** + * Reads data to the buffer, if any data exists. Returns the number of bytes + * read. + * + * NOTE: zero (`0`) is a valid return value meaning no data was available. + */ +SFUNC size_t fio_io_read(fio_io_s *io, void *buf, size_t len) { + if (!io) + return 0; + ssize_t r = io->pr->io_functions.read(io->fd, buf, len, io->tls); + if (r > 0) { +#if FIO_IO_COUNT_STORAGE + io->total_recieved += r; +#endif + fio_io_touch(io); + return r; + } + if ((unsigned)(!len) | + ((unsigned)(r == -1) & ((unsigned)(errno == EAGAIN) | + (errno == EWOULDBLOCK) | (errno == EINTR)))) + return 0; + fio_io_close(io); + return 0; +} + +FIO_SFUNC void fio___io_write2_dealloc_task(void *fn, void *data) { + union { + void *ptr; + void (*fn)(void *); + } u = {.ptr = fn}; + u.fn(data); +} + +FIO_SFUNC void fio___io_write2(void *io_, void *packet_) { + fio_io_s *io = (fio_io_s *)io_; + fio_stream_packet_s *packet = (fio_stream_packet_s *)packet_; + if (!(io->flags & FIO___IO_FLAG_OPEN)) + goto io_closed; + + fio_stream_add(&io->out, packet); + fio___io_poll_on_ready_schd((void *)io); + fio___io_free2(io); + return; + +io_closed: + fio_stream_packet_free(packet); + fio___io_free2(io); +} + +void fio_io_write2___(void); +/** + * Writes data to the outgoing buffer and schedules the buffer to be sent. + */ +SFUNC void fio_io_write2 FIO_NOOP(fio_io_s *io, fio_io_write_args_s args) { + fio_stream_packet_s *packet = NULL; + if (!io) + goto io_error_null; + if (args.buf) { + packet = fio_stream_pack_data(args.buf, + args.len, + args.offset, + args.copy, + args.dealloc); + } else if ((unsigned)(args.fd + 1) > 1) { + packet = fio_stream_pack_fd((int)args.fd, args.len, args.offset, args.copy); + } + if (!packet) + goto error; + if ((io->flags & FIO___IO_FLAG_CLOSE)) + goto write_called_after_close; + fio_io_defer(fio___io_write2, (void *)fio___io_dup2(io), (void *)packet); + return; + +error: /* note: `dealloc` already called by the `fio_stream` error handler. */ + FIO_LOG_ERROR("couldn't create %zu bytes long user-packet for IO %p (%d)", + args.len, + (void *)io, + (io ? io->fd : -1)); + return; + +write_called_after_close: + FIO_LOG_DEBUG2("`write` called after `close` was called for IO."); + { + union { + void *ptr; + void (*fn)(fio_stream_packet_s *); + } u = {.fn = fio_stream_pack_free}; + fio___io_defer_no_wakeup(fio___io_write2_dealloc_task, u.ptr, packet); + } + return; + +io_error_null: + FIO_LOG_ERROR("(%d) `fio_write2` called for invalid IO (NULL)", FIO___IO.pid); + if (args.dealloc) { + union { + void *ptr; + void (*fn)(void *); + } u = {.fn = args.dealloc}; + fio___io_defer_no_wakeup(fio___io_write2_dealloc_task, u.ptr, args.buf); + if ((unsigned)(args.fd + 1) > 1) + close((int)args.fd); + } +} + +/** Marks the IO for closure as soon as scheduled data was sent. */ +SFUNC void fio_io_close(fio_io_s *io) { + if (io && (io->flags & FIO___IO_FLAG_OPEN) && + !(FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE) & FIO___IO_FLAG_CLOSE)) { + FIO_LOG_DDEBUG2("(%d) scheduling IO %p (fd %d) for closure", + fio_io_pid(), + (void *)io, + io->fd); + fio___io_poll_on_ready_schd((void *)io); + } +} + +/** Marks the IO for immediate closure. */ +SFUNC void fio_io_close_now(fio_io_s *io) { + if (!io) + return; + FIO_LOG_DDEBUG2("(%d) pre-destruction close called for fd %d", + fio_io_pid(), + fio_io_fd(io)); + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE); + if ((FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_OPEN) & FIO___IO_FLAG_OPEN)) + fio_io_free(io); +} + +/** + * Increases a IO's reference count, so it won't be automatically destroyed + * when all tasks have completed. + * + * Use this function in order to use the IO outside of a scheduled task. + * + * This function is thread-safe. + */ +SFUNC fio_io_s *fio_io_dup(fio_io_s *io) { return fio___io_dup2(io); } + +SFUNC void fio___io_free_task(void *io_, void *ignr_) { + fio___io_free2((fio_io_s *)io_); + (void)ignr_; +} +/** Free IO (reference) - thread-safe */ +SFUNC void fio_io_free(fio_io_s *io) { + fio___io_defer_no_wakeup(fio___io_free_task, (void *)io, NULL); +} + +/** Suspends future "on_data" events for the IO. */ +SFUNC void fio_io_suspend(fio_io_s *io) { + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_SUSPENDED); +} + +SFUNC void fio___io_unsuspend(void *io_, void *ignr_) { + fio_io_s *io = (fio_io_s *)io_; + fio___io_monitor_in(io); + (void)ignr_; +} + +/** Listens for future "on_data" events related to the IO. */ +SFUNC void fio_io_unsuspend(fio_io_s *io) { + if ((FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_SUSPENDED) & + FIO___IO_FLAG_SUSPENDED)) + fio_io_defer(fio___io_unsuspend, (void *)io, NULL); +} + +/** Returns 1 if the IO handle was suspended. */ +SFUNC int fio_io_is_suspended(fio_io_s *io) { + return (int)((io->flags & FIO___IO_FLAG_SUSPENDED) / FIO___IO_FLAG_SUSPENDED); +} + +/** Returns 1 if the IO handle is marked as open. */ +SFUNC int fio_io_is_open(fio_io_s *io) { + return (int)((io->flags & FIO___IO_FLAG_OPEN) / FIO___IO_FLAG_OPEN); +} + +/** Returns the approximate number of bytes in the outgoing buffer. */ +SFUNC size_t fio_io_backlog(fio_io_s *io) { + return fio_stream_length(&io->out); +} + +/* ***************************************************************************** +Connection Object Links / Environment +***************************************************************************** */ + +void *fio_io_env_get___(void); /* IDE Marker */ +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +SFUNC void *fio_io_env_get FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_get(e, a.name.buf, a.name.len, a.type); +} + +void fio_io_env_set___(void); /* IDE Marker */ +/** Links an object to a connection's lifetime / environment. */ +SFUNC void fio_io_env_set FIO_NOOP(fio_io_s *io, fio_io_env_set_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + fio___io_env_safe_set(e, + a.name.buf, + a.name.len, + a.type, + (fio___io_env_obj_s){.on_close = a.on_close, a.udata}, + a.const_name); +} + +int fio_io_env_unset___(void); /* IDE Marker */ +/** Un-links an object from the connection's lifetime, so it's `on_close` */ +SFUNC int fio_io_env_unset FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_unset(e, a.name.buf, a.name.len, a.type); +} + +int fio_io_env_remove___(void); /* IDE Marker */ +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + */ +SFUNC int fio_io_env_remove FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_remove(e, a.name.buf, a.name.len, a.type); +} + +/* ***************************************************************************** +Event handling +***************************************************************************** */ + +static void fio___io_poll_on_data(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_POLLIN_SET); + if (!(io->flags & FIO___IO_FLAG_PREVENT_ON_DATA)) { + /* this also tests for the suspended / throttled / closing flags */ + io->pr->on_data(io); + fio___io_monitor_in(io); + } else if ((io->flags & FIO___IO_FLAG_OPEN)) { + fio___io_monitor_out(io); + } + fio___io_free2(io); + return; +} + +static void fio___io_poll_on_ready(void *io_, void *ignr_) { + (void)ignr_; +#if DEBUG + errno = 0; +#endif + fio_io_s *io = (fio_io_s *)io_; + char buf_mem[FIO_IO_BUFFER_PER_WRITE]; + size_t total = 0; + FIO___IO_FLAG_UNSET(io, + (FIO___IO_FLAG_POLLOUT_SET | FIO___IO_FLAG_WRITE_SCHD)); + FIO_LOG_DDEBUG2("(%d) poll_on_ready callback for fd %d", + fio_io_pid(), + fio_io_fd(io)); + if (!(io->flags & FIO___IO_FLAG_OPEN)) + goto finish; + for (;;) { + size_t len = FIO_IO_BUFFER_PER_WRITE; + char *buf = buf_mem; + fio_stream_read(&io->out, &buf, &len); + if (!len) + break; + ssize_t r = io->pr->io_functions.write(io->fd, buf, len, io->tls); + if (r > 0) { + FIO_LOG_DDEBUG2("(%d) written %zu bytes to fd %d", + FIO___IO.pid, + (size_t)r, + io->fd); + total += r; + fio_stream_advance(&io->out, r); + continue; + } + if (r == -1) { + if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) + break; + if (errno == EINTR) + continue; + } + goto connection_error; + } + if (total) { + fio___io_touch((void *)fio___io_dup2(io), NULL); +#if FIO_IO_COUNT_STORAGE + io->total_sent += total; +#endif + } + if (fio_stream_any(&io->out) || io->pr->io_functions.flush(io->fd, io->tls)) { + if (fio_stream_length(&io->out) >= FIO_IO_THROTTLE_LIMIT) { + if (!(io->flags & FIO___IO_FLAG_THROTTLED)) + FIO_LOG_DDEBUG2("(%d), throttled IO %p (fd %d)", + FIO___IO.pid, + (void *)io, + io->fd); + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_THROTTLED); + } + fio___io_monitor_out(io); + } else if ((io->flags & FIO___IO_FLAG_CLOSE)) { + io->pr->io_functions.finish(io->fd, io->tls); + fio_io_close_now(io); + } else { + if ((io->flags & FIO___IO_FLAG_THROTTLED)) { + FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_THROTTLED); + fio___io_monitor_in(io); + } + FIO_LOG_DDEBUG2("(%d) calling on_ready for %p (fd %d) - %zu data left.", + FIO___IO.pid, + (void *)io, + io->fd, + fio_stream_length(&io->out)); + io->pr->on_ready(io); + } + +finish: + fio___io_free2(io); + return; + +connection_error: +#if DEBUG + if (fio_stream_any(&io->out)) + FIO_LOG_DERROR( + "(%d) IO write failed (%d), disconnecting: %p (fd %d)\n\tError: %s", + FIO___IO.pid, + errno, + (void *)io, + io->fd, + strerror(errno)); +#endif + fio_io_close_now(io); + fio___io_free2(io); +} + +static void fio___io_poll_on_close(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE_REMOTE); + FIO_LOG_DEBUG2("(%d) fd %d closed by remote peer", FIO___IO.pid, io->fd); + fio_io_close_now(io); + fio___io_free2(io); +} + +static void fio___io_poll_on_timeout(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + io->pr->on_timeout(io); + fio___io_free2(io); +} + +/* ***************************************************************************** +Event scheduling +***************************************************************************** */ + +static void fio___io_poll_on_data_schd(void *io) { + FIO_LOG_DDEBUG2("(%d) `on_data` scheduled for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_data, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); +} +static void fio___io_poll_on_ready_schd(void *io) { + if (!(FIO___IO_FLAG_SET((fio_io_s *)io, FIO___IO_FLAG_WRITE_SCHD) & + FIO___IO_FLAG_WRITE_SCHD)) { + FIO_LOG_DDEBUG2("(%d) `on_ready` scheduled for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_ready, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); + } +} +static void fio___io_poll_on_close_schd(void *io) { + FIO_LOG_DDEBUG2("(%d) remote closure for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_close, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); +} + +/* ***************************************************************************** +Timeout Review +***************************************************************************** */ + +/** Schedules the timeout event for any timed out IO object */ +static int fio___io_review_timeouts(void) { + int c = 0; + static time_t last_to_review = 0; + /* test timeouts at whole second intervals */ + if (last_to_review + 1000 > FIO___IO.tick) + return c; + last_to_review = FIO___IO.tick; + const int64_t now_milli = FIO___IO.tick; + + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_ASSERT_DEBUG(pr->reserved.flags, "protocol object flags unmarked?!"); + if (!pr->timeout || pr->timeout > FIO_IO_TIMEOUT_MAX) + pr->timeout = FIO_IO_TIMEOUT_MAX; + int64_t limit = now_milli - ((int64_t)pr->timeout); + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + FIO_ASSERT_DEBUG(io->pr == pr, "IO protocol ownership error"); + if (io->active >= limit) + break; + FIO_LOG_DDEBUG2("(%d) scheduling timeout for %p (fd %d)", + FIO___IO.pid, + (void *)io, + io->fd); + fio___io_defer_no_wakeup(fio___io_poll_on_timeout, + (void *)fio___io_dup2(io), + NULL); + ++c; + } + } + return c; +} + +/* ***************************************************************************** +Wakeup Protocol +***************************************************************************** */ + +FIO_SFUNC void fio___io_wakeup_cb(fio_io_s *io) { + char buf[512]; + ssize_t r = fio_sock_read(fio_io_fd(io), buf, 512); + (void)r; + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup called", FIO___IO.pid); + FIO___IO_FLAG_UNSET(&FIO___IO, FIO___IO_FLAG_WAKEUP); +} +FIO_SFUNC void fio___io_wakeup_on_close(void *ignr1_, void *ignr2_) { + fio_sock_close(FIO___IO.wakeup_fd); + FIO___IO.wakeup = NULL; + FIO___IO.wakeup_fd = -1; + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup destroyed", FIO___IO.pid); + (void)ignr1_, (void)ignr2_; +} + +FIO_SFUNC void fio___io_wakeup(void) { + if (!FIO___IO.wakeup || (FIO___IO_FLAG_SET(&FIO___IO, FIO___IO_FLAG_WAKEUP) & + FIO___IO_FLAG_WAKEUP)) + return; + char buf[1] = {(char)~0}; + ssize_t ignr = fio_sock_write(FIO___IO.wakeup_fd, buf, 1); + (void)ignr; +} + +static fio_io_protocol_s FIO___IO_WAKEUP_PROTOCOL = { + .on_data = fio___io_wakeup_cb, + .on_close = fio___io_wakeup_on_close, + .on_timeout = fio_io_touch, +}; + +FIO_SFUNC void fio___io_wakeup_init(void) { + if (FIO___IO.wakeup) + return; + int fds[2]; + if (pipe(fds)) { + FIO_LOG_ERROR("(%d) couldn't open wakeup pipes, fio___io_wakeup disabled.", + FIO___IO.pid); + return; + } + fio_sock_set_non_block(fds[0]); + fio_sock_set_non_block(fds[1]); + FIO___IO.wakeup_fd = fds[1]; + FIO___IO.wakeup = fio_io_attach_fd(fds[0], + &FIO___IO_WAKEUP_PROTOCOL, + (void *)(uintptr_t)fds[1], + NULL); + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup initialized", FIO___IO.pid); +} + +/* ***************************************************************************** +TLS Context Type and Helpers +***************************************************************************** */ + +typedef struct { + fio_keystr_s nm; + void (*fn)(fio_io_s *); +} fio___io_tls_alpn_s; + +typedef struct { + fio_keystr_s nm; + fio_keystr_s public_cert_file; + fio_keystr_s private_key_file; + fio_keystr_s pk_password; +} fio___io_tls_cert_s; + +typedef struct { + fio_keystr_s nm; +} fio___io_tls_trust_s; + +#undef FIO_TYPEDEF_IMAP_REALLOC +#undef FIO_TYPEDEF_IMAP_FREE +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC(p, size_old, size, copy) realloc(p, size) +#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 + +#define FIO___IO_ALPN_HASH(o) ((uint16_t)fio_keystr_hash(o->nm)) +#define FIO___IO_ALPN_CMP(a, b) fio_keystr_is_eq(a->nm, b->nm) +#define FIO___IO_ALPN_VALID(o) fio_keystr_buf(&o->nm).len + +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_alpn_map, + fio___io_tls_alpn_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO___IO_ALPN_VALID) +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_trust_map, + fio___io_tls_trust_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO___IO_ALPN_VALID) +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_cert_map, + fio___io_tls_cert_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO_IMAP_ALWAYS_VALID) + +#undef FIO___IO_ALPN_HASH +#undef FIO___IO_ALPN_CMP +#undef FIO___IO_ALPN_VALID +#undef FIO_TYPEDEF_IMAP_REALLOC +#undef FIO_TYPEDEF_IMAP_FREE +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC +#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE + +struct fio_io_tls_s { + fio___io_tls_cert_map_s cert; + fio___io_tls_alpn_map_s alpn; + fio___io_tls_trust_map_s trust; + uint8_t trust_sys; /** Set to 1 if system certificate registry is trusted */ +}; + +#define FIO___RECURSIVE_INCLUDE 1 +#define FIO_REF_NAME fio_io_tls +#define FIO_REF_DESTROY(tls) \ + do { \ + FIO_IMAP_EACH(fio___io_tls_alpn_map, &tls.alpn, i) { \ + fio_keystr_destroy(&tls.alpn.ary[i].nm, FIO_STRING_FREE_KEY); \ + } \ + FIO_IMAP_EACH(fio___io_tls_trust_map, &tls.trust, i) { \ + fio_keystr_destroy(&tls.trust.ary[i].nm, FIO_STRING_FREE_KEY); \ + } \ + FIO_IMAP_EACH(fio___io_tls_cert_map, &tls.cert, i) { \ + fio_keystr_destroy(&tls.cert.ary[i].nm, FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].public_cert_file, \ + FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].private_key_file, \ + FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].pk_password, FIO_STRING_FREE_KEY); \ + } \ + fio___io_tls_alpn_map_destroy(&tls.alpn); \ + fio___io_tls_trust_map_destroy(&tls.trust); \ + fio___io_tls_cert_map_destroy(&tls.cert); \ + } while (0) +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/** Performs a `new` operation, returning a new `fio_io_tls_s` context. */ +SFUNC fio_io_tls_s *fio_io_tls_new(void) { + fio_io_tls_s *r = fio_io_tls_new2(); + FIO_ASSERT_ALLOC(r); + *r = (fio_io_tls_s){.trust_sys = 0}; + return r; +} + +/** Performs a `dup` operation, increasing the object's reference count. */ +SFUNC fio_io_tls_s *fio_io_tls_dup(fio_io_tls_s *tls) { + return fio_io_tls_dup2(tls); +} + +/** Performs a `free` operation, reducing the reference count and freeing. */ +SFUNC void fio_io_tls_free(fio_io_tls_s *tls) { + if (!tls) + return; + fio_io_tls_free2(tls); +} + +/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ +SFUNC fio_io_tls_s *fio_io_tls_from_url(fio_io_tls_s *tls, fio_url_s url) { + /* test for TLS info in URL */ + fio_url_tls_info_s tls_info = fio_url_is_tls(url); + if (!tls_info.tls) + return tls; + + if (!tls && tls_info.tls) + tls = fio_io_tls_new(); + + if (tls_info.key.buf && tls_info.cert.buf) { + const char *tmp = NULL; + FIO_STR_INFO_TMP_VAR(host_tmp, 512); + FIO_STR_INFO_TMP_VAR(key_tmp, 128); + FIO_STR_INFO_TMP_VAR(cert_tmp, 128); + FIO_STR_INFO_TMP_VAR(pass_tmp, 128); + if (url.host.len < 512 && url.host.buf) + fio_string_write(&host_tmp, NULL, url.host.buf, url.host.len); + else + host_tmp.buf = NULL; + + if (tls_info.key.len < 124 && tls_info.cert.len < 124 && + tls_info.pass.len < 124) { + fio_string_write(&key_tmp, NULL, tls_info.key.buf, tls_info.key.len); + fio_string_write(&cert_tmp, NULL, tls_info.cert.buf, tls_info.cert.len); + if (tls_info.pass.len) + fio_string_write(&pass_tmp, NULL, tls_info.pass.buf, tls_info.pass.len); + else + pass_tmp.buf = NULL; + + if (tls_info.key.buf == + tls_info.cert.buf) { /* assume value is prefix / folder */ + if ((tmp = getenv(cert_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + key_tmp.len = cert_tmp.len = buf_tmp.len; + FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); + FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + fio_string_write(&key_tmp, NULL, "key.pem", 7); + fio_string_write(&cert_tmp, NULL, "cert.pem", 8); + } else { + if ((tmp = getenv(key_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + key_tmp.len = buf_tmp.len; + FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + + if ((tmp = getenv(cert_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + cert_tmp.len = buf_tmp.len; + FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + + if (tls_info.key.len < 5 || + (fio_buf2u32u(tls_info.key.buf + (tls_info.key.len - 4)) | + 0x20202020UL) != fio_buf2u32u(".pem")) { + fio_string_write(&key_tmp, NULL, ".pem", 4); + } + if (tls_info.cert.len < 5 || + (fio_buf2u32u(tls_info.cert.buf + (tls_info.cert.len - 4)) | + 0x20202020UL) != fio_buf2u32u(".pem")) { + fio_string_write(&cert_tmp, NULL, ".pem", 4); + } + } + fio_io_tls_cert_add(tls, + host_tmp.buf, + cert_tmp.buf, + key_tmp.buf, + pass_tmp.buf); + } else { + FIO_LOG_ERROR("TLS files in `fio_io_listen` URL too long, " + "construct TLS object separately"); + } + } + return tls; +} + +/** Adds a certificate a new SSL/TLS context / settings object (SNI support). */ +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *t, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + if (!t) + return t; + fio___io_tls_cert_s o = { + .nm = fio_keystr_init(FIO_STR_INFO1((char *)server_name), + FIO_STRING_ALLOC_KEY), + .public_cert_file = + fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), + FIO_STRING_ALLOC_KEY), + .private_key_file = + fio_keystr_init(FIO_STR_INFO1((char *)private_key_file), + FIO_STRING_ALLOC_KEY), + .pk_password = fio_keystr_init(FIO_STR_INFO1((char *)pk_password), + FIO_STRING_ALLOC_KEY), + }; + fio___io_tls_cert_s *old = fio___io_tls_cert_map_get(&t->cert, o); + if (old) + goto replace_old; + fio___io_tls_cert_map_set(&t->cert, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->public_cert_file, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->private_key_file, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->pk_password, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** + * Adds an ALPN protocol callback to the SSL/TLS context. + * + * The first protocol added will act as the default protocol to be selected. + * + * Except for the `tls` and `protocol_name` arguments, all arguments can be + * NULL. + */ +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *t, + const char *protocol_name, + void (*on_selected)(fio_io_s *)) { + if (!t || !protocol_name) + return t; + if (!on_selected) + on_selected = fio___io_on_ev_mock; + size_t pr_name_len = strlen(protocol_name); + if (pr_name_len > 255) { + FIO_LOG_ERROR( + "fio_io_tls_alpn_add called with name longer than 255 chars!"); + return t; + } + fio___io_tls_alpn_s o = { + .nm = fio_keystr_init(FIO_STR_INFO2((char *)protocol_name, pr_name_len), + FIO_STRING_ALLOC_KEY), + .fn = on_selected, + }; + fio___io_tls_alpn_s *old = fio___io_tls_alpn_map_get(&t->alpn, o); + if (old) + goto replace_old; + fio___io_tls_alpn_map_set(&t->alpn, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *t, + const char *protocol_name, + size_t name_length, + fio_io_s *io) { + if (!t || !protocol_name) + return -1; + fio___io_tls_alpn_s seeking = { + .nm = fio_keystr_tmp(protocol_name, (uint32_t)name_length)}; + fio___io_tls_alpn_s *alpn = fio___io_tls_alpn_map_get(&t->alpn, seeking); + if (!alpn) { + FIO_LOG_DDEBUG2("TLS ALPN %.*s not found in %zu long list", + (int)name_length, + protocol_name, + t->alpn.count); + return -1; + } + alpn->fn(io); + return 0; +} + +/** + * Adds a certificate to the "trust" list, which automatically adds a peer + * verification requirement. + * + * If `public_cert_file` is `NULL`, adds the system's default trust registry. + * + * Note: when the `fio_io_tls_s` object is used for server connections, this + * will limit connections to clients that connect using a trusted certificate. + * + * fio_io_tls_trust_add(tls, "google-ca.pem" ); + */ +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *t, + const char *public_cert_file) { + if (!t) + return t; + if (!public_cert_file) { + t->trust_sys = 1; + return t; + } + fio___io_tls_trust_s o = { + .nm = fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), + FIO_STRING_ALLOC_KEY), + }; + fio___io_tls_trust_s *old = fio___io_tls_trust_map_get(&t->trust, o); + if (old) + goto replace_old; + fio___io_tls_trust_map_set(&t->trust, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** + * Returns the number of `fio_io_tls_cert_add` instructions. + * + * This could be used when deciding if to add a NULL instruction (self-signed). + * + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls) { + return tls ? tls->cert.count : 0; +} + +/** + * Returns the number of registered ALPN protocol names. + * + * This could be used when deciding if protocol selection should be delegated to + * the ALPN mechanism, or whether a protocol should be immediately assigned. + * + * If no ALPN protocols are registered, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls) { + return tls ? tls->alpn.count : 0; +} + +/** + * Returns the number of `fio_io_tls_trust_add` instructions. + * + * This could be used when deciding if to disable peer verification or not. + * + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls) { + return tls ? tls->trust.count : 0; +} + +/** Calls callbacks for certificate, trust certificate and ALPN added. */ +void fio_io_tls_each___(void); /* IDE Marker*/ +SFUNC int fio_io_tls_each FIO_NOOP(fio_io_tls_each_s a) { + if (!a.tls) + return -1; + if (a.each_cert) { + FIO_IMAP_EACH(fio___io_tls_cert_map, &a.tls->cert, i) { + if (a.each_cert(&a, + fio_keystr_buf(&a.tls->cert.ary[i].nm).buf, + fio_keystr_buf(&a.tls->cert.ary[i].public_cert_file).buf, + fio_keystr_buf(&a.tls->cert.ary[i].private_key_file).buf, + fio_keystr_buf(&a.tls->cert.ary[i].pk_password).buf)) + return -1; + } + } + if (a.each_alpn) { + FIO_IMAP_EACH(fio___io_tls_alpn_map, &a.tls->alpn, i) { + if (a.each_alpn(&a, + fio_keystr_buf(&a.tls->alpn.ary[i].nm).buf, + a.tls->alpn.ary[i].fn)) + return -1; + } + } + if (a.each_trust) { + if (a.tls->trust_sys && a.each_trust(&a, NULL)) + return -1; + FIO_IMAP_EACH(fio___io_tls_trust_map, &a.tls->trust, i) { + if (a.each_trust(&a, fio_keystr_buf(&a.tls->trust.ary[i].nm).buf)) + return -1; + } + } + return 0; +} + +/** If `NULL` returns current default, otherwise sets it. */ +SFUNC fio_io_functions_s fio_io_tls_default_functions(fio_io_functions_s *f) { + static fio_io_functions_s default_io_functions = { + .build_context = fio___io_func_default_build_context, + .start = fio___io_on_ev_mock, + .read = fio___io_func_default_read, + .write = fio___io_func_default_write, + .flush = fio___io_func_default_flush, + .finish = fio___io_func_default_finish, + .cleanup = fio___io_func_default_cleanup, + }; + if (!f) + return default_io_functions; + if (!f->build_context) + f->build_context = fio___io_func_default_build_context; + if (!f->start) + f->start = fio___io_on_ev_mock; + if (!f->read) + f->read = fio___io_func_default_read; + if (!f->write) + f->write = fio___io_func_default_write; + if (!f->flush) + f->flush = fio___io_func_default_flush; + if (!f->finish) + f->finish = fio___io_func_default_finish; + if (!f->cleanup) + f->cleanup = fio___io_func_default_cleanup; + default_io_functions = *f; + return default_io_functions; +} + +/* ***************************************************************************** +IO Async Queues - Worker Threads for non-IO tasks +***************************************************************************** */ + +FIO_SFUNC void fio___io_async_start(fio_io_async_s *q) { + if (!q->count) + goto no_worker_threads; + q->q = &q->queue; + if (q->count > 4095) + goto failed; + fio_queue_workers_stop(&q->queue); + if (fio_queue_workers_add(&q->queue, (size_t)q->count)) + goto failed; + return; + +failed: + FIO_LOG_ERROR("IO Async Queue couldn't spawn threads!"); +no_worker_threads: + q->q = fio_io_queue(); + fio_queue_perform_all(&q->queue); +} +FIO_SFUNC void fio___io_async_stop(fio_io_async_s *q) { + q->q = fio_io_queue(); + fio_queue_workers_stop(&q->queue); + fio_queue_perform_all(&q->queue); + fio_queue_destroy(&q->queue); +} + +/** + * Attaches an IO Async Queue for use in multi-threaded (non IO) tasks. + * + * This function can be called multiple times for the same (or other) queue, as + * long as the async queue (`fio_io_async_s`) was previously initialized using + * `FIO_IO_ASYN_INIT` or zeroed out. i.e.: + * + * static fio_io_async_s SLOW_HTTP_TASKS = FIO_IO_ASYN_INIT; + * fio_io_async_attach(&SLOW_HTTP_TASKS, 32); + */ +SFUNC void fio_io_async_attach(fio_io_async_s *q, uint32_t threads) { + if (!q) + return; + if (!q->node.next) { + *q = (fio_io_async_s){ + .q = fio_io_queue(), + .count = threads, + .queue = FIO_QUEUE_STATIC_INIT(q->queue), + .node = FIO_LIST_INIT(q->node), + }; + FIO_LIST_PUSH(&FIO___IO.async, &q->node); + } + q->count = threads; + if (fio_io_is_running()) + fio___io_async_start(q); +} + +/* ***************************************************************************** +Managing data after a fork +***************************************************************************** */ +FIO_SFUNC void fio___io_after_fork(void *ignr_) { + (void)ignr_; + FIO___IO.pid = fio_thread_getpid(); + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + fio_io_close_now(io); + } + } + fio_queue_perform_all(&FIO___IO.queue); + fio_queue_destroy(&FIO___IO.queue); +} + +FIO_SFUNC void fio___io_cleanup_at_exit(void *ignr_) { + fio___io_after_fork(ignr_); + fio_poll_destroy(&FIO___IO.poll); + fio___io_env_safe_destroy(&FIO___IO.env); +#if FIO_VALIDITY_MAP_USE + fio_validity_map_destroy(&FIO___IO.valid); +#if FIO_VALIDATE_IO_MUTEX + fio_thread_mutex_destroy(&FIO___IO.valid_lock); +#endif +#endif /* FIO_VALIDATE_IO_MUTEX / FIO_VALIDITY_MAP_USE */ + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_queue_perform_all(&FIO___IO.queue); + fio_timer_destroy(&FIO___IO.timer); + fio_queue_perform_all(&FIO___IO.queue); +} + +/* ***************************************************************************** +Initializing IO Reactor State +***************************************************************************** */ +FIO_CONSTRUCTOR(fio___io) { + fio_queue_init(&FIO___IO.queue); + FIO___IO.protocols = FIO_LIST_INIT(FIO___IO.protocols); + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + FIO___IO.root_pid = FIO___IO.pid = fio_thread_getpid(); + FIO___IO.async = FIO_LIST_INIT(FIO___IO.async); + fio___io_init_protocol(&FIO___IO_MOCK_PROTOCOL, 0); + fio_poll_init(&FIO___IO.poll, + .on_data = fio___io_poll_on_data_schd, + .on_ready = fio___io_poll_on_ready_schd, + .on_close = fio___io_poll_on_close_schd); + fio___io_init_protocol_test(&FIO___IO_MOCK_PROTOCOL, 0); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___io_after_fork, NULL); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___io_cleanup_at_exit, NULL); +} + +/* ***************************************************************************** +IO Types Finish +***************************************************************************** */ +#endif /* FIO_IO */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + IO Reactor - an Evented IO Reactor, Single-Threaded + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO_REACTOR___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +#define H___FIO_IO_REACTOR___H + +/* ***************************************************************************** +The IO Reactor Cycle (the actual work) +***************************************************************************** */ + +static void fio___io_signal_handle(int sig, void *flg) { + ((uint8_t *)flg)[0] = 1; + (void)sig; +} + +FIO_SFUNC void fio___io_tick(int timeout) { + static size_t performed_idle = 0; + size_t idle_round = (fio_poll_review(&FIO___IO.poll, timeout) == 0); + performed_idle &= idle_round; + idle_round &= (timeout > 0); + idle_round ^= performed_idle; + if ((idle_round & !FIO___IO.stop)) { + fio_state_callback_force(FIO_CALL_ON_IDLE); + performed_idle = 1; + } + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_timer_push2queue(&FIO___IO.queue, &FIO___IO.timer, FIO___IO.tick); + for (size_t i = 0; i < 2048; ++i) + if (fio_queue_perform(&FIO___IO.queue)) + break; + fio___io_review_timeouts(); + fio_signal_review(); +} + +FIO_SFUNC void fio___io_run_async_as_sync(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + unsigned repeat = 0; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, pos) { + fio_queue_task_s t = fio_queue_pop(&pos->queue); + if (!t.fn) + continue; + t.fn(t.udata1, t.udata2); + repeat = 1; + } + if (repeat) + fio_queue_push(&FIO___IO.queue, fio___io_run_async_as_sync); +} + +FIO_SFUNC void fio___io_shutdown_task(void *shutdown_start_, void *a2) { + intptr_t shutdown_start = (intptr_t)shutdown_start_; + if (shutdown_start + FIO_IO_SHUTDOWN_TIMEOUT < FIO___IO.tick || + FIO_LIST_IS_EMPTY(&FIO___IO.protocols)) + return; + fio___io_tick(fio_queue_count(&FIO___IO.queue) ? 0 : 100); + fio_queue_push(&FIO___IO.queue, fio___io_run_async_as_sync); + fio_queue_push(&FIO___IO.queue, fio___io_shutdown_task, shutdown_start_, a2); +} + +FIO_SFUNC void fio___io_shutdown(void) { + /* collect tick for shutdown start, to monitor for possible timeout */ + int64_t shutdown_start = FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + size_t connected = 0; + /* first notify that shutdown is starting */ + fio_state_callback_force(FIO_CALL_ON_SHUTDOWN); + /* preform on_shutdown callback for each connection and close */ + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + pr->on_shutdown(io); /* TODO / FIX: move callback to task? */ + fio_io_close(io); /* TODO / FIX: skip close on return value? */ + ++connected; + } + } + FIO_LOG_DEBUG2("(%d) IO Reactor shutting down with %zu connected clients", + fio_io_pid(), + connected); + /* cycle while connections exist. */ + fio_queue_push(&FIO___IO.queue, + fio___io_shutdown_task, + (void *)(intptr_t)shutdown_start, + NULL); + fio_queue_perform_all(&FIO___IO.queue); + /* in case of timeout, force close remaining connections. */ + connected = 0; + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + fio_io_close_now(io); + ++connected; + } + } + FIO_LOG_DEBUG2("(%d) IO Reactor shutdown timeout/done with %zu clients", + fio_io_pid(), + connected); + /* perform remaining tasks. */ + fio_queue_perform_all(&FIO___IO.queue); +} + +FIO_SFUNC void fio___io_work_task(void *ignr_1, void *ignr_2) { + if (FIO___IO.stop) + return; + fio___io_tick(fio_queue_count(&FIO___IO.queue) ? 0 : 500); + fio_queue_push(&FIO___IO.queue, fio___io_work_task, ignr_1, ignr_2); +} + +FIO_SFUNC void fio___io_work(int is_worker) { + FIO___IO.is_worker = is_worker; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_start(q); + } + + fio_queue_perform_all(&FIO___IO.queue); + if (is_worker) { + fio_state_callback_force(FIO_CALL_ON_START); + } + fio___io_wakeup_init(); + fio_queue_push(&FIO___IO.queue, fio___io_work_task); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); + } + fio___io_shutdown(); + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); + } + fio_queue_perform_all(&FIO___IO.queue); + fio_state_callback_force(FIO_CALL_ON_STOP); + fio_queue_perform_all(&FIO___IO.queue); + FIO___IO.workers = 0; +} + +/* ***************************************************************************** +Worker Forking +***************************************************************************** */ +static void fio___io_spawn_worker(void *ignr_1, void *ignr_2); + +static void fio___io_wait_for_worker(void *thr_) { + fio_thread_t t = (fio_thread_t)thr_; + fio_thread_join(&t); +} + +/** Worker sentinel */ +static void *fio___io_worker_sentinel(void *pid_data) { +#ifdef WEXITSTATUS + fio_thread_pid_t pid = (fio_thread_pid_t)(uintptr_t)pid_data; + int status = 0; + (void)status; + fio_thread_t thr = fio_thread_current(); + fio_state_callback_add(FIO_CALL_ON_STOP, + fio___io_wait_for_worker, + (void *)thr); + if (fio_thread_waitpid(pid, &status, 0) != pid && !FIO___IO.stop) + FIO_LOG_ERROR("waitpid failed, worker re-spawning might fail."); + if (!WIFEXITED(status) || WEXITSTATUS(status)) { + FIO_LOG_WARNING("abnormal worker exit detected"); + fio_state_callback_force(FIO_CALL_ON_CHILD_CRUSH); + } + if (!FIO___IO.stop) { + FIO_ASSERT_DEBUG( + 0, + "DEBUG mode prevents worker re-spawning, now crashing parent."); + fio_state_callback_remove(FIO_CALL_ON_STOP, + fio___io_wait_for_worker, + (void *)thr); + fio_thread_detach(&thr); + fio___io_defer_no_wakeup(fio___io_spawn_worker, (void *)thr, NULL); + } +#else /* Non POSIX? no `fork`? no fio_thread_waitpid? */ + FIO_ASSERT( + 0, + "facil.io doesn't know how to spawn and wait on workers on this system."); +#endif + return NULL; +} + +static void fio___io_spawn_worker(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + fio_thread_t t; + fio_signal_review(); + + if (FIO___IO.stop || !fio_io_is_master()) + return; + if (fio_atomic_or_fetch(&FIO___IO.stop, 2) != 2) + return; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); + } + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_state_callback_force(FIO_CALL_BEFORE_FORK); + /* do not allow master tasks to run in worker */ + fio_queue_perform_all(&FIO___IO.queue); + /* perform actual fork */ + fio_thread_pid_t pid = fio_thread_fork(); + FIO_ASSERT(pid != (fio_thread_pid_t)-1, "system call `fork` failed."); + if (!pid) + goto is_worker_process; + fio_state_callback_force(FIO_CALL_AFTER_FORK); + fio_state_callback_force(FIO_CALL_IN_MASTER); + if (fio_thread_create(&t, fio___io_worker_sentinel, (void *)(uintptr_t)pid)) { + FIO_LOG_FATAL( + "sentinel thread creation failed, no worker will be spawned."); + fio_io_stop(); + } + if (!fio_atomic_xor_fetch(&FIO___IO.stop, 2)) + fio___io_defer_no_wakeup(fio___io_work_task, NULL, NULL); + return; + +is_worker_process: + FIO___IO.pid = fio_thread_getpid(); + FIO___IO.is_worker = 1; + FIO_LOG_INFO("(%d) worker starting up.", fio_io_pid()); + fio_state_callback_force(FIO_CALL_AFTER_FORK); + fio_state_callback_force(FIO_CALL_IN_CHILD); + if (!fio_atomic_xor_fetch(&FIO___IO.stop, 2)) + fio___io_work(1); + FIO_LOG_INFO("(%d) worker exiting.", fio_io_pid()); + exit(0); +} + +/* ***************************************************************************** +Starting / Stopping the IO Reactor +***************************************************************************** */ + +/** Adds `workers` amount of workers to the root IO reactor process. */ +SFUNC void fio_io_add_workers(int workers) { + if (!workers || !fio_io_is_master()) + return; + FIO_LOG_INFO("(%d) spawning %d workers.", fio_io_pid(), workers); + for (int i = 0; i < workers; ++i) + fio___io_defer_no_wakeup(fio___io_spawn_worker, NULL, NULL); +} + +/** Starts the IO reactor, using optional `workers` processes. Will BLOCK! */ +SFUNC void fio_io_start(int workers) { + FIO___IO.stop = 0; + FIO___IO.workers = fio_io_workers(workers); + workers = (int)FIO___IO.workers; + FIO___IO.is_worker = !workers; + fio_sock_maximize_limits(0); + + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_start(q); + } + + fio_state_callback_force(FIO_CALL_PRE_START); + fio_queue_perform_all(&FIO___IO.queue); + fio_signal_monitor(SIGINT, fio___io_signal_handle, (void *)&FIO___IO.stop); + fio_signal_monitor(SIGTERM, fio___io_signal_handle, (void *)&FIO___IO.stop); +#ifdef SIGPIPE + fio_signal_monitor(SIGPIPE, NULL, NULL); +#endif + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + if (workers) { + FIO_LOG_INFO("(%d) spawning %d workers.", fio_io_root_pid(), workers); + for (int i = 0; i < workers; ++i) { + fio___io_spawn_worker(NULL, NULL); + } + } else { + FIO_LOG_DEBUG2("(%d) starting facil.io IO reactor in single process mode.", + fio_io_root_pid()); + } + fio___io_work(!workers); + fio_signal_forget(SIGINT); + fio_signal_forget(SIGTERM); +#ifdef SIGPIPE + fio_signal_forget(SIGPIPE); +#endif + fio_queue_perform_all(&FIO___IO.queue); +} + +/** Returns the number or workers the IO reactor will actually run. */ +SFUNC uint16_t fio_io_workers(int workers) { + if (workers < 0) { + long cores = -1; +#ifdef _SC_NPROCESSORS_ONLN + cores = sysconf(_SC_NPROCESSORS_ONLN); +#endif /* _SC_NPROCESSORS_ONLN */ + if (cores == -1L) { + cores = 8; + FIO_LOG_WARNING( + "fio_io_start / fio_io_workers called with negative value for worker " + "count, but auto-detect failed, assuming %d CPU cores", + cores); + } + workers = (int)(cores / (0 - workers)); + workers += !workers; + } + return (uint16_t)workers; +} + +/* ***************************************************************************** +Listening to Incoming Connections +***************************************************************************** */ + +typedef struct { + fio_io_protocol_s *protocol; + void *udata; + void *tls_ctx; + fio_io_async_s *queue_for_accept; + fio_queue_s *queue; + fio_io_s *io; + void (*on_start)(fio_io_protocol_s *protocol, void *udata); + void (*on_stop)(fio_io_protocol_s *protocol, void *udata); + int owner; + int fd; + size_t ref_count; + size_t url_len; + uint8_t hide_from_log; + char url[]; +} fio___io_listen_s; + +FIO_LEAK_COUNTER_DEF(fio_io_listen) + +static fio___io_listen_s *fio___io_listen_dup(fio___io_listen_s *l) { + fio_atomic_add(&l->ref_count, 1); + return l; +} + +static void fio___io_listen_free(void *l_) { + fio___io_listen_s *l = (fio___io_listen_s *)l_; + if (l->io) + fio_io_close(l->io); + if (fio_atomic_sub(&l->ref_count, 1)) + return; + + fio_state_callback_remove(FIO_CALL_AT_EXIT, fio___io_listen_free, (void *)l); + fio_state_callback_remove(FIO_CALL_ON_START, fio___io_listen_free, (void *)l); + fio_state_callback_remove(FIO_CALL_PRE_START, + fio___io_listen_free, + (void *)l); + fio___io_func_free_context_caller(l->protocol->io_functions.free_context, + l->tls_ctx); + fio_sock_close(l->fd); + +#ifdef AF_UNIX + /* delete the unix socket file, if any. */ + fio_url_s u = fio_url_parse(l->url, FIO_STRLEN(l->url)); + if (FIO___IO.pid == l->owner && !u.host.buf && !u.port.buf && u.path.buf) { + unlink(u.path.buf); + } +#endif + + if (l->on_stop) + l->on_stop(l->protocol, l->udata); + + if (l->hide_from_log) + FIO_LOG_DEBUG2("(%d) stopped listening @ %.*s", + getpid(), + (int)l->url_len, + l->url); + else + FIO_LOG_INFO("(%d) stopped listening @ %.*s", + getpid(), + (int)l->url_len, + l->url); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LEAK_COUNTER_ON_FREE(fio_io_listen); + FIO_MEM_FREE_(l, sizeof(*l) + l->url_len + 1); +} + +SFUNC void fio_io_listen_stop(void *listener) { + if (listener) + fio___io_listen_free(listener); +} + +/** Returns the URL on which the listener is listening. */ +SFUNC fio_buf_info_s fio_io_listener_url(void *listener) { + fio___io_listen_s *l = (fio___io_listen_s *)listener; + return FIO_BUF_INFO2(l->url, l->url_len); +} + +/** Returns true if the listener protocol has an attached TLS context. */ +SFUNC int fio_io_listener_is_tls(void *listener) { + fio___io_listen_s *l = (fio___io_listen_s *)listener; + return !!l->tls_ctx; +} + +static void fio___io_listen_on_data_task(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + int fd; + fio___io_listen_s *l = (fio___io_listen_s *)fio_io_udata(io); + fio_io_unsuspend(io); + while (FIO_SOCK_FD_ISVALID(fd = fio_sock_accept(fio_io_fd(io), NULL, NULL))) { + FIO_LOG_DDEBUG2("(%d) accepted new connection with fd %d", + fio_io_pid(), + fd); + fio_io_attach_fd(fd, l->protocol, l->udata, l->tls_ctx); + } + fio___io_free2(io); +} +static void fio___io_listen_on_data_task_reschd(void *io_, void *ignr_) { + fio_io_defer(fio___io_listen_on_data_task, io_, ignr_); +} +static void fio___io_listen_on_attach(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + l->queue = (l->queue_for_accept && l->queue_for_accept->q != &FIO___IO.queue) + ? l->queue_for_accept->q + : NULL; + if (l->on_start) + l->on_start(l->protocol, l->udata); + if (l->hide_from_log) + FIO_LOG_DEBUG2("(%d) started listening @ %s", fio_io_pid(), l->url); + else + FIO_LOG_INFO("(%d) started listening @ %s", fio_io_pid(), l->url); +} +static void fio___io_listen_on_shutdown(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + l->queue = fio_io_queue(); +} +static void fio___io_listen_on_data(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + if (l->queue) { + fio_io_suspend(io); + fio_queue_push(l->queue, + fio___io_listen_on_data_task_reschd, + fio___io_dup2(io)); + return; + } + fio___io_listen_on_data_task(fio___io_dup2(io), NULL); +} +static void fio___io_listen_on_close(void *buffer, void *l) { + ((fio___io_listen_s *)l)->io = NULL; + fio___io_listen_free(l); + (void)buffer; +} + +static fio_io_protocol_s FIO___IO_LISTEN_PROTOCOL = { + .on_attach = fio___io_listen_on_attach, + .on_data = fio___io_listen_on_data, + .on_close = fio___io_listen_on_close, + .on_timeout = fio_io_touch, + .on_shutdown = fio___io_listen_on_shutdown, +}; + +FIO_SFUNC void fio___io_listen_attach_task_deferred(void *l_, void *ignr_) { + fio___io_listen_s *l = (fio___io_listen_s *)l_; + l = fio___io_listen_dup(l); + int fd = fio_sock_dup(l->fd); + FIO_ASSERT(fd != -1, "listening socket failed to `dup`"); + FIO_LOG_DEBUG2("(%d) Called dup(%d) to attach %d as a listening socket.", + (int)fio_io_pid(), + l->fd, + fd); + l->io = fio_io_attach_fd(fd, &FIO___IO_LISTEN_PROTOCOL, l, NULL); + (void)ignr_; +} + +FIO_SFUNC void fio___io_listen_attach_task(void *l_) { + /* make sure to run in server thread */ + fio_io_defer(fio___io_listen_attach_task_deferred, l_, NULL); +} + +int fio_io_listen___(void); /* IDE marker */ +/** + * Sets up a network service on a listening socket. + * + * Returns 0 on success or -1 on error. + * + * See the `fio_listen` Macro for details. + */ +SFUNC void *fio_io_listen FIO_NOOP(struct fio_io_listen_args args) { + fio___io_listen_s *l = NULL; + void *built_tls = NULL; + int should_free_tls = !args.tls; + FIO_STR_INFO_TMP_VAR(url_alt, 2048); + if (!args.protocol) { + FIO_LOG_ERROR("fio_io_listen requires a protocol to be assigned."); + return l; + } + if (args.on_root && !fio_io_is_master()) { + FIO_LOG_ERROR("fio_io_listen called with `on_root` by a non-root worker."); + return l; + } + if (!args.url) { + args.url = getenv("ADDRESS"); + if (!args.url) + args.url = "0.0.0.0"; + } + url_alt.len = strlen(args.url); + if (url_alt.len > 2024) { + FIO_LOG_ERROR("binding address / url too long."); + args.url = NULL; + } + fio_url_s url = fio_url_parse(args.url, url_alt.len); + if (url.scheme.buf && + (url.scheme.len > 2 && url.scheme.len < 5 && + (url.scheme.buf[0] | (char)0x20) == 't' && + (url.scheme.buf[1] | (char)0x20) == 'c') && + (url.scheme.buf[2] | (char)0x20) == 'p') + url.scheme = FIO_BUF_INFO0; + if (!url.port.buf && !url.scheme.buf) { + static size_t port_counter = 3000; + size_t port = fio_atomic_add(&port_counter, 1); + if (port == 3000 && getenv("PORT")) { + char *port_env = getenv("PORT"); + port = fio_atol10(&port_env); + if (!port | (port > 65535ULL)) + port = 3000; + } + url_alt.len = 0; + fio_string_write2(&url_alt, + NULL, + FIO_STRING_WRITE_STR2(url.scheme.buf, url.scheme.len), + (url.scheme.len ? FIO_STRING_WRITE_STR2("://", 3) + : FIO_STRING_WRITE_STR2(NULL, 0)), + FIO_STRING_WRITE_STR2(url.host.buf, url.host.len), + FIO_STRING_WRITE_STR2(":", 1), + FIO_STRING_WRITE_NUM(port)); + args.url = url_alt.buf; + url = fio_url_parse(args.url, url_alt.len); + } + + args.tls = fio_io_tls_from_url(args.tls, url); + fio___io_init_protocol_test(args.protocol, !!args.tls); + built_tls = args.protocol->io_functions.build_context(args.tls, 0); + fio_buf_info_s url_buf = FIO_BUF_INFO2((char *)args.url, url_alt.len); + /* remove query details from URL */ + if (url.query.len) + url_buf.len = url.query.buf - (url_buf.buf + 1); + else if (url.target.len) + url_buf.len = url.target.buf - (url_buf.buf + 1); + l = (fio___io_listen_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*l) + url_buf.len + 1, 0); + FIO_ASSERT_ALLOC(l); + FIO_LEAK_COUNTER_ON_ALLOC(fio_io_listen); + *l = (fio___io_listen_s){ + .protocol = args.protocol, + .udata = args.udata, + .tls_ctx = built_tls, + .queue_for_accept = args.queue_for_accept, + .on_start = args.on_start, + .on_stop = args.on_stop, + .owner = FIO___IO.pid, + .url_len = url_buf.len, + .hide_from_log = args.hide_from_log, + }; + FIO_MEMCPY(l->url, url_buf.buf, url_buf.len); + l->url[l->url_len] = 0; + if (should_free_tls) + fio_io_tls_free(args.tls); + + l->fd = fio_sock_open2(l->url, FIO_SOCK_SERVER | FIO_SOCK_TCP); + if (l->fd == -1) { + fio___io_listen_free(l); + return (l = NULL); + } + if (fio_io_is_running()) { + fio_io_defer(fio___io_listen_attach_task_deferred, l, NULL); + } else { + fio_state_callback_add( + (args.on_root ? FIO_CALL_PRE_START : FIO_CALL_ON_START), + fio___io_listen_attach_task, + (void *)l); + } + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___io_listen_free, l); + return l; +} + +/* ***************************************************************************** +Connecting as a Client +***************************************************************************** */ + +typedef struct { + fio_io_protocol_s protocol; + fio_io_protocol_s *upr; + void (*on_failed)(fio_io_protocol_s *protocol, void *udata); + void *udata; + void *tls_ctx; + size_t url_len; + char url[]; +} fio___io_connecting_s; + +FIO_SFUNC void fio___connecting_cleanup(fio___io_connecting_s *c) { + fio___io_func_free_context_caller(c->protocol.io_functions.free_context, + c->tls_ctx); + FIO_MEM_FREE_(c, sizeof(*c) + c->url_len + 1); +} + +FIO_SFUNC void fio___connecting_on_close(void *buffer, void *udata) { + fio___io_connecting_s *c = (fio___io_connecting_s *)udata; + if (c->on_failed) + c->on_failed(c->upr, c->udata); + fio___connecting_cleanup(c); + (void)buffer; +} + +FIO_SFUNC void fio___connecting_on_ready(fio_io_s *io) { + if (!fio_io_is_open(io)) + return; + fio___io_connecting_s *c = (fio___io_connecting_s *)fio_io_udata(io); + FIO_LOG_DEBUG2("(%d) established client connection to %s", + fio_io_pid(), + c->url); + fio_io_udata_set(io, c->udata); + fio_io_protocol_set(io, c->upr); + c->on_failed = NULL; + fio___io_defer_no_wakeup(fio___connecting_on_close, NULL, (void *)c); +} + +void fio_io_connect___(void); /* IDE Marker */ +SFUNC fio_io_s *fio_io_connect FIO_NOOP(fio_io_connect_args_s args) { + int should_free_tls = !args.tls; + if (!args.protocol) + return NULL; + if (!args.url) { + if (args.on_failed) + args.on_failed(args.protocol, args.udata); + return NULL; + } + if (!args.timeout) + args.timeout = 30000; + + size_t url_len = strlen(args.url); + fio_url_s url = fio_url_parse(args.url, url_len); + args.tls = fio_io_tls_from_url(args.tls, url); + fio___io_init_protocol(args.protocol, !!args.tls); + if (url.query.len) + url_len = url.query.buf - (args.url + 1); + else if (url.target.len) + url_len = url.target.buf - (args.url + 1); + fio___io_connecting_s *c = (fio___io_connecting_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*c) + url_len + 1, 0); + FIO_ASSERT_ALLOC(c); + *c = (fio___io_connecting_s){ + .protocol = + { + .on_ready = fio___connecting_on_ready, + .on_close = fio___connecting_on_close, + .io_functions = args.protocol->io_functions, + .timeout = args.timeout, + .buffer_size = args.protocol->buffer_size, + }, + .upr = args.protocol, + .on_failed = args.on_failed, + .udata = args.udata, + .tls_ctx = args.protocol->io_functions.build_context(args.tls, 1), + }; + FIO_MEMCPY(c->url, args.url, url_len); + c->url[url_len] = 0; + fio_io_s *io = fio_io_attach_fd( + fio_sock_open2(c->url, FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), + &c->protocol, + c, + c->tls_ctx); + if (should_free_tls) + fio_io_tls_free(args.tls); + return io; +} +/* ***************************************************************************** +IO Reactor Finish +***************************************************************************** */ +#endif /* FIO_IO */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + OpenSSL Implementation for IO Functions + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(H___FIO_SERVER___H) && \ + (HAVE_OPENSSL || __has_include("openssl/ssl.h")) && \ + !defined(H___FIO_OPENSSL___H) && !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_OPENSSL___H 1 +/* ***************************************************************************** +OpenSSL IO Function Getter +***************************************************************************** */ + +/* Returns the OpenSSL IO functions. */ +SFUNC fio_io_functions_s fio_openssl_io_functions(void); + +/* ***************************************************************************** +OpenSSL Helpers Implementation +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +#include +#include + +FIO_ASSERT_STATIC(OPENSSL_VERSION_MAJOR > 2, "OpenSSL version mismatch"); + +/* ***************************************************************************** +Self-Signed Certificates - TODO: change to ECDSA +***************************************************************************** */ +static EVP_PKEY *fio___openssl_pkey = NULL; +static void fio___openssl_clear_root_key(void *key) { + EVP_PKEY_free(((EVP_PKEY *)key)); + fio___openssl_pkey = NULL; +} + +static void fio___openssl_make_root_key(void) { + static fio_lock_i lock = FIO_LOCK_INIT; + fio_lock(&lock); + if (!fio___openssl_pkey) { + /* create private key, free it at exit */ + FIO_LOG_DEBUG2("calculating a new TLS private key... might take a while."); + fio___openssl_pkey = EVP_RSA_gen(2048); + FIO_ASSERT(fio___openssl_pkey, "OpenSSL failed to create private key."); + fio_state_callback_add(FIO_CALL_AT_EXIT, + fio___openssl_clear_root_key, + fio___openssl_pkey); + } + fio_unlock(&lock); +} + +static X509 *fio_io_tls_create_self_signed(const char *server_name) { + X509 *cert = X509_new(); + static uint32_t counter = 0; + FIO_ASSERT(cert, + "OpenSSL failed to allocate memory for self-signed certificate."); + fio___openssl_make_root_key(); + + /* serial number */ + fio_atomic_add(&counter, 1); + ASN1_INTEGER_set(X509_get_serialNumber(cert), counter); + + /* validity (180 days) */ + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 15552000L); + + /* set (public) key */ + X509_set_pubkey(cert, fio___openssl_pkey); + + /* set identity details */ + X509_NAME *s = X509_get_subject_name(cert); + size_t srv_name_len = FIO_STRLEN(server_name); + FIO_ASSERT(srv_name_len < (size_t)((int)0 - 1), + "fio_io_tls_create_self_signed server_name too long"); + X509_NAME_add_entry_by_txt(s, + "O", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_NAME_add_entry_by_txt(s, + "CN", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_NAME_add_entry_by_txt(s, + "CA", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_set_issuer_name(cert, s); + + /* sign certificate */ + FIO_ASSERT(X509_sign(cert, fio___openssl_pkey, EVP_sha512()), + "OpenSSL failed to signed self-signed certificate"); + return cert; +} + +/* ***************************************************************************** +OpenSSL Context type wrappers +***************************************************************************** */ + +/* Context for all future connections */ +typedef struct { + SSL_CTX *ctx; + fio_io_tls_s *tls; +} fio___openssl_context_s; + +FIO_LEAK_COUNTER_DEF(fio___openssl_context_s) + +/* ***************************************************************************** +OpenSSL Callbacks +***************************************************************************** */ + +FIO_SFUNC int fio___openssl_pem_password_cb(char *buf, + int size, + int rwflag, + void *u) { + const char *password = (const char *)u; + size_t password_len = FIO_STRLEN(password); + if (password_len > (size_t)size) + return -1; + FIO_MEMCPY(buf, password, password_len); + return (int)password_len; + (void)rwflag; +} + +FIO_SFUNC int fio___openssl_alpn_selector_cb(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *tls_) { + fio_io_s *io = (fio_io_s *)SSL_get_ex_data(ssl, 0); + fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_; + + const unsigned char *end = in + inlen; + char buf[256]; + for (;;) { + uint8_t len = in[0]; + FIO_MEMCPY(buf, in + 1, len); + buf[len] = 0; + if (fio_io_tls_alpn_select(ctx->tls, buf, (size_t)len, io)) { + in += len + 1; + if (in < end) + continue; + FIO_LOG_DDEBUG2("(%d) ALPN Failed! No protocol name match for %p", + (int)fio_thread_getpid(), + io); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *out = in + 1; + *outlen = len; + FIO_LOG_DDEBUG2("(%d) TLS ALPN set to: %s for %p", + (int)fio_thread_getpid(), + buf, + io); + return SSL_TLSEXT_ERR_OK; + (void)tls_; + } +} + +/* ***************************************************************************** +Public Context Builder +***************************************************************************** */ + +FIO_SFUNC int fio___openssl_each_cert(struct fio_io_tls_each_s *e, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + fio___openssl_context_s *s = (fio___openssl_context_s *)e->udata; + if (public_cert_file && private_key_file) { /* load certificate */ + SSL_CTX_set_default_passwd_cb(s->ctx, fio___openssl_pem_password_cb); + SSL_CTX_set_default_passwd_cb_userdata(s->ctx, (void *)pk_password); + FIO_LOG_DDEBUG2("loading TLS certificates: %s & %s", + public_cert_file, + private_key_file); + /* Set the key and cert */ + if (SSL_CTX_use_certificate_chain_file(s->ctx, public_cert_file) <= 0) { + ERR_print_errors_fp(stderr); + FIO_ASSERT(0, + "OpenSSL couldn't open PEM file for certificate: %s", + public_cert_file); + } + + if (SSL_CTX_use_PrivateKey_file(s->ctx, + private_key_file, + SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + FIO_ASSERT(0, + "OpenSSL couldn't open PEM file for private key: %s", + private_key_file); + } + SSL_CTX_set_default_passwd_cb(s->ctx, NULL); + SSL_CTX_set_default_passwd_cb_userdata(s->ctx, NULL); + } else { /* self signed */ + if (!server_name || !strlen(server_name)) + server_name = (const char *)"localhost"; + X509 *cert = fio_io_tls_create_self_signed(server_name); + SSL_CTX_use_certificate(s->ctx, cert); + SSL_CTX_use_PrivateKey(s->ctx, fio___openssl_pkey); + } + return 0; +} + +FIO_SFUNC int fio___openssl_each_alpn(struct fio_io_tls_each_s *e, + const char *protocol_name, + void (*on_selected)(fio_io_s *)) { + fio_str_info_s *str = (fio_str_info_s *)e->udata2; + size_t len = FIO_STRLEN(protocol_name); + if (len > 255 || (len + str->len >= str->capa)) { + FIO_LOG_ERROR("ALPN protocol name/list overflow."); + return -1; + } + str->buf[str->len++] = (len & 0xFF); + FIO_MEMCPY(str->buf + str->len, protocol_name, len); + str->len += len; + return 0; + (void)on_selected; +} + +FIO_SFUNC int fio___openssl_each_trust(struct fio_io_tls_each_s *e, + const char *public_cert_file) { + X509_STORE *store = (X509_STORE *)e->udata2; + if (public_cert_file) /* trust specific certificate */ + X509_STORE_load_file(store, public_cert_file); + else { /* trust system's trust */ + const char *path = getenv(X509_get_default_cert_dir_env()); + if (!path) + path = X509_get_default_cert_dir(); + if (path) + X509_STORE_load_path(store, path); + } + return 0; +} + +/** Helper that converts a `fio_io_tls_s` into the implementation's context. */ +FIO_SFUNC void *fio___openssl_build_context(fio_io_tls_s *tls, + uint8_t is_client) { + fio___openssl_context_s *ctx = + (fio___openssl_context_s *)FIO_MEM_REALLOC(NULL, 0, sizeof(*ctx), 0); + FIO_ASSERT_ALLOC(ctx); + FIO_LEAK_COUNTER_ON_ALLOC(fio___openssl_context_s); + *ctx = (fio___openssl_context_s){ + .ctx = SSL_CTX_new((is_client ? TLS_client_method : TLS_server_method)()), + .tls = fio_io_tls_dup(tls), + }; + + SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_clear_mode(ctx->ctx, SSL_MODE_AUTO_RETRY); + + X509_STORE *store = NULL; + if (fio_io_tls_trust_count(tls)) { + SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, NULL); + store = X509_STORE_new(); + SSL_CTX_set_cert_store(ctx->ctx, store); + } else { + SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, NULL); + } + if (!fio_io_tls_cert_count(tls) && !is_client) { + /* add self-signed certificate to context */ + X509 *cert = fio_io_tls_create_self_signed("localhost"); + SSL_CTX_use_certificate(ctx->ctx, cert); + SSL_CTX_use_PrivateKey(ctx->ctx, fio___openssl_pkey); + } + fio_io_tls_each(tls, + .udata = ctx, + .udata2 = store, + .each_cert = fio___openssl_each_cert, + .each_trust = fio___openssl_each_trust); + + if (fio_io_tls_alpn_count(tls)) { + FIO_STR_INFO_TMP_VAR(alpn_list, 1023); + fio_io_tls_each(tls, + .udata = ctx, + .udata2 = &alpn_list, + .each_alpn = fio___openssl_each_alpn); + if (SSL_CTX_set_alpn_protos(ctx->ctx, + (const unsigned char *)alpn_list.buf, + (unsigned int)alpn_list.len)) { + FIO_LOG_ERROR("SSL_CTX_set_alpn_protos failed."); + } else { + SSL_CTX_set_alpn_select_cb(ctx->ctx, fio___openssl_alpn_selector_cb, ctx); + SSL_CTX_set_next_proto_select_cb( + ctx->ctx, + (int (*)(SSL *, + unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *))fio___openssl_alpn_selector_cb, + (void *)ctx); + } + } + return ctx; +} + +/* ***************************************************************************** +IO functions +***************************************************************************** */ + +/** Called to perform a non-blocking `read`, same as the system call. */ +FIO_SFUNC ssize_t fio___openssl_read(int fd, + void *buf, + size_t len, + void *tls_ctx) { + ssize_t r; + SSL *ssl = (SSL *)tls_ctx; + errno = 0; + if (len > INT_MAX) + len = INT_MAX; + r = SSL_read(ssl, buf, (int)len); + if (r > 0) + return r; + if (errno == EWOULDBLOCK || errno == EAGAIN) + return (ssize_t)-1; + + switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ + r = SSL_write_ex(ssl, (void *)&r, 0, (size_t *)&r); /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return (r = -1); + } + (void)fd; +} + +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +FIO_SFUNC int fio___openssl_flush(int fd, void *tls_ctx) { + return 0; + (void)fd, (void)tls_ctx; +#if 0 /* no flushing necessary? */ + int r; + char buf[8] = {0}; + size_t count = 0; + SSL *ssl = (SSL *)tls_ctx; + r = SSL_write_ex(ssl, buf, 0, &count); + if (count) + return 1; + if (r > 0) + return 0; + switch ((r = SSL_get_error(ssl, r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ + SSL_read_ex(ssl, buf, 0, &count); /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return -1; + } +#endif +} + +/** Called to perform a non-blocking `write`, same as the system call. */ +FIO_SFUNC ssize_t fio___openssl_write(int fd, + const void *buf, + size_t len, + void *tls_ctx) { + ssize_t r = -1; + if (!buf || !len || !tls_ctx) + return r; + SSL *ssl = (SSL *)tls_ctx; + errno = 0; + if (len > INT_MAX) + len = INT_MAX; + r = SSL_write(ssl, buf, (int)len); + if (r > 0) + return r; + if (errno == EWOULDBLOCK || errno == EAGAIN) + return -1; + + switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return (r = -1); + } + (void)fd; +} + +/* ***************************************************************************** +Per-Connection Builder +***************************************************************************** */ + +FIO_LEAK_COUNTER_DEF(fio___SSL) + +/** called once the IO was attached and the TLS object was set. */ +FIO_SFUNC void fio___openssl_start(fio_io_s *io) { + fio___openssl_context_s *ctx_parent = (fio___openssl_context_s *)fio_tls(io); + FIO_ASSERT_DEBUG(ctx_parent, "OpenSSL Context missing!"); + + SSL *ssl = SSL_new(ctx_parent->ctx); + FIO_LEAK_COUNTER_ON_ALLOC(fio___SSL); + fio_io_tls_set(io, (void *)ssl); + + /* attach socket */ + FIO_LOG_DDEBUG2("(%d) allocated new TLS context for %p.", + (int)fio_thread_getpid(), + (void *)io); + BIO *bio = BIO_new_socket(fio_fd(io), 0); + SSL_set_bio(ssl, bio, bio); + SSL_set_ex_data(ssl, 0, (void *)io); + if (SSL_is_server(ssl)) + SSL_accept(ssl); + else + SSL_connect(ssl); +} + +/* ***************************************************************************** +Closing Connections +***************************************************************************** */ + +/** Decreases a fio_io_tls_s object's reference count, or frees the object. */ +FIO_SFUNC void fio___openssl_finish(int fd, void *tls_ctx) { + SSL *ssl = (SSL *)tls_ctx; + SSL_shutdown(ssl); + (void)fd; +} + +/* ***************************************************************************** +Per-Connection Cleanup +***************************************************************************** */ + +/** Decreases a fio_io_tls_s object's reference count, or frees the object. */ +FIO_SFUNC void fio___openssl_cleanup(void *tls_ctx) { + SSL *ssl = (SSL *)tls_ctx; + SSL_shutdown(ssl); + FIO_LEAK_COUNTER_ON_FREE(fio___SSL); + SSL_free(ssl); +} + +/* ***************************************************************************** +Context Cleanup +***************************************************************************** */ + +static void fio___openssl_free_context_task(void *tls_ctx, void *ignr_) { + fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_ctx; + FIO_LEAK_COUNTER_ON_FREE(fio___openssl_context_s); + SSL_CTX_free(ctx->ctx); + fio_io_tls_free(ctx->tls); + FIO_MEM_FREE(ctx, sizeof(*ctx)); + (void)ignr_; +} + +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void fio___openssl_free_context(void *tls_ctx) { + fio_srv_defer(fio___openssl_free_context_task, tls_ctx, NULL); +} +/* ***************************************************************************** +IO Functions Structure +***************************************************************************** */ + +/* Returns the OpenSSL IO functions (implementation) */ +SFUNC fio_io_functions_s fio_openssl_io_functions(void) { + return (fio_io_functions_s){ + .build_context = fio___openssl_build_context, + .free_context = fio___openssl_free_context, + .start = fio___openssl_start, + .read = fio___openssl_read, + .write = fio___openssl_write, + .flush = fio___openssl_flush, + .cleanup = fio___openssl_cleanup, + }; +} + +/* Setup OpenSSL as TLS IO default */ +FIO_CONSTRUCTOR(fio___openssl_setup_default) { + static fio_io_functions_s FIO___OPENSSL_IO_FUNCS; + FIO___OPENSSL_IO_FUNCS = fio_openssl_io_functions(); + fio_io_tls_default_io_functions(&FIO___OPENSSL_IO_FUNCS); +#ifdef SIGPIPE + fio_signal_monitor(SIGPIPE, NULL, NULL); /* avoid OpenSSL issue... */ +#endif +} + +/* ***************************************************************************** +OpenSSL Helpers Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* HAVE_OPENSSL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_PUBSUB /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Pub/Sub Services for IPC / Server applications + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_PUBSUB) && !defined(H___FIO_PUBSUB___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_PUBSUB___H + +/* ***************************************************************************** +Pub/Sub - message format +***************************************************************************** */ + +/** Message structure, as received by the `on_message` subscription callback. */ +struct fio_msg_s { + /** A connection (if any) to which the subscription belongs. */ + fio_io_s *io; + /** The `udata` argument associated with the subscription. */ + void *udata; + /** Message ID. */ + uint64_t id; + /** Milliseconds since epoch. */ + uint64_t published; + /** + * A channel name, allowing for pub/sub patterns. + * + * NOTE: this is a shared copy - do NOT mutate the channel name string. + */ + fio_buf_info_s channel; + /** + * The actual message. + * + * NOTE: this is a shared copy - do NOT mutate the message payload string. + **/ + fio_buf_info_s message; + /** Channel name namespace. Negative values are reserved. */ + int16_t filter; + /** flag indicating if the message is JSON data or binary/text. */ + uint8_t is_json; +}; + +/* ***************************************************************************** +Pub/Sub - Subscribe / Unsubscribe +***************************************************************************** */ + +/** Possible arguments for the fio_subscribe method. */ +typedef struct { + /** + * The subscription owner - if none, the subscription is owned by the system. + * + * Note: + * + * Both the system and the `io` objects each manage channel listing + * which allows only a single subscription to the same channel. + * + * This means a single subscription per channel per IO and a single + * subscription per channel for the global system unless managing the + * subscription handle manually. + */ + fio_io_s *io; + /** + * A named `channel` to which the message was sent. + * + * Subscriptions require a match by both channel name and namespace filter. + */ + fio_buf_info_s channel; + /** + * The callback to be called for each message forwarded to the subscription. + */ + void (*on_message)(fio_msg_s *msg); + /** An optional callback for when a subscription is canceled. */ + void (*on_unsubscribe)(void *udata); + /** The opaque udata value is ignored and made available to the callbacks. */ + void *udata; + /** Replay cached messages (if any) since supplied time in milliseconds. */ + uint64_t replay_since; + /** + * OPTIONAL: subscription handle return value - should be NULL when using + * automatic memory management with the IO or global environment. + * + * When set, the `io` pointer will be ignored and the subscription object + * handle will be written to the `subscription_handle_ptr` which MUST be + * used when unsubscribing. + * + * NOTE: this could cause subscriptions and memory leaks unless properly + * handled. + */ + uintptr_t *subscription_handle_ptr; + /** + * A numerical namespace `filter` subscribers need to match. + * + * Negative values are reserved for facil.io framework extensions. + * + * Filer channels are bound to the processes and workers, they are NOT + * forwarded to engines and can be used for inter process communication (IPC). + */ + int16_t filter; + /** If set, pattern matching will be used (name is a pattern). */ + uint8_t is_pattern; + /** If set, subscription will be limited to the root / master process. */ + uint8_t master_only; +} fio_subscribe_args_s; + +/** + * Subscribes to a channel / filter pair. + * + * The on_unsubscribe callback will be called on failure. + */ +SFUNC void fio_subscribe(fio_subscribe_args_s args); + +/** + * Subscribes to a channel / filter pair. + * + * See `fio_subscribe_args_s` for details. + */ +#define fio_subscribe(...) fio_subscribe((fio_subscribe_args_s){__VA_ARGS__}) + +/** + * Cancels an existing subscriptions. + * + * Accepts the same arguments as `fio_subscribe`, except the `udata` and + * callback details are ignored (no need to provide `udata` or callback + * details). + * + * If a `subscription_handle_ptr` was provided it should contain the value of + * the subscription handle returned. + * + * Returns -1 if the subscription could not be found. Otherwise returns 0. + */ +SFUNC int fio_unsubscribe(fio_subscribe_args_s args); + +/** + * Cancels an existing subscriptions. + * + * Accepts the same arguments as `fio_subscribe`, except the `udata` and + * callback details are ignored (no need to provide `udata` or callback + * details). + * + * Returns -1 if the subscription could not be found. Otherwise returns 0. + */ +#define fio_unsubscribe(...) \ + fio_unsubscribe((fio_subscribe_args_s){__VA_ARGS__}) + +/* A callback for IO subscriptions - sends raw message data. */ +FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg); + +/* ***************************************************************************** +Pub/Sub - Publish +***************************************************************************** */ + +/** A pub/sub engine data structure. See details later on. */ +typedef struct fio_pubsub_engine_s fio_pubsub_engine_s; + +/** Publishing and on_message callback arguments. */ +typedef struct fio_publish_args_s { + /** The pub/sub engine that should be used to forward this message. */ + fio_pubsub_engine_s const *engine; + /** If `from` is specified, it will be skipped (won't receive message) + * UNLESS a non-native `engine` is specified. */ + fio_io_s *from; + /** Message ID (if missing, a random ID will be generated). */ + uint64_t id; + /** Milliseconds since epoch (if missing, defaults to "now"). */ + uint64_t published; + /** The target named channel. */ + fio_buf_info_s channel; + /** The message body / content. */ + fio_buf_info_s message; + /** A numeral namespace for channel names. Negative values are reserved. */ + int16_t filter; + /** A flag indicating if the message is JSON data or not. */ + uint8_t is_json; +} fio_publish_args_s; + +/** + * Publishes a message to the relevant subscribers (if any). + * + * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by + * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the + * calling process). + * + * To limit the message only to other processes (exclude the calling process), + * use the `FIO_PUBSUB_SIBLINGS` engine. + * + * To limit the message only to the calling process, use the + * `FIO_PUBSUB_PROCESS` engine. + * + * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` + * engine. + */ +SFUNC void fio_publish(fio_publish_args_s args); +/** + * Publishes a message to the relevant subscribers (if any). + * + * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by + * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the + * calling process). + * + * To limit the message only to other processes (exclude the calling process), + * use the `FIO_PUBSUB_SIBLINGS` engine. + * + * To limit the message only to the calling process, use the + * `FIO_PUBSUB_PROCESS` engine. + * + * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` + * engine. + */ +#define fio_publish(...) fio_publish((fio_publish_args_s){__VA_ARGS__}) + +/** + * Defers the current callback, so it will be called again for the message. + * + * After calling this function, the `msg` object must NOT be accessed again. + */ +SFUNC void fio_pubsub_message_defer(fio_msg_s *msg); + +/* ***************************************************************************** +Pub/Sub - History and Event Replay - TODO!!! +***************************************************************************** */ + +/** Sets the maximum number of messages to be stored in the history store. */ +// SFUNC void fio_pubsub_store_limit(size_t messages); + +/* ***************************************************************************** +Pub/Sub - defaults and builtin pub/sub engines +***************************************************************************** */ + +/** Flag bits for internal usage (message exchange network format). */ +typedef enum { + /* pub/sub messages */ + FIO___PUBSUB_JSON = 1, + FIO___PUBSUB_PROCESS = 2, + FIO___PUBSUB_ROOT = 4, + FIO___PUBSUB_SIBLINGS = 8, + FIO___PUBSUB_WORKERS = (8 | 2), + FIO___PUBSUB_LOCAL = (8 | 4 | 2), + FIO___PUBSUB_REMOTE = 16, + FIO___PUBSUB_CLUSTER = (16 | 8 | 4 | 2), + FIO___PUBSUB_REPLAY = 32, /* history replay message */ + + /* internal messages */ + FIO___PUBSUB_SPECIAL = 128, + FIO___PUBSUB_SUB = (128 | 1), + FIO___PUBSUB_UNSUB = (128 | 2), + FIO___PUBSUB_IDENTIFY = (128 | 4), /* identify remote connection */ + FIO___PUBSUB_FORWARDER = (128 | 8), /* forward to external engine */ + FIO___PUBSUB_PING = (128 | 16), + + FIO___PUBSUB_HISTORY_START = (128 | 32), + FIO___PUBSUB_HISTORY_END = (128 | 64), +} fio___pubsub_msg_flags_e; + +/** Used to publish the message exclusively to the root / master process. */ +#define FIO_PUBSUB_ROOT ((fio_pubsub_engine_s *)FIO___PUBSUB_ROOT) +/** Used to publish the message only within the current process. */ +#define FIO_PUBSUB_PROCESS ((fio_pubsub_engine_s *)FIO___PUBSUB_PROCESS) +/** Used to publish the message except within the current process. */ +#define FIO_PUBSUB_SIBLINGS ((fio_pubsub_engine_s *)FIO___PUBSUB_SIBLINGS) +/** Used to publish the message for this process, its siblings and root. */ +#define FIO_PUBSUB_LOCAL ((fio_pubsub_engine_s *)FIO___PUBSUB_LOCAL) +/** Used to publish the message to any possible publishers. */ +#define FIO_PUBSUB_CLUSTER ((fio_pubsub_engine_s *)FIO___PUBSUB_CLUSTER) + +#if defined(FIO_EXTERN) /* static definitions can't be easily repeated. */ +/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ +SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT; + +/** + * The pattern matching callback used for pattern matching. + * + * Returns 1 on a match or 0 if the string does not match the pattern. + * + * By default, the value is set to `fio_glob_match` (see facil.io's C STL). + */ +SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s pattern, + fio_str_info_s channel); +#endif + +/* ***************************************************************************** +Message metadata (advance usage API) +***************************************************************************** */ + +/** + * The number of different metadata callbacks that can be attached. + * + * Effects performance. + * + * The default value should be enough for the following metadata objects: + * - WebSocket server headers. + * - WebSocket client (header + masked message copy). + * - EventSource (SSE) encoded named channel and message. + */ +#ifndef FIO___PUBSUB_METADATA_STORE_LIMIT +#define FIO___PUBSUB_METADATA_STORE_LIMIT 4 +#endif + +/** Pub/Sub Metadata callback type. */ +typedef void *(*fio_msg_metadata_fn)(fio_msg_s *); + +/** + * It's possible to attach metadata to facil.io pub/sub messages before they are + * published. + * + * This allows, for example, messages to be encoded as network packets for + * outgoing protocols (i.e., encoding for WebSocket transmissions), improving + * performance in large network based broadcasting. + * + * Up to `FIO___PUBSUB_METADATA_STORE_LIMIT` metadata callbacks can be attached. + * + * The callback should return a `void *` pointer. + * + * To remove a callback, call `fio_message_metadata_remove` with the returned + * value. + * + * The cluster messaging system allows some messages to be flagged as JSON and + * this flag is available to the metadata callback. + * + * Returns zero (0) on success or -1 on failure. + * + * Multiple `fio_message_metadata_add` calls increase a reference count and + * should be matched by the same number of `fio_message_metadata_remove`. + */ +SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, + void (*cleanup)(void *)); + +/** + * Removed the metadata callback. + * + * Removal might be delayed if live metatdata exists. + */ +SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func); + +/** Finds the message's metadata, returning the data or NULL. */ +SFUNC void *fio_message_metadata(fio_msg_s *msg, + fio_msg_metadata_fn metadata_func); + +/* ***************************************************************************** +Pub/Sub Middleware and Extensions ("Engines") +***************************************************************************** */ + +/** + * facil.io can be linked with external Pub/Sub services using "engines". + * + * Engines MUST provide the listed function pointers and should be attached + * using the `fio_pubsub_attach` function. + * + * Engines that were connected / attached using `fio_pubsub_attach` MUST + * disconnect / detach, before being destroyed, by using the `fio_pubsub_detach` + * function. + * + * When an engine received a message to publish, it should call the + * `fio_publish` function with the engine to which the message is forwarded. + * i.e.: + * + * fio_publish( + * .engine = FIO_PUBSUB_LOCAL, + * .channel = channel_name, + * .message = msg_body); + * + * IMPORTANT: The callbacks will be called by the main IO thread, so they should + * never block. Long tasks should copy the data and scheduling an external task + * (i.e., using `fio_io_defer`). + */ +struct fio_pubsub_engine_s { + /** Called after the engine was detached, may be used for cleanup. */ + void (*detached)(const fio_pubsub_engine_s *eng); + /** Subscribes to a channel. Called ONLY in the Root (master) process. */ + void (*subscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Subscribes to a pattern. Called ONLY in the Root (master) process. */ + void (*psubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Unsubscribes to a channel. Called ONLY in the Root (master) process. */ + void (*unsubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Unsubscribe to a pattern. Called ONLY in the Root (master) process. */ + void (*punsubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Publishes a message through the engine. Called by any worker / thread. */ + void (*publish)(const fio_pubsub_engine_s *eng, fio_msg_s *msg); +}; + +/** + * Attaches an engine, so it's callback can be called by facil.io. + * + * The `(p)subscribe` callback will be called for every existing channel. + * + * This can be called multiple times resulting in re-running the `(p)subscribe` + * callbacks. + * + * NOTE: engines are automatically detached from child processes but can be + * safely used even so - messages are always forwarded to the engine attached to + * the root (master) process. + * + * NOTE: engines should publish events to `FIO_PUBSUB_LOCAL`. + */ +SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine); + +/** Schedules an engine for Detachment, so it could be safely destroyed. */ +SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine); + +/* ***************************************************************************** +Pub/Sub Clustering and Security +***************************************************************************** */ + +/** Sets the current IPC socket address (can't be changed while running). */ +SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len); + +/** Returns a pointer to the current IPC socket address. */ +SFUNC const char *fio_pubsub_ipc_url(void); + +/** + * Sets a (possibly shared) secret for securing pub/sub communication. + * + * If `secret` is `NULL`, the environment variable `"SECRET"` will be used or. + * + * If secret is never set, a random secret will be generated. + * + * NOTE: secrets produce a SHA-512 Hash that is used to produce 256 bit keys. + */ +SFUNC void fio_pubsub_secret_set(char *secret, size_t len); + +/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ +SFUNC void fio_pubsub_broadcast_on_port(int16_t port); + +/* ***************************************************************************** + + + +Pub/Sub Implementation + + + +The implementation has a big number of interconnected modules: +- Distribution Channels (`fio_channel_s` and `FIO___PUBSUB_POSTOFFICE`) +- Subscriptions (`fio_subscription_s`) +- Metadata Management. +- External Distribution Engines (`fio_pubsub_engine_s`) +- Message format and their network exchange protocols (`fio___pubsub_message_s`) + +Message wire format (as 64 bit numerals in little endien encoding): +[0] Message ID +[1] Publication time (milliseconds since epoch) +[2] 16 bit filter | 16 bit channel len | 24 bit message len | 8 bit flags +| --- encryption starts --- | +| X bytes - (channel name, + 1 NUL terminator) | +| Y bytes - (message data, + 1 NUL terminator) | +| --- encryption ends --- | +| 16 bytes - (optional) message MAC | +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +#undef FIO___PUBSUB_MESSAGE_HEADER +#define FIO___PUBSUB_MESSAGE_HEADER 24 +/* header + 2 NUL bytes (message + channel) + 16 byte MAC */ +#undef FIO___PUBSUB_MESSAGE_OVERHEAD +#define FIO___PUBSUB_MESSAGE_OVERHEAD (FIO___PUBSUB_MESSAGE_HEADER + 18) + +/* ***************************************************************************** +Pub/Sub - defaults and builtin pub/sub engines +***************************************************************************** */ + +/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ +SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; + +/** + * The pattern matching callback used for pattern matching. + * + * Returns 1 on a match or 0 if the string does not match the pattern. + * + * By default, the value is set to `fio_glob_match` (see facil.io's C STL). + */ +SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s, + fio_str_info_s) = fio_glob_match; + +/* a mock callback for subscriptions */ +FIO_SFUNC void fio___subscription_mock_cb(fio_msg_s *msg) { (void)msg; } + +/* A callback for IO subscriptions. */ +FIO_SFUNC void fio___subscription_call_protocol(fio_msg_s *msg) { + if (!msg->io) + return; + fio_io_protocol_s *p = fio_io_protocol(msg->io); + FIO_ASSERT_DEBUG(p, "every IO object should have a protocol, always"); + p->on_pubsub(msg); +} + +#ifndef FIO___PUBSUB_CLUSTER_BACKLOG +#define FIO___PUBSUB_CLUSTER_BACKLOG (1UL << 12) +#endif + +/* ***************************************************************************** +PostOffice Distribution types - Channel and Subscription Core Types +***************************************************************************** */ + +/** The Distribution Channel: manages subscriptions to named channels. */ +typedef struct fio_channel_s { + FIO_LIST_HEAD subscriptions; + FIO_LIST_HEAD history; + uint32_t name_len; + int16_t filter; + uint8_t is_pattern; + char name[]; +} fio_channel_s; + +/** The Channel Map: maps named channels. */ +FIO_SFUNC void fio___channel_on_create(fio_channel_s *ch); +FIO_SFUNC void fio___channel_on_destroy(fio_channel_s *ch); + +/** + * Reference counting: `fio_channel_dup(ch)` / `fio_channel_free(ch)` + */ +#define FIO_REF_NAME fio_channel +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_DESTROY(ch) fio___channel_on_destroy(&(ch)) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/** The Subscription: contains subscriber data. */ +typedef struct fio_subscription_s { + FIO_LIST_NODE node; + FIO_LIST_NODE history; + FIO_LIST_NODE history_active; + uint64_t replay_since; + fio_io_s *io; + fio_channel_s *channel; + void (*on_message)(fio_msg_s *msg); + void (*on_unsubscribe)(void *udata); + void *udata; +} fio_subscription_s; + +/** + * Reference counting: `fio_subscription_dup(sb)` / `fio_subscription_free(sb)` + */ +FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *sub); +#define FIO_REF_NAME fio_subscription +#define FIO_REF_DESTROY(obj) fio___pubsub_subscription_on_destroy(&(obj)) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/** The Message Container */ +typedef struct { + fio_msg_s data; + void *metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; + uint8_t metadata_is_initialized; /* to compact this we need to change all? */ + char buf[]; +} fio___pubsub_message_s; + +/* returns the internal message object. */ +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg); + +/** Callback called when a message is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m); + +/* Message reference counting */ +#define FIO_REF_NAME fio___pubsub_message +#define FIO_REF_DESTROY(obj) fio___pubsub_message_on_destroy(&(obj)) +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +typedef struct { + size_t len; + uint64_t uuid[2]; + fio___pubsub_message_s *msg; + char buf[]; +} fio___pubsub_message_parser_s; + +FIO_LEAK_COUNTER_DEF(fio___pubsub_message_parser_s) + +FIO_IFUNC fio___pubsub_message_parser_s *fio___pubsub_message_parser( + fio_io_s *io) { + return io ? (fio___pubsub_message_parser_s *)fio_io_buffer(io) : NULL; +} + +FIO_SFUNC void fio___pubsub_message_parser_init( + fio___pubsub_message_parser_s *p) { + FIO_LEAK_COUNTER_ON_ALLOC(fio___pubsub_message_parser_s); + *p = (fio___pubsub_message_parser_s){0}; +} + +FIO_SFUNC void fio___pubsub_message_parser_destroy( + fio___pubsub_message_parser_s *p) { + fio___pubsub_message_free(p->msg); + FIO_LEAK_COUNTER_ON_FREE(fio___pubsub_message_parser_s); +} + +/* ***************************************************************************** +PostOffice Distribution types - The Distribution Channel Map +***************************************************************************** */ + +#define FIO___PUBSUB_CHANNEL_ENCODE_CAPA(filter_, is_pattern_) \ + (((size_t)(is_pattern_) << 16) | (size_t)(uint16_t)(filter_)) + +#define FIO___PUBSUB_CHANNEL2STR(ch) \ + FIO_STR_INFO3(ch->name, \ + ch->name_len, \ + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(ch->filter, ch->is_pattern)) + +FIO_IFUNC int fio___channel_cmp(fio_channel_s *ch, fio_str_info_s s) { + fio_str_info_s c = FIO___PUBSUB_CHANNEL2STR(ch); + return FIO_STR_INFO_IS_EQ(c, s); +} + +FIO_IFUNC fio_channel_s *fio___channel_new_for_map(fio_str_info_s s) { + fio_channel_s *ch = fio_channel_new(s.len + 1); + FIO_ASSERT_ALLOC(ch); + *ch = (fio_channel_s){ + .subscriptions = FIO_LIST_INIT(ch->subscriptions), + .history = FIO_LIST_INIT(ch->history), + .name_len = (uint32_t)s.len, + .filter = (int16_t)(s.capa & 0xFFFFUL), + .is_pattern = (uint8_t)(s.capa >> 16), + }; + FIO_MEMCPY(ch->name, s.buf, s.len); + ch->name[s.len] = 0; + fio___channel_on_create(ch); + return ch; +} + +#define FIO_MAP_NAME fio___channel_map +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL fio_channel_s * +#define FIO_MAP_KEY_FROM_INTERNAL(k_) FIO___PUBSUB_CHANNEL2STR(k_) +#define FIO_MAP_KEY_COPY(dest, src) ((dest) = fio___channel_new_for_map((src))) +#define FIO_MAP_KEY_CMP(a, b) fio___channel_cmp((a), (b)) +#define FIO_MAP_HASH_FN(str) fio_risky_hash(str.buf, str.len, str.capa) +#define FIO_MAP_KEY_DESTROY(key) fio_channel_free((key)) +#define FIO_MAP_KEY_DISCARD(key) +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Pub/Sub Subscription destruction +***************************************************************************** */ + +/* calls the on_unsubscribe callback. */ +FIO_SFUNC void fio___pubsub_subscription_on_destroy__task(void *fnp, + void *udata) { + union { + void *p; + void (*fn)(void *udata); + } u = {.p = fnp}; + u.fn(udata); +} + +FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *s) { + if (s->on_unsubscribe) { + union { + void *p; + void (*fn)(void *udata); + } u = {.fn = s->on_unsubscribe}; + fio_queue_push(fio_io_queue(), + fio___pubsub_subscription_on_destroy__task, + u.p, + s->udata); + } +} +/* ***************************************************************************** +Pub/Sub Subscription map (for mapping Master only subscriptions) +***************************************************************************** */ + +/** Performs Housekeeping and defers the on_unsubscribe callback. */ +FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s); + +/* define a helper map to manage master only subscription. */ +#define FIO_MAP_KEY_KSTR +#define FIO_MAP_NAME fio___postoffice_msmap +#define FIO_MAP_VALUE fio_subscription_s * +#define FIO_MAP_VALUE_DESTROY(s) fio___pubsub_subscription_unsubscribe(s) +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Pub/Sub Remote Connection Uniqueness +***************************************************************************** */ + +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_broadcast_connected +#define FIO_MAP_KEY uint64_t +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Pub/Sub Engine Map +***************************************************************************** */ + +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_engines +#define FIO_MAP_KEY fio_pubsub_engine_s * +#define FIO_MAP_HASH_FN(e) fio_risky_ptr(e) +#define FIO_MAP_RECALC_HASH 1 +#define FIO_MAP_KEY_DESTROY(e) \ + do { \ + e->detached(e); \ + e = NULL; \ + } while (0) +#define FIO_MAP_KEY_DISCARD(e) +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Message Uniqueness Map for filtering remote connection broadcasts +***************************************************************************** */ + +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_message_map +#define FIO_MAP_KEY fio___pubsub_message_s * +#define FIO_MAP_KEY_COPY(d_, e_) (d_ = fio___pubsub_message_dup(e_)) +#define FIO_MAP_KEY_CMP(a, b) \ + (a->data.id == b->data.id && a->data.published == b->data.published) +#define FIO_MAP_KEY_DESTROY(e) fio___pubsub_message_free(e) +#define FIO_MAP_HASH_FN(m) fio_risky_num(m->data.id, m->data.published) +#define FIO_MAP_RECALC_HASH 1 +#define FIO_MAP_LRU FIO___PUBSUB_CLUSTER_BACKLOG +#define FIO_MAP_KEY_DISCARD(e) +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Pub/Sub Post Office State +***************************************************************************** */ +#ifndef FIO___IPC_LEN +#define FIO___IPC_LEN 256 +#endif + +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_close(void *buffer, void *udata); +FIO_SFUNC void fio___pubsub_protocol_on_timeout(fio_io_s *io); + +static struct FIO___PUBSUB_POSTOFFICE { + fio_u128 uuid; + fio_u512 secret; + fio___channel_map_s channels; + fio___channel_map_s patterns; + struct { + uint8_t publish; + uint8_t local; + uint8_t remote; + } filter; + uint8_t secret_is_random; + FIO___LOCK_TYPE lock; + fio___pubsub_engines_s engines; + FIO_LIST_NODE history_active; + FIO_LIST_NODE history_waiting; + fio___postoffice_msmap_s master_subscriptions; + fio___postoffice_msmap_s global_subscriptions; + fio___pubsub_broadcast_connected_s remote_uuids; + fio___pubsub_message_map_s remote_messages; + fio___pubsub_message_map_s history_messages; + struct { + fio_io_protocol_s ipc; + fio_io_protocol_s remote; + } protocol; + fio_io_s *broadcaster; + struct { + fio_msg_metadata_fn build; + void (*cleanup)(void *); + size_t ref; + } metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; + char ipc_url[FIO___IPC_LEN]; +} FIO___PUBSUB_POSTOFFICE = { + .filter = + { + .publish = (FIO___PUBSUB_PROCESS | FIO___PUBSUB_ROOT), + .local = (FIO___PUBSUB_SIBLINGS), + .remote = FIO___PUBSUB_REMOTE, + }, + .lock = FIO___LOCK_INIT, + .protocol = + { + .ipc = + { + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_master, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + .buffer_size = sizeof(fio___pubsub_message_parser_s) + + FIO___PUBSUB_MESSAGE_OVERHEAD, + }, + .remote = + { + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_remote, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio___pubsub_protocol_on_timeout, + .buffer_size = sizeof(fio___pubsub_message_parser_s) + + FIO___PUBSUB_MESSAGE_OVERHEAD, + }, + }, +}; + +/** Returns the secret key for a message with stated `rndm` value. */ +FIO_IFUNC const void *fio___pubsub_secret_key(uint64_t rndm) { + return (void *)&FIO___PUBSUB_POSTOFFICE.secret.u8[rndm & 15]; +} + +/* ***************************************************************************** +PostOffice Helpers +***************************************************************************** */ + +/** Sets the current IPC socket address (shouldn't be changed while running). */ +SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len) { + if (fio_io_is_running() || len >= FIO___IPC_LEN) + return -1; + fio_str_info_s url = + FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); + fio_string_write2(&url, NULL, FIO_STRING_WRITE_STR2(str, len)); + return 0; +} +/** Returns the current IPC socket address (shouldn't be changed). */ +SFUNC const char *fio_pubsub_ipc_url(void) { + return FIO___PUBSUB_POSTOFFICE.ipc_url; +} + +/** Sets a (possibly shared) secret for securing pub/sub communication. */ +SFUNC void fio_pubsub_secret_set(char *str, size_t len) { + FIO___PUBSUB_POSTOFFICE.secret_is_random = 0; + uint64_t fallback_secret = 0; + if (!str || !len) { + if ((str = getenv("SECRET"))) { + const char *secret_length = getenv("SECRET_LENGTH"); + len = secret_length ? fio_atol((char **)&secret_length) : 0; + if (!len) + len = strlen(str); + } else { + fallback_secret = fio_rand64(); + str = (char *)&fallback_secret; + len = sizeof(fallback_secret); + FIO___PUBSUB_POSTOFFICE.secret_is_random = 1; + } + } + FIO___PUBSUB_POSTOFFICE.secret = fio_sha512(str, len); +} + +/* ***************************************************************************** +Postoffice History Control +***************************************************************************** */ + +FIO_SFUNC void fio___pubub_on_history_start(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + if (!FIO_LIST_IS_EMPTY(&FIO___PUBSUB_POSTOFFICE.history_active)) + return; + FIO_LIST_EACH(fio_subscription_s, + history_active, + &FIO___PUBSUB_POSTOFFICE.history_active, + s) { + FIO_LIST_REMOVE(&s->history); + FIO_LIST_REMOVE(&s->history_active); + FIO_LIST_PUSH(&s->channel->history, &s->history); + FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_active, &s->history_active); + } +} + +FIO_SFUNC void fio___pubub_on_history_end(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + FIO_LIST_EACH(fio_subscription_s, + history_active, + &FIO___PUBSUB_POSTOFFICE.history_active, + s) { + FIO_LIST_REMOVE(&s->history); + FIO_LIST_REMOVE(&s->history_active); + } +} + +/* ***************************************************************************** +Postoffice Metadata Control +***************************************************************************** */ + +/* Returns zero (0) on success or -1 on failure. */ +SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, + void (*cleanup)(void *)) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) + return 0; + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* insert if available */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + continue; + } + FIO___PUBSUB_POSTOFFICE.metadata[i].build = metadata_func; + FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup = cleanup; + return 0; + } + return -1; +} + +/** + * Removed the metadata callback. + * + * Removal might be delayed if live metatdata + * exists. + */ +SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } +} + +/** Finds the message's metadata, returning the data or NULL. */ +SFUNC void *fio_message_metadata(fio_msg_s *msg, + fio_msg_metadata_fn metadata_func) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (FIO___PUBSUB_POSTOFFICE.metadata[i].ref && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { + return fio___pubsub_msg2internal(msg)->metadata[i]; + } + } + return NULL; +} + +/* ***************************************************************************** +Listening to Local Connections (IPC) +***************************************************************************** */ + +#if defined(DEBUG) +#define FIO___PUBSUB_HIDE_FROM_LOG 0 +#else +#define FIO___PUBSUB_HIDE_FROM_LOG 1 +#endif +/** Starts listening to IPC connections on a local socket. */ +FIO_IFUNC void fio___pubsub_ipc_listen(void *ignr_) { + (void)ignr_; + if (fio_io_is_worker()) { + FIO_LOG_DEBUG2("(%d) pub/sub IPC socket skipped - no workers are spawned.", + fio_io_pid()); + return; + } + FIO_ASSERT(fio_io_listen(.url = FIO___PUBSUB_POSTOFFICE.ipc_url, + .protocol = &FIO___PUBSUB_POSTOFFICE.protocol.ipc, + .on_root = 1, + .hide_from_log = FIO___PUBSUB_HIDE_FROM_LOG), + "(%d) pub/sub couldn't open a socket for IPC\n\t\t%s", + fio_io_pid(), + FIO___PUBSUB_POSTOFFICE.ipc_url); +} +#undef FIO___PUBSUB_HIDE_FROM_LOG + +/* ***************************************************************************** +Postoffice Constructor / Destructor +***************************************************************************** */ + +/* listens for IPC connections. */ +FIO_SFUNC void fio___pubsub_ipc_listen(void *); +/* protocol functions. */ +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_close(void *buffer, void *udata); + +FIO_SFUNC void fio___pubsub_at_exit(void *ignr_) { + (void)ignr_; + fio_queue_perform_all(fio_io_queue()); + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.global_subscriptions); + fio___pubsub_broadcast_connected_destroy( + &FIO___PUBSUB_POSTOFFICE.remote_uuids); + fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.remote_messages); + fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.history_messages); + fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); + FIO___LOCK_DESTROY(FIO___PUBSUB_POSTOFFICE.lock); + fio_queue_perform_all(fio_io_queue()); +} + +/** Callback called by the letter protocol entering a child processes. */ +FIO_SFUNC void fio___pubsub_on_enter_child(void *ignr_) { + (void)ignr_; + FIO___PUBSUB_POSTOFFICE.protocol.ipc.on_data = + fio___pubsub_protocol_on_data_worker; + + FIO___PUBSUB_POSTOFFICE.filter.publish = FIO___PUBSUB_PROCESS; + FIO___PUBSUB_POSTOFFICE.filter.local = + (FIO___PUBSUB_SIBLINGS | FIO___PUBSUB_ROOT); + FIO___PUBSUB_POSTOFFICE.filter.remote = 0; + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); + fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); + if (!fio_io_attach_fd(fio_sock_open2(FIO___PUBSUB_POSTOFFICE.ipc_url, + FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), + &FIO___PUBSUB_POSTOFFICE.protocol.ipc, + NULL, + NULL)) { + FIO_LOG_FATAL("(%d) couldn't connect to pub/sub socket @ %s", + fio_io_pid(), + FIO___PUBSUB_POSTOFFICE.ipc_url); + fio_thread_kill(fio_io_root_pid(), SIGINT); + FIO_ASSERT(0, "fatal error encountered"); + } +} + +FIO_CONSTRUCTOR(fio_postoffice_init) { + FIO___PUBSUB_POSTOFFICE.engines = (fio___pubsub_engines_s)FIO_MAP_INIT; + fio_pubsub_secret_set(NULL, 0); /* allocate a random secret */ + for (size_t i = 0; i < sizeof(FIO___PUBSUB_POSTOFFICE.uuid) / 8; ++i) + FIO___PUBSUB_POSTOFFICE.uuid.u64[i] = fio_rand64(); + fio_str_info_s url = + FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); + + fio_string_write2(&url, + NULL, + FIO_STRING_WRITE_STR1((char *)"priv://facil_io_tmp_"), + FIO_STRING_WRITE_HEX(fio_rand64()), + FIO_STRING_WRITE_STR1((char *)".sock")); + fio_state_callback_add(FIO_CALL_PRE_START, fio___pubsub_ipc_listen, NULL); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___pubsub_on_enter_child, NULL); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___pubsub_at_exit, NULL); + /* TODO!!! */ + FIO___PUBSUB_POSTOFFICE.protocol.ipc = (fio_io_protocol_s){ + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_master, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + .buffer_size = + sizeof(fio___pubsub_message_parser_s) + FIO___PUBSUB_MESSAGE_OVERHEAD, + }; + FIO___PUBSUB_POSTOFFICE.protocol.remote = (fio_io_protocol_s){ + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_remote, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + .buffer_size = + sizeof(fio___pubsub_message_parser_s) + FIO___PUBSUB_MESSAGE_OVERHEAD, + }; +} + +/* ***************************************************************************** +Subscription Setup +***************************************************************************** */ + +/** Completes the subscription request. */ +FIO_IFUNC void fio___pubsub_subscribe_task(void *sub_, void *ignr_) { + fio_subscription_s *sub = (fio_subscription_s *)sub_; + union { + FIO_LIST_HEAD *ls; + fio_str_info_s *str; + } uptr = {.ls = &sub->node}; + const fio_str_info_s ch_name = *uptr.str; + fio_channel_s **ch_ptr = + fio___channel_map_node2key_ptr(fio___channel_map_set_ptr( + &FIO___PUBSUB_POSTOFFICE.channels + (ch_name.capa >> 16), + ch_name)); + fio_bstr_free(ch_name.buf); + sub->node = FIO_LIST_INIT(sub->node); + sub->history = FIO_LIST_INIT(sub->history); + sub->history_active = FIO_LIST_INIT(sub->history_active); + if (FIO_UNLIKELY(!ch_ptr)) + goto no_channel; + sub->channel = ch_ptr[0]; + FIO_LIST_PUSH(&(ch_ptr[0]->subscriptions), &sub->node); + if (sub->replay_since) { + FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_waiting, &sub->history); + /* TODO: publish history request event to the cluster. */ + } + return; +no_channel: + fio___pubsub_subscription_unsubscribe(sub); + (void)ignr_; +} + +/** Unsubscribes a node and destroys the channel if no more subscribers. */ +FIO_IFUNC void fio___pubsub_unsubscribe_task(void *sub_, void *ignr_) { + fio_subscription_s *sub = (fio_subscription_s *)sub_; + fio_channel_s *ch = sub->channel; + fio___channel_map_s *map; + FIO_LIST_REMOVE(&sub->node); + FIO_LIST_REMOVE(&sub->history); + FIO_LIST_REMOVE(&sub->history_active); + if (FIO_UNLIKELY(!ch)) + goto no_channel; + + if (FIO_LIST_IS_EMPTY(&ch->subscriptions)) { + map = &FIO___PUBSUB_POSTOFFICE.channels + ch->is_pattern; + fio___channel_map_remove(map, FIO___PUBSUB_CHANNEL2STR(ch), NULL); + if (!fio___channel_map_count(map)) + fio___channel_map_destroy(map); + } + sub->channel = NULL; + +no_channel: + fio_subscription_free(sub); + return; + (void)ignr_; +} + +/** Performs Housekeeping and defers the on_unsubscribe callback. */ +FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s) { + if (!s) + return; + s->on_message = fio___subscription_mock_cb; + fio_queue_push(fio_io_queue(), + fio___pubsub_unsubscribe_task, + (void *)s, + NULL); +} + +/** Subscribes to a named channel in the numerical filter's namespace. */ +void fio_subscribe___(void); /* sublimetext marker */ +SFUNC void fio_subscribe FIO_NOOP(fio_subscribe_args_s args) { + fio_subscription_s *s = NULL; + union { + FIO_LIST_HEAD *ls; + fio_str_info_s *str; + } uptr; + if (args.channel.len > 0xFFFFUL) + goto sub_error; + s = fio_subscription_new(); + if (!s) + goto sub_error; + + *s = (fio_subscription_s){ + .replay_since = args.replay_since, + .io = args.io, + .on_message = + (args.on_message ? args.on_message + : (args.io ? fio___subscription_call_protocol + : fio___subscription_mock_cb)), + .on_unsubscribe = args.on_unsubscribe, + .udata = args.udata, + }; + args.is_pattern = !!args.is_pattern; /* make sure this is either 1 or zero */ + uptr.ls = &s->node; + *uptr.str = FIO_STR_INFO3( + (args.channel.len + ? fio_bstr_write(NULL, args.channel.buf, args.channel.len) + : NULL), + args.channel.len, + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(args.filter, args.is_pattern)); + + if (args.subscription_handle_ptr) + goto has_handle; + if (args.master_only) + goto is_master_only; + if (!args.io) + goto is_global; + + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + fio_io_env_set( + args.io, + .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | + (uint16_t)args.filter)), + .name = args.channel, + .udata = s, + .on_close = (void (*)(void *))fio___pubsub_subscription_unsubscribe); + return; + +has_handle: + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + *args.subscription_handle_ptr = (uintptr_t)s; + return; + +is_master_only: + if (!fio_io_is_master()) + goto error_not_on_master; +is_global: + if (1) { /* so C++ can jump even though there's a new var here */ + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + uint64_t hashed_value = + fio_risky_hash(args.channel.buf, + args.channel.len, + args.filter | ((size_t)args.is_pattern << 20)); + FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); + fio___postoffice_msmap_set( + &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), + hashed_value, + FIO_STR_INFO2(args.channel.buf, args.channel.len), + s, + NULL); + FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); + } + return; + +error_not_on_master: + fio_bstr_free(uptr.str->buf); + s->node = FIO_LIST_INIT(s->node); + s->history = FIO_LIST_INIT(s->history); + fio_subscription_free(s); + FIO_LOG_WARNING( + "(%d) master-only subscription attempt on a non-master process: %.*s", + fio_io_pid(), + (int)args.channel.len, + args.channel.buf); + return; + +sub_error: + FIO_LOG_ERROR("(%d) pub/sub subscription/channel cannot be created?" + "\n\t%zu bytes long\n\t%.*s...", + fio_io_pid(), + args.channel.len, + (int)(args.channel.len > 10 ? 7 : args.channel.len), + args.channel.buf); + FIO_LOG_ERROR("failed to allocate a new subscription"); + if (args.on_unsubscribe) { + union { + void *p; + void (*fn)(void *udata); + } u = {.fn = args.on_unsubscribe}; + fio_queue_push(fio_io_queue(), + fio___pubsub_subscription_on_destroy__task, + u.p, + args.udata); + } + return; +} + +/** Cancels an existing subscriptions. */ +void fio_unsubscribe___(void); /* sublimetext marker */ +int fio_unsubscribe FIO_NOOP(fio_subscribe_args_s args) { + if (args.subscription_handle_ptr) + goto has_handle; + if (args.master_only || !args.io) + goto is_global; + + return fio_io_env_remove( + args.io, + .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | + (uint16_t)args.filter)), + .name = args.channel); + +has_handle: + fio___pubsub_subscription_unsubscribe( + *(fio_subscription_s **)args.subscription_handle_ptr); + return 0; + +is_global: + if (1) { + int r; + uint64_t hashed_value = + fio_risky_hash(args.channel.buf, + args.channel.len, + args.filter | ((size_t)args.is_pattern << 20)); + FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); + r = fio___postoffice_msmap_remove( + &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), + hashed_value, + FIO_STR_INFO3(args.channel.buf, args.channel.len, (size_t)-1), + NULL); + FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); + return r; + } +} + +/* ***************************************************************************** +Pub/Sub Message Distribution (local process) +***************************************************************************** */ + +/* performs the subscription callback */ +FIO_IFUNC void fio___subscription_on_message_task(void *s_, void *m_) { + fio_subscription_s *s = (fio_subscription_s *)s_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + struct { + fio_msg_s msg; + fio___pubsub_message_s *m; + uintptr_t flag; + } container = { + .msg = m->data, + .m = m, + }; + container.msg.io = s->io; + container.msg.udata = s->udata; + container.msg.is_json = !!(container.msg.is_json & FIO___PUBSUB_JSON); + s->on_message(&container.msg); + s->udata = container.msg.udata; + if (container.flag) + goto reschedule; + fio_subscription_free(s); + fio___pubsub_message_free(m); + return; +reschedule: + fio_queue_push(fio_io_queue(), fio___subscription_on_message_task, s_, m_); +} + +/* returns the internal message object. */ +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg) { + return *(fio___pubsub_message_s **)(msg + 1); +} + +/** Defers the current callback, so it will be called again for the message. */ +SFUNC void fio_pubsub_message_defer(fio_msg_s *msg) { + ((uintptr_t *)(msg + 1))[1] = 1; +} + +/* distributes a message to all of a channel's subscribers */ +FIO_SFUNC void fio___pubsub_channel_deliver_task(void *ch_, void *m_) { + fio_channel_s *ch = (fio_channel_s *)ch_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + FIO_LIST_HEAD *head = (&ch->subscriptions); + _Bool is_history = !!(m->data.is_json & FIO___PUBSUB_REPLAY); + head += is_history; + if (m->data.io) { /* move as many `if` statements as possible out of loops. */ + if (is_history) { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.io != s->io && m->data.published >= s->replay_since) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } else { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.io != s->io) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } + } else { + if (is_history) { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.published >= s->replay_since) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } else { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } + } + fio___pubsub_message_free(m); + fio_channel_free(ch); +} + +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m); +/** distributes a message to all matching channels */ +FIO_SFUNC void fio___pubsub_message_deliver(fio___pubsub_message_s *m) { + fio___pubsub_message_metadata_init(m); /* metadata initialization */ + fio_str_info_s ch_name = + FIO_STR_INFO3(m->data.channel.buf, + m->data.channel.len, + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(m->data.filter, 0)); + fio_channel_s **ch_ptr = fio___channel_map_node2key_ptr( + fio___channel_map_get_ptr(&FIO___PUBSUB_POSTOFFICE.channels, ch_name)); + if (ch_ptr) + fio_queue_push(fio_io_queue(), + fio___pubsub_channel_deliver_task, + fio_channel_dup(ch_ptr[0]), + fio___pubsub_message_dup(m)); + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { + if (i.node->key->filter == m->data.filter && + FIO_PUBSUB_PATTERN_MATCH(i.key, ch_name)) + fio_queue_push(fio_io_queue(), + fio___pubsub_channel_deliver_task, + fio_channel_dup(i.node->key), + fio___pubsub_message_dup(m)); + } +} + +FIO_SFUNC void fio___pubsub_message_deliver_task(void *m_, void *ignr_) { + fio___pubsub_message_deliver((fio___pubsub_message_s *)m_); + fio___pubsub_message_free((fio___pubsub_message_s *)m_); + (void)ignr_; +} + +/* ***************************************************************************** +Pub/Sub Message Type (internal data carrying structure) +***************************************************************************** */ + +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m) { + if (fio_atomic_or(&m->metadata_is_initialized, 1)) { + return; + } + fio_msg_s msg = m->data; + msg.is_json &= FIO___PUBSUB_JSON; + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + m->metadata[i] = FIO___PUBSUB_POSTOFFICE.metadata[i].build(&msg); + continue; + } + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } +} + +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_free(fio___pubsub_message_s *m) { + if (!m->metadata_is_initialized) + return; + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup(m->metadata[i]); + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + m->metadata_is_initialized = 0; +} + +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m) { + fio___pubsub_message_metadata_free(m); +} + +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_alloc(void *header) { + fio___pubsub_message_s *m; + const size_t channel_len = fio_buf2u16_le((char *)header + 18); + const size_t message_len = fio_buf2u24_le((char *)header + 20); + m = fio___pubsub_message_new(((channel_len + message_len) << 1) + + (FIO___PUBSUB_MESSAGE_OVERHEAD + 2)); + FIO_ASSERT_ALLOC(m); + *m = (fio___pubsub_message_s){ + .data = + (fio_msg_s){ + .udata = m->buf + channel_len + message_len + 2, + .channel = FIO_BUF_INFO2(m->buf, channel_len), + .message = FIO_BUF_INFO2(m->buf + channel_len + 1, message_len), + }, + }; + return m; +} + +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_author( + fio_publish_args_s args) { + fio___pubsub_message_s *m = + fio___pubsub_message_new(((args.message.len + args.channel.len) << 1) + + (FIO___PUBSUB_MESSAGE_OVERHEAD + 2)); + FIO_ASSERT_ALLOC(m); + *m = (fio___pubsub_message_s){ + .data = + (fio_msg_s){ + .io = args.from, + .id = args.id ? args.id : fio_rand64(), + .published = args.published + ? args.published + : (uint64_t)fio_time2milli(fio_time_real()), + .channel = FIO_BUF_INFO2(m->buf, args.channel.len), + .message = FIO_BUF_INFO2(m->buf + args.channel.len + 1, + args.message.len), + .filter = args.filter, + .is_json = args.is_json, + }, + }; + if (args.channel.buf && args.channel.len) + FIO_MEMCPY(m->data.channel.buf, args.channel.buf, args.channel.len); + m->data.channel.buf[args.channel.len] = 0; + if (args.message.buf && args.message.len) + FIO_MEMCPY(m->data.message.buf, args.message.buf, args.message.len); + m->data.message.buf[args.message.len] = 0; + return m; +} + +FIO_SFUNC void fio___pubsub_message_encrypt(fio___pubsub_message_s *m) { + if (m->data.udata) + return; + const void *k = fio___pubsub_secret_key(m->data.id); + const uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; + uint8_t *pos = (uint8_t *)(m->data.message.buf + m->data.message.len + 1); + uint8_t *dest = pos; + m->data.udata = (void *)pos; + fio_u2buf64_le(pos, m->data.id); + pos += 8; + fio_u2buf64_le(pos, m->data.published); + pos += 8; + fio_u2buf16_le(pos, (uint16_t)m->data.filter); + pos += 2; + fio_u2buf16_le(pos, (uint16_t)m->data.channel.len); + pos += 2; + fio_u2buf24_le(pos, (uint32_t)m->data.message.len); + pos += 3; + *(pos++) = m->data.is_json; + const size_t enc_len = m->data.channel.len + m->data.message.len + 2; + FIO_MEMCPY(pos, m->data.channel.buf, enc_len); + if (enc_len == 2) + return; + pos += enc_len; + fio_chacha20_poly1305_enc(pos, + (void *)(dest + FIO___PUBSUB_MESSAGE_HEADER), + m->data.channel.len + m->data.message.len + 2, + m->data.udata, + FIO___PUBSUB_MESSAGE_HEADER, + k, + nonce); +} + +FIO_SFUNC int fio___pubsub_message_decrypt(fio___pubsub_message_s *m) { + if (m->data.id) + return 0; + if (!m->data.udata) + return -1; + uint8_t *pos = (uint8_t *)(m->data.message.buf + m->data.message.len + 1); + uint8_t *const dest = pos; + m->data.id = fio_buf2u64_le(pos); + pos += 8; + m->data.published = fio_buf2u64_le(pos); + pos += 8; + m->data.filter = fio_buf2u16_le(pos); + pos += 2; + m->data.channel = FIO_BUF_INFO2(m->buf, fio_buf2u16_le(pos)); + pos += 2; + m->data.message = + FIO_BUF_INFO2(m->buf + m->data.channel.len + 1, fio_buf2u24_le(pos)); + pos += 3; + m->data.is_json = *(pos++); + const void *k = fio___pubsub_secret_key(m->data.id); + uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; + const size_t enc_len = m->data.channel.len + m->data.message.len + 2; + FIO_MEMCPY(m->buf, pos, enc_len); + if (enc_len == 2) + return 0; + pos += enc_len; + return fio_chacha20_poly1305_dec(pos, + m->buf, + m->data.channel.len + m->data.message.len + + 2, + dest, + FIO___PUBSUB_MESSAGE_HEADER, + k, + nonce); +} + +FIO_IFUNC void fio___pubsub_message_is_dirty(fio___pubsub_message_s *m) { + m->data.udata = NULL; +} + +/* ***************************************************************************** +Pub/Sub Message Object - IO helpers +***************************************************************************** */ + +FIO_IFUNC void fio___pubsub_message_write2io(fio_io_s *io, void *m_) { + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + if (io == m->data.io) + return; + FIO_LOG_DDEBUG2("(%d) pub/sub sending IPC/peer message: %zu bytes", + fio_io_pid(), + m->data.message.len + m->data.channel.len + + FIO___PUBSUB_MESSAGE_OVERHEAD); + fio___pubsub_message_encrypt(m); + fio_io_write2(io, + .buf = fio___pubsub_message_dup(m), + .len = (m->data.message.len + m->data.channel.len + + FIO___PUBSUB_MESSAGE_OVERHEAD), + .offset = ((uintptr_t)(m->data.udata) - (uintptr_t)(m)), + .dealloc = (void (*)(void *))fio___pubsub_message_free); +} + +/* A callback for IO subscriptions - sends raw message data. */ +FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg) { + if (!msg || !msg->message.len) + return; + fio___pubsub_message_s *m = fio___pubsub_msg2internal(msg); + fio_io_write2(msg->io, + .buf = fio___pubsub_message_dup(m), + .len = msg->message.len, + .offset = (size_t)(msg->message.buf - (char *)m), + .dealloc = (void (*)(void *))fio___pubsub_message_free); +} + +/* ***************************************************************************** +Pub/Sub Message Routing +***************************************************************************** */ + +FIO_SFUNC void fio___pubsub_message_route(fio___pubsub_message_s *m) { + fio___pubsub_message_parser_s *p; + unsigned flags = m->data.is_json; + FIO_LOG_DDEBUG2("(%d) pub/sub routing message (%x)", + fio_io_pid(), + (int)m->data.is_json); + + if (flags & FIO___PUBSUB_SPECIAL) + goto is_special_message; + + if ((FIO___PUBSUB_POSTOFFICE.filter.local & flags)) + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + + if ((FIO___PUBSUB_POSTOFFICE.filter.remote & flags)) + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.remote, + fio___pubsub_message_write2io, + m); + + if ((FIO___PUBSUB_POSTOFFICE.filter.publish & flags)) + fio_queue_push(fio_io_queue(), + fio___pubsub_message_deliver_task, + fio___pubsub_message_dup(m)); + + return; + +is_special_message: + FIO_LOG_DDEBUG2("(%d) pub/sub internal subscription/ID message received", + fio_io_pid()); + switch (flags) { + case FIO___PUBSUB_SPECIAL: /* TODO: run generic command on root */ break; + case FIO___PUBSUB_SUB: + fio_subscribe(.io = m->data.io, + .channel = m->data.channel, + .on_message = fio___subscription_mock_cb, + .filter = m->data.filter, + .is_pattern = (uint8_t)(m->data.id - 1)); + return; + case FIO___PUBSUB_UNSUB: + fio_unsubscribe(.io = m->data.io, + .channel = m->data.channel, + .on_message = fio___subscription_mock_cb, + .filter = m->data.filter, + .is_pattern = (uint8_t)(m->data.id - 1)); + return; + + case FIO___PUBSUB_IDENTIFY: + p = fio___pubsub_message_parser(m->data.io); + if (p) { + p->uuid[0] = m->data.id; + p->uuid[1] = m->data.published; + fio___pubsub_broadcast_connected_set( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + p->uuid[0], + p->uuid[1]); + } + FIO_LOG_INFO("(%d - cluster) identified new peer (%zu connections)", + fio_io_pid(), + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + return; + case FIO___PUBSUB_FORWARDER: /* fall through */ + case (FIO___PUBSUB_FORWARDER | FIO___PUBSUB_JSON): + if (FIO___PUBSUB_POSTOFFICE.filter.remote) { /* root process */ + fio___pubsub_message_is_dirty(m); + m->data.message.len -= 8; + m->data.is_json &= FIO___PUBSUB_JSON; + fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)(uintptr_t)fio_buf2u64u( + m->data.message.buf + m->data.message.len); + m->data.message.buf[m->data.message.len] = 0; + e->publish(e, &m->data); + } else { /* child process */ + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + } + return; + + case FIO___PUBSUB_HISTORY_START: + FIO_LOG_DDEBUG2("(%d) pub/sub internal history start message received", + fio_io_pid()); + /* TODO! */ + return; + case FIO___PUBSUB_HISTORY_END: + FIO_LOG_DDEBUG2("(%d) pub/sub internal history end message received", + fio_io_pid()); + /* TODO! */ + return; + } + return; +} + +/* ***************************************************************************** +Pub/Sub - Publish +***************************************************************************** */ + +FIO_SFUNC void fio___publish_message_task(void *m_, void *ignr_) { + (void)ignr_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + fio___pubsub_message_route(m); + fio___pubsub_message_free(m); +} + +/** Publishes a message to the relevant subscribers (if any). */ +void fio_publish___(void); /* SublimeText marker*/ +void fio_publish FIO_NOOP(fio_publish_args_s args) { + if (FIO_UNLIKELY(args.channel.len > 0xFFFFUL)) { + FIO_LOG_ERROR("(%d) pub/sub channel name too long (%zu bytes)", + fio_io_pid(), + args.channel.len); + return; + } + if (FIO_UNLIKELY(args.message.len > 0xFFFFFFUL)) { + FIO_LOG_ERROR("(%d) pub/sub message payload too large (%zu bytes)", + fio_io_pid(), + args.message.len); + return; + } + fio___pubsub_message_s *m; + fio_msg_s msg; + + if (!args.engine) { + args.engine = FIO_PUBSUB_DEFAULT; + if (!args.engine) + args.engine = FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; + if (args.filter < 0) + args.engine = FIO_PUBSUB_LOCAL; + } + if ((uintptr_t)args.engine > 0xFFUL) + goto external_engine; + + m = fio___pubsub_message_author(args); + m->data.is_json = ((!!args.is_json) | ((uint8_t)(uintptr_t)args.engine)); + + FIO_LOG_DDEBUG2("(%d) publishing pub/sub message (scheduling)", fio_io_pid()); + fio_io_defer(fio___publish_message_task, m, NULL); + return; + +external_engine: + + msg.message = args.message; + args.message.buf = NULL; + args.message.len += 8; + + m = fio___pubsub_message_author(args); + m->data.is_json = ((!!args.is_json) | ((uint8_t)FIO___PUBSUB_FORWARDER)); + FIO_MEMCPY(m->data.message.buf, msg.message.buf, msg.message.len); + fio_u2buf64u(m->data.message.buf + msg.message.len, (uintptr_t)args.engine); + fio_io_defer(fio___publish_message_task, m, NULL); +} + +/* ***************************************************************************** +Pub/Sub Message on-the-wire parsing +***************************************************************************** */ + +FIO_IFUNC void fio___pubsub_message_parse( + fio_io_s *io, + void (*cb)(fio_io_s *, fio___pubsub_message_s *)) { + fio___pubsub_message_parser_s *parser = fio___pubsub_message_parser(io); + if (!parser) + return; + size_t existing = parser->len; + if (!parser->msg) { + while (existing < FIO___PUBSUB_MESSAGE_HEADER) { /* get message length */ + size_t consumed = fio_io_read(io, + parser->buf + existing, + FIO___PUBSUB_MESSAGE_OVERHEAD - existing); + if (!consumed) { + parser->len = existing; + return; + } + existing += consumed; + } + parser->msg = fio___pubsub_message_alloc(parser->buf); + FIO_MEMCPY(parser->msg->data.udata, parser->buf, existing); + } + /* known message length, read to end and publish */ + fio___pubsub_message_s *m = parser->msg; + const size_t needed = + m->data.channel.len + m->data.message.len + FIO___PUBSUB_MESSAGE_OVERHEAD; + // FIO_LOG_DDEBUG2("(%d) pub/sub parsing IPC/peer message %p (%zu/%zu bytes)", + // fio_io_pid(), + // (void *)fio_buf2u64_le(m->data.udata), + // existing, + // needed); + while (existing < needed) { + size_t consumed = + fio_io_read(io, (char *)m->data.udata + existing, needed - existing); + if (!consumed) { + parser->len = existing; + return; + } + existing += consumed; + } + parser->msg = NULL; + parser->len = 0; + m->data.io = io; + if (fio___pubsub_message_decrypt(m)) { + FIO_LOG_SECURITY("(%d) pub/sub message decryption error", fio_io_pid()); + fio_io_close_now(io); + } else { + cb(io, m); + } + fio___pubsub_message_free(m); + return; /* consume no more than 1 message at a time */ +} + +/* ***************************************************************************** +Pub/Sub Protocols +***************************************************************************** */ + +FIO_SFUNC void fio___pubsub_on_message_master(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_on_message_worker(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_on_message_remote(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_map_s *map = &FIO___PUBSUB_POSTOFFICE.remote_messages; + map += !!(msg->data.is_json & FIO___PUBSUB_REPLAY); + fio___pubsub_message_s *existing = fio___pubsub_message_map_set(map, msg); + if (existing != msg) + return; /* already received */ + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io) { + fio___pubsub_message_parser_init(fio___pubsub_message_parser(io)); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_master); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_worker); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_remote); +} + +FIO_SFUNC void fio___pubsub_protocol_on_close(void *p_, void *udata) { + fio___pubsub_message_parser_s *p = (fio___pubsub_message_parser_s *)p_; + if (p->uuid[0] || p->uuid[1]) { + // TODO!: fio___pubsub_broadcast_hello(fio_io_s *io) ? + fio___pubsub_broadcast_connected_remove( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + p->uuid[0], + p->uuid[1], + NULL); + FIO_LOG_INFO( + "(%d) (pub/sub cluster) lost peer connection (%zu connections)", + fio_io_pid(), + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + } + fio___pubsub_message_parser_destroy(p); + if (!fio_io_is_master()) + fio_io_stop(); + (void)udata; +} + +static void fio___pubsub_protocol_on_timeout(fio_io_s *io) { + static const uint8_t ping_msg[FIO___PUBSUB_MESSAGE_OVERHEAD] = { + [23] = FIO___PUBSUB_PING}; + fio_io_write2(io, + .buf = (void *)ping_msg, + .len = FIO___PUBSUB_MESSAGE_OVERHEAD); +} + +/* ***************************************************************************** +Pub/Sub Engine Support Implementation +***************************************************************************** */ + +static void fio___pubsub_mock_detached(const fio_pubsub_engine_s *eng) { + (void)eng; +} +static void fio___pubsub_mock_sub_unsub(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + (void)eng, (void)channel, (void)filter; +} +static void fio___pubsub_mock_publish(const fio_pubsub_engine_s *eng, + fio_msg_s *msg) { + (void)eng, (void)msg; /* TODO:? sensible default? publish to cluster? */ +} + +static void fio___pubsub_attach_task(void *engine_, void *ignr_) { + (void)ignr_; + fio_pubsub_engine_s *engine = (fio_pubsub_engine_s *)engine_; + if (!engine->detached) + engine->detached = fio___pubsub_mock_detached; + if (!engine->subscribe) + engine->subscribe = fio___pubsub_mock_sub_unsub; + if (!engine->unsubscribe) + engine->unsubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->psubscribe) + engine->psubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->punsubscribe) + engine->punsubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->publish) + engine->publish = fio___pubsub_mock_publish; + fio___pubsub_engines_set(&FIO___PUBSUB_POSTOFFICE.engines, engine); + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.channels, i) { + engine->subscribe(engine, + FIO_BUF_INFO2(i.key.buf, i.key.len), + (i.key.capa >> 16)); + } + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { + engine->psubscribe(engine, + FIO_BUF_INFO2(i.key.buf, i.key.len), + (i.key.capa >> 16)); + } +} + +FIO_SFUNC void fio___pubsub_detach_task(void *engine, void *ignr_) { + (void)ignr_; + fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)engine; + fio___pubsub_engines_remove(&FIO___PUBSUB_POSTOFFICE.engines, e, NULL); +} + +/** Attaches an engine, so it's callback can be called by facil.io. */ +SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine) { + if (!engine) + return; + fio_io_defer(fio___pubsub_attach_task, engine, NULL); +} + +/** Schedules an engine for Detachment, so it could be safely destroyed. */ +SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine) { + fio_queue_push(fio_io_queue(), fio___pubsub_detach_task, engine, NULL); +} + +/* ***************************************************************************** +Channel Creation / Destruction Callback (notifying engines) +***************************************************************************** */ + +/** Callback for when a channel is created. */ +FIO_IFUNC void fio___channel_on_create(fio_channel_s *ch) { + fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); + FIO_LOG_DDEBUG2("(%d) pub/sub %s created, filter %d, length %zu bytes: %s", + fio_io_pid(), + (ch->is_pattern ? "pattern" : "channel"), + (int)ch->filter, + (size_t)ch->name_len, + name.buf); + FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { + (&i.key->subscribe + ch->is_pattern)[0](i.key, name, ch->filter); + } + if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ + FIO_LOG_DDEBUG2("(%d) informing root process of new channel.", + fio_io_pid()); + fio___pubsub_message_s *m = + fio___pubsub_message_author((fio_publish_args_s){ + .id = (uint64_t)(ch->is_pattern + 1), + .channel = FIO_BUF_INFO2(ch->name, ch->name_len), + .filter = ch->filter, + .is_json = FIO___PUBSUB_SUB, + }); + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + fio___pubsub_message_free(m); + } +} +/** Callback for when a channel is destroy. */ +FIO_IFUNC void fio___channel_on_destroy(fio_channel_s *ch) { + fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); + + FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { + (&i.key->unsubscribe + ch->is_pattern)[0](i.key, name, ch->filter); + } + + if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ + fio___pubsub_message_s *m = + fio___pubsub_message_author((fio_publish_args_s){ + .id = (uint64_t)(ch->is_pattern + 1), + .channel = FIO_BUF_INFO2(ch->name, ch->name_len), + .filter = ch->filter, + .is_json = FIO___PUBSUB_UNSUB, + }); + if (m) { + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + fio___pubsub_message_free(m); + } + } + + FIO_LOG_DDEBUG2("(%d) pub/sub %s destroyed, filter %d, length %zu bytes: %s", + fio_io_pid(), + (ch->is_pattern ? "pattern" : "channel"), + (int)ch->filter, + (size_t)ch->name_len, + name.buf); +} + +/* ***************************************************************************** +Broadcasting for remote connections +***************************************************************************** */ + +FIO_IFUNC fio_u512 fio___pubsub_broadcast_compose(uint64_t tick) { + /* [0-1] Sender's 128 bit UUID + * [2] Random nonce + * [3] Timestamp in milliseconds + * [4-5] MAC + */ + fio_u512 u = {0}; + uint64_t hello_rand = fio_rand64(); + const void *k = fio___pubsub_secret_key(hello_rand); + u.u64[0] = FIO___PUBSUB_POSTOFFICE.uuid.u64[0]; + u.u64[1] = FIO___PUBSUB_POSTOFFICE.uuid.u64[1]; + u.u64[2] = fio_ltole64(hello_rand); /* persistent endienes required for k */ + u.u64[3] = fio_ltole64(tick); + fio_poly1305_auth(u.u64 + 4, k, NULL, 0, u.u64, 32); + return u; +} + +FIO_SFUNC void fio___pubsub_broadcast_hello(fio_io_s *io) { + + if (!fio_io_is_running() || !(io = FIO___PUBSUB_POSTOFFICE.broadcaster)) + return; + static int64_t last_hello = 0; + int64_t this_hello = fio_io_last_tick(); + if (last_hello == this_hello) + return; + fio_u512 u = fio___pubsub_broadcast_compose((last_hello = this_hello)); + struct sockaddr_in addr = (struct sockaddr_in){ + .sin_family = AF_INET, + .sin_port = fio_lton16((uint16_t)(uintptr_t)fio_io_udata(io)), + .sin_addr.s_addr = INADDR_BROADCAST, // inet_addr("255.255.255.255"), + }; + FIO_LOG_DEBUG2("(%d) pub/sub sending broadcast.", fio_io_pid()); + sendto(fio_io_fd(io), + (const char *)u.u8, + 48, + 0, + (struct sockaddr *)&addr, + sizeof(addr)); +} + +FIO_SFUNC void fio___pubsub_broadcast_hello_task_done(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + fio_io_free(io); +} + +FIO_SFUNC int fio___pubsub_broadcast_hello_task(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + fio___pubsub_broadcast_hello(io); + return 0; +} + +FIO_SFUNC int fio___pubsub_broadcast_hello_validate(uint64_t *hello) { + uint64_t mac[2] = {0}; + /* test server UUID (ignore self generated messages) */ + if (hello[0] == FIO___PUBSUB_POSTOFFICE.uuid.u64[0] && + hello[1] == FIO___PUBSUB_POSTOFFICE.uuid.u64[1]) + return -1; + /* test time window */ + mac[0] = fio_io_last_tick(); + if (mac[0] > fio_ltole64(hello[3]) + 8192 || + mac[0] + 8192 < fio_ltole64(hello[3])) { + FIO_LOG_SECURITY( + "(%d) pub/sub-broadcast timing error - possible replay attack?", + fio_io_pid()); + return -1; + } + /* test for duplicate connections */ + if (fio___pubsub_broadcast_connected_get( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + hello[0], + hello[1])) { + FIO_LOG_DEBUG2("(%d) pub/sub-broadcast Prevented duplicate connection!", + fio_io_pid()); + return -1; + } + /* test MAC */ + const void *k = fio___pubsub_secret_key(fio_ltole64(hello[2])); + fio_poly1305_auth(mac, k, NULL, 0, hello, 32); + if (mac[0] != hello[4] || mac[1] != hello[5]) { + FIO_LOG_SECURITY("(%d) pub/sub-broadcast MAC failure - under attack?", + fio_io_pid()); + return -1; + } + return 0; +} +/* ***************************************************************************** +Letter Listening to Remote Connections - TODO! +***************************************************************************** +*/ +FIO_SFUNC void fio___pubsub_broadcast_on_attach(fio_io_s *io) { + fio___pubsub_broadcast_hello((FIO___PUBSUB_POSTOFFICE.broadcaster = io)); + fio_io_run_every(.fn = fio___pubsub_broadcast_hello_task, + .udata1 = fio_io_dup(io), + .on_finish = fio___pubsub_broadcast_hello_task_done, + .every = + (uint32_t)(1024 | + (1023 & FIO___PUBSUB_POSTOFFICE.uuid.u64[0])), + .repetitions = 2); +} +FIO_SFUNC void fio___pubsub_broadcast_on_close(void *ignr1_, void *ignr2_) { + FIO___PUBSUB_POSTOFFICE.broadcaster = NULL; + (void)ignr1_, (void)ignr2_; +} + +FIO_SFUNC void fio___pubsub_broadcast_on_data(fio_io_s *io) { + uint64_t buf[16]; + struct sockaddr from[2]; + socklen_t from_len = sizeof(from); + ssize_t len; + int should_say_hello = 0; + fio___pubsub_message_s *m = fio___pubsub_message_author( + (fio_publish_args_s){.id = FIO___PUBSUB_POSTOFFICE.uuid.u64[0], + .published = FIO___PUBSUB_POSTOFFICE.uuid.u64[1], + .is_json = FIO___PUBSUB_IDENTIFY}); + + while ((len = recvfrom(fio_io_fd(io), (char *)buf, 128, 0, from, &from_len)) > + 0) { + if (len != 48) { + FIO_LOG_WARNING( + "(%d) pub/sub peer detection received invalid packet (%zu bytes)!", + fio_io_pid(), + len); + continue; + } + if (fio___pubsub_broadcast_hello_validate(buf)) { + FIO_LOG_WARNING( + "(%d) pub/sub peer detection received invalid packet payload!", + fio_io_pid()); + continue; + } + if (fio___pubsub_broadcast_connected_get( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + buf[0], + buf[1]) == buf[1]) { + FIO_LOG_DDEBUG2("(%d) skipping peer connection - already exists", + fio_io_pid()); + continue; /* skip connection, already exists. */ + } + should_say_hello |= 1; + FIO_LOG_DDEBUG2("(%d) detected peer, should now connect", fio_io_pid()); + + /* TODO: fixme! */ + char addr_buf[128]; + if (getnameinfo(from, + from_len, + addr_buf, + 64, + addr_buf + 64, + 64, + (NI_NUMERICHOST | NI_NUMERICHOST))) { + FIO_LOG_ERROR("(%d) couldn't resolve peer address", fio_io_pid()); + continue; + } + int fd = fio_sock_open(addr_buf, + addr_buf + 64, + FIO_SOCK_NONBLOCK | FIO_SOCK_CLIENT | FIO_SOCK_TCP); + if (fd == -1) { + FIO_LOG_ERROR("couldn't connect to cluster peer: %s", strerror(errno)); + continue; + } + fio___pubsub_broadcast_connected_set(&FIO___PUBSUB_POSTOFFICE.remote_uuids, + addr_buf[0], + addr_buf[1]); + fio_io_s *peer = fio_io_attach_fd(fd, + &FIO___PUBSUB_POSTOFFICE.protocol.remote, + NULL, + NULL); + fio___pubsub_message_write2io(peer, m); + FIO_LOG_INFO("(%d) pub/sub-cluster connecting to peer (%zu connections).", + fio_io_pid(), + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + } + fio___pubsub_message_free(m); + if (should_say_hello) + fio_io_run_every(.fn = fio___pubsub_broadcast_hello_task, + .udata1 = fio_io_dup(io), + .on_finish = fio___pubsub_broadcast_hello_task_done, + .every = + (uint32_t)(1024 | + (1023 & + FIO___PUBSUB_POSTOFFICE.uuid.u64[0]))); +} + +FIO_SFUNC void fio___pubsub_broadcast_on_incoming(fio_io_s *io) { + int fd; + while ((fd = accept(fio_io_fd(io), NULL, NULL)) != -1) { + FIO_LOG_DDEBUG2("(%d) accepting a cluster peer connection", fio_io_pid()); + fio_io_attach_fd(fd, &FIO___PUBSUB_POSTOFFICE.protocol.remote, NULL, NULL); + } + FIO_LOG_INFO("(%d) (cluster) accepted new peer(s) (%zu connections).", + fio_io_pid(), + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); +} + +SFUNC void fio___pubsub_broadcast_on_port(void *port_) { + int16_t port = (int16_t)(uintptr_t)port_; + static fio_io_protocol_s broadcast = { + .on_attach = fio___pubsub_broadcast_on_attach, + .on_data = fio___pubsub_broadcast_on_data, + .on_close = fio___pubsub_broadcast_on_close, + .on_timeout = fio_io_touch, + }; + static fio_io_protocol_s accept_remote = { + .on_data = fio___pubsub_broadcast_on_incoming, + .on_timeout = fio_io_touch, + }; + if (FIO___PUBSUB_POSTOFFICE.secret_is_random) { + FIO_LOG_ERROR( + "(%d) Listening to cluster peer connections failed!" + "\n\tUsing a random (non-shared) secret, cannot validate peers.", + fio_io_pid()); + return; + } + if (!port || port < 0) + port = 3333; + FIO_STR_INFO_TMP_VAR(url, 32); + url.buf[0] = ':'; + url.len = 1; + fio_string_write_u(&url, NULL, (uint64_t)port); + + int fd_udp = + fio_sock_open(NULL, + url.buf + 1, + FIO_SOCK_UDP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); + FIO_ASSERT(fd_udp != -1, "couldn't open broadcast socket!"); + int fd_tcp = + fio_sock_open(NULL, + url.buf + 1, + FIO_SOCK_TCP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); + FIO_ASSERT(fd_tcp != -1, "couldn't open cluster-peer listening socket!"); + { +#if FIO_OS_WIN + char enabled = 1; +#else + int enabled = 1; +#endif + setsockopt(fd_udp, SOL_SOCKET, SO_BROADCAST, &enabled, sizeof(enabled)); + enabled = 1; + setsockopt(fd_udp, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)); + } + fio_io_attach_fd(fd_udp, &broadcast, port_, NULL); + fio_io_attach_fd(fd_tcp, &accept_remote, NULL, NULL); + return; +} + +/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ +SFUNC void fio_pubsub_broadcast_on_port(int16_t port) { + fio_state_callback_add(FIO_CALL_PRE_START, + fio___pubsub_broadcast_on_port, + (void *)(uintptr_t)port); +} + +/* ***************************************************************************** +Pub/Sub Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_PUBSUB +#endif /* FIO_PUBSUB */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_HTTP_HANDLE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + An HTTP connection Handle helper + +See also: +https://www.rfc-editor.org/rfc/rfc9110.html + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_HTTP_HANDLE) && !defined(H___FIO_HTTP_HANDLE___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_HTTP_HANDLE___H + +/* ***************************************************************************** +HTTP Handle Settings +***************************************************************************** */ +#ifndef FIO_HTTP_EXACT_LOGGING +/** + * By default, facil.io logs the HTTP request cycle using a fuzzy starting and + * ending point for the time stamp. + * + * The fuzzy timestamp includes delays that aren't related to the HTTP request + * and may ignore time passed due to timestamp caching. + * + * On the other hand, `FIO_HTTP_EXACT_LOGGING` collects exact time stamps to + * measure the time it took to process the HTTP request (excluding time spent + * reading / writing the data from the network). + * + * Due to the preference to err on the side of higher performance, fuzzy + * time-stamping is the default. + */ +#define FIO_HTTP_EXACT_LOGGING 0 +#ifndef H___FIO_IO___H +#undef FIO_HTTP_EXACT_LOGGING +#define FIO_HTTP_EXACT_LOGGING 1 +#endif +#endif + +#ifndef FIO_HTTP_BODY_RAM_LIMIT +/** + * The HTTP handle automatically switches between RAM storage and file storage + * once the HTTP body (payload) reaches a certain size. This control this point + * of transition + */ +#define FIO_HTTP_BODY_RAM_LIMIT (1 << 17) +#endif + +#ifndef FIO_HTTP_CACHE_LIMIT +/** Each of the HTTP String Caches will be limited to this String count. */ +#define FIO_HTTP_CACHE_LIMIT 0 /* ((1UL << 6) + (1UL << 5)) */ +#endif + +#ifndef FIO_HTTP_CACHE_STR_MAX_LEN +/** The HTTP handle will avoid caching strings longer than this value. */ +#define FIO_HTTP_CACHE_STR_MAX_LEN (1 << 12) +#endif + +#ifndef FIO_HTTP_CACHE_USES_MUTEX +/** The HTTP cache will use a mutex to allow headers to be set concurrently. */ +#define FIO_HTTP_CACHE_USES_MUTEX 1 +#endif + +#ifndef FIO_HTTP_PRE_CACHE_KNOWN_HEADERS +/** Adds a static cache for common HTTP header names. */ +#define FIO_HTTP_PRE_CACHE_KNOWN_HEADERS 1 +#endif + +#ifndef FIO_HTTP_DEFAULT_INDEX_FILENAME +/** The default file name when a static file response points to a folder. */ +#define FIO_HTTP_DEFAULT_INDEX_FILENAME "index" +#endif + +#ifndef FIO_HTTP_STATIC_FILE_COMPLETION +/** Attempts to auto-complete static file paths with missing extensions. */ +#define FIO_HTTP_STATIC_FILE_COMPLETION 1 +#endif + +#ifndef FIO_HTTP_LOG_X_REQUEST_START +#define FIO_HTTP_LOG_X_REQUEST_START 1 +#endif + +#ifndef FIO_HTTP_ENFORCE_LOWERCASE_HEADERS +/** If true, the HTTP handle will copy input header names to lower case. */ +#define FIO_HTTP_ENFORCE_LOWERCASE_HEADERS 0 +#endif + +/* ***************************************************************************** +HTTP Handle Type +***************************************************************************** */ + +/** + * The HTTP Handle type. + * + * Note that the type is NOT designed to be thread-safe. + */ +typedef struct fio_http_s fio_http_s; + +/** + * The HTTP Controller points to all the callbacks required by the HTTP Handler. + * + * This allows the HTTP Handler to be somewhat protocol agnostic. + * + * Note: if the controller callbacks aren't thread-safe, than the `http_write` + * function MUST NOT be called from any thread except the thread that the + * controller is expecting. + */ +typedef struct fio_http_controller_s fio_http_controller_s; + +/* ***************************************************************************** +Constructor / Destructor +***************************************************************************** */ + +/** Create a new fio_http_s handle. */ +SFUNC fio_http_s *fio_http_new(void); + +/** Creates a copy of an existing handle, copying only its request data. */ +SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *old); + +/** Reduces an fio_http_s handle's reference count or frees it. */ +SFUNC void fio_http_free(fio_http_s *); + +/** Increases an fio_http_s handle's reference count. */ +SFUNC fio_http_s *fio_http_dup(fio_http_s *); + +/** Destroyed the HTTP handle object, freeing all allocated resources. */ +SFUNC fio_http_s *fio_http_destroy(fio_http_s *h); + +/** Collects an updated timestamp for logging purposes. */ +SFUNC void fio_http_start_time_set(fio_http_s *); + +/** Clears any response data. */ +SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body); + +/* ***************************************************************************** +Opaque User and Controller Data +***************************************************************************** */ + +/** Gets the opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata(fio_http_s *); + +/** Sets the opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata_set(fio_http_s *, void *); + +/** Gets the second opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata2(fio_http_s *); + +/** Sets a second opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata2_set(fio_http_s *, void *); + +/** Gets the HTTP Controller associated with the HTTP handle. */ +FIO_IFUNC fio_http_controller_s *fio_http_controller(fio_http_s *h); + +/** Gets the HTTP Controller associated with the HTTP handle. */ +FIO_IFUNC fio_http_controller_s *fio_http_controller_set( + fio_http_s *h, + fio_http_controller_s *controller); + +/** Returns the existing controller data (`void *` pointer). */ +FIO_IFUNC void *fio_http_cdata(fio_http_s *h); + +/** Sets a new controller data (`void *` pointer). */ +FIO_IFUNC void *fio_http_cdata_set(fio_http_s *h, void *cdata); + +/* ***************************************************************************** +Data associated with the Request (usually set by the HTTP protocol) +***************************************************************************** */ + +/** Gets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status(fio_http_s *); + +/** Sets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status_set(fio_http_s *, size_t status); + +/** Gets the method information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_method(fio_http_s *); + +/** Sets the method information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_method_set(fio_http_s *, fio_str_info_s); + +/** Gets the path information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_path(fio_http_s *); + +/** Sets the path information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_path_set(fio_http_s *, fio_str_info_s); + +/** Gets the query information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_query(fio_http_s *); + +/** Sets the query information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_query_set(fio_http_s *, fio_str_info_s); + +/** Gets the version information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_version(fio_http_s *); + +/** Sets the version information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_version_set(fio_http_s *, fio_str_info_s); + +/** + * Gets the header information associated with the HTTP handle. + * + * Since more than a single value may be associated with a header name, the + * index may be used to collect subsequent values. + * + * An empty value is returned if no header value is available (or index is + * exceeded). + */ +SFUNC fio_str_info_s fio_http_request_header(fio_http_s *, + fio_str_info_s name, + size_t index); + +/** + * Returns the number of headers named `name` that were received. + * + * If `name` buffer is `NULL`, returns the number of unique headers (not the + * number of unique values). + */ +SFUNC size_t fio_http_request_header_count(fio_http_s *, fio_str_info_s name); + +/** Sets the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_request_header_set(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); + +/** Sets the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s +fio_http_request_header_set_if_missing(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); + +/** Adds to the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_request_header_add(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); + +/** + * Iterates through all request headers (except cookies!). + * + * A non-zero return will stop iteration. + * + * Returns the number of iterations performed. If `callback` is `NULL`, returns + * the number of headers available (multi-value headers are counted as 1). + * */ +SFUNC size_t fio_http_request_header_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); + +/** Gets the body (payload) length associated with the HTTP handle. */ +SFUNC size_t fio_http_body_length(fio_http_s *); + +/** Adjusts the body's reading position. Negative values start at the end. */ +SFUNC size_t fio_http_body_seek(fio_http_s *, ssize_t pos); + +/** Reads up to `length` of data from the body, returns nothing on EOF. */ +SFUNC fio_str_info_s fio_http_body_read(fio_http_s *, size_t length); + +/** + * Reads from the body until finding `token`, reaching `limit` or EOF. + * + * Note: `limit` is ignored if zero or if the remaining data is lower than + * limit. + */ +SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *, + char token, + size_t limit); + +/** Allocates a body (payload) of (at least) the `expected_length`. */ +SFUNC void fio_http_body_expect(fio_http_s *, size_t expected_length); + +/** Writes `data` to the body (payload) associated with the HTTP handle. */ +SFUNC void fio_http_body_write(fio_http_s *, const void *data, size_t len); + +/** + * If the body is stored in a temporary file, returns the file's handle. + * + * Otherwise returns -1. + */ +SFUNC int fio_http_body_fd(fio_http_s *); + +/* ***************************************************************************** +Cookies +***************************************************************************** */ + +/** + * Possible values for the `same_site` property in the cookie settings. + * + * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + */ +typedef enum fio_http_cookie_same_site_e { + /** allow the browser to dictate this property */ + FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT = 0, + /** The browser sends the cookie with cross-site and same-site requests. */ + FIO_HTTP_COOKIE_SAME_SITE_NONE, + /** + * The cookie is withheld on cross-site sub-requests. + * + * The cookie is sent when a user navigates to the URL from an external + * site. + */ + FIO_HTTP_COOKIE_SAME_SITE_LAX, + /** The browser sends the cookie only for same-site requests. */ + FIO_HTTP_COOKIE_SAME_SITE_STRICT, +} fio_http_cookie_same_site_e; + +/** + * This is a helper for setting cookie data. + * + * This struct is used together with the `fio_http_cookie_set` macro. i.e.: + * + * fio_http_set_cookie(h, + * .name = FIO_STR_INFO1("my_cookie"), + * .value = FIO_STR_INFO1("data")); + * + */ +typedef struct fio_http_cookie_args_s { + /** The cookie's name. */ + fio_str_info_s name; + /** The cookie's value (leave blank to delete cookie). */ + fio_str_info_s value; + /** The cookie's domain (optional). */ + fio_str_info_s domain; + /** The cookie's path (optional). */ + fio_str_info_s path; + /** Max Age (how long should the cookie persist), in seconds (0 == session).*/ + int max_age; + /** SameSite value. */ + fio_http_cookie_same_site_e same_site; + /** Limit cookie to secure connections.*/ + unsigned secure : 1; + /** Limit cookie to HTTP (intended to prevent JavaScript access/hijacking).*/ + unsigned http_only : 1; + /** + * Set the Partitioned (third party) cookie flag: + * https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies + */ + unsigned partitioned : 1; +} fio_http_cookie_args_s; + +/** + * Sets a response cookie. + * + * Returns -1 on error and 0 on success. + * + * Note: Long cookie names and long cookie values will be considered a security + * violation and an error will be returned. Many browsers and proxies impose + * limits on headers and cookies, cookies often limited to 4Kb in total for both + * name and value. + */ +SFUNC int fio_http_cookie_set(fio_http_s *h, fio_http_cookie_args_s); + +/** Named arguments helper. See fio_http_cookie_args_s for details. */ +#define fio_http_cookie_set(http___handle, ...) \ + fio_http_cookie_set((http___handle), (fio_http_cookie_args_s){__VA_ARGS__}) + +/** Returns a cookie value (either received of newly set), if any. */ +SFUNC fio_str_info_s fio_http_cookie(fio_http_s *, + const char *name, + size_t name_len); + +/** Iterates through all cookies. A non-zero return will stop iteration. */ +SFUNC size_t fio_http_cookie_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); + +/** + * Iterates through all response set cookies. + * + * A non-zero return value from the callback will stop iteration. + */ +SFUNC size_t +fio_http_set_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s set_cookie_header, + fio_str_info_s value, + void *udata), + void *udata); + +/* ***************************************************************************** +Responding to an HTTP event. +***************************************************************************** */ + +/** Returns true if no HTTP headers / data was sent (a clean slate). */ +SFUNC int fio_http_is_clean(fio_http_s *); + +/** Returns true if the HTTP handle's response was sent. */ +SFUNC int fio_http_is_finished(fio_http_s *); + +/** Returns true if the HTTP handle's response is streaming. */ +SFUNC int fio_http_is_streaming(fio_http_s *); + +/** Returns true if the HTTP connection was (or should have been) upgraded. */ +SFUNC int fio_http_is_upgraded(fio_http_s *h); + +/** Returns true if the HTTP handle refers to a WebSocket connection. */ +SFUNC int fio_http_is_websocket(fio_http_s *); + +/** Returns true if the HTTP handle refers to an EventSource connection. */ +SFUNC int fio_http_is_sse(fio_http_s *); + +/** Returns true if handle is in the process of freeing itself. */ +SFUNC int fio_http_is_freeing(fio_http_s *); + +/** + * Gets the header information associated with the HTTP handle. + * + * Since more than a single value may be associated with a header name, the + * index may be used to collect subsequent values. + * + * An empty value is returned if no header value is available (or index is + * exceeded). + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header(fio_http_s *, + fio_str_info_s name, + size_t index); +/** + * Returns the number of headers named `name` in the response. + * + * If `name` buffer is `NULL`, returns the number of unique headers (not the + * number of unique values). + */ +SFUNC size_t fio_http_response_header_count(fio_http_s *, fio_str_info_s name); + +/** + * Sets the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header_set(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); +/** + * Sets the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s +fio_http_response_header_set_if_missing(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); + +/** + * Adds to the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header_add(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); + +/** + * Iterates through all response headers (except cookies!). + * + * A non-zero return will stop iteration. + * */ +SFUNC size_t fio_http_response_header_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); + +/** Arguments for the fio_http_write function. */ +typedef struct fio_http_write_args_s { + /** The data to be written. */ + const void *buf; + /** The length of the data to be written. */ + size_t len; + /** The offset at which writing should begin. */ + size_t offset; + /** If streaming a file, set this value. The file is always closed. */ + int fd; + /** If the data is a buffer, this callback may be set to free it once sent. */ + void (*dealloc)(void *); + /** If the data is a buffer / a file - should it be copied? */ + int copy; + /** + * If `finish` is set, this data marks the end of the response. + * + * Otherwise the response will stream the data. + */ + int finish; +} fio_http_write_args_s; + +/** + * Writes `data` to the response body associated with the HTTP handle after + * sending all headers (no further headers may be sent). + */ +SFUNC void fio_http_write(fio_http_s *, fio_http_write_args_s args); + +/** Named arguments helper. See fio_http_write and fio_http_write_args_s. */ +#define fio_http_write(http_handle, ...) \ + fio_http_write(http_handle, (fio_http_write_args_s){__VA_ARGS__}) +#define fio_http_finish(http_handle) fio_http_write(http_handle, .finish = 1) + +/** Closes a persistent HTTP connection (i.e., if upgraded). */ +SFUNC void fio_http_close(fio_http_s *h); + +/* ***************************************************************************** +WebSocket / SSE Helpers +***************************************************************************** */ + +/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ +SFUNC int fio_http_websocket_requested(fio_http_s *); + +/** Returns non-zero if the response accepts a WebSocket upgrade request. */ +SFUNC int fio_http_websocket_accepted(fio_http_s *h); + +/** Sets response data to agree to a WebSockets Upgrade.*/ +SFUNC void fio_http_upgrade_websocket(fio_http_s *); + +/** Sets request data to request a WebSockets Upgrade.*/ +SFUNC void fio_http_websocket_set_request(fio_http_s *); + +/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ +SFUNC int fio_http_sse_requested(fio_http_s *); + +/** Returns non-zero if the response accepts an SSE request. */ +SFUNC int fio_http_sse_accepted(fio_http_s *h); + +/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_upgrade_sse(fio_http_s *); + +/** Sets request data to request an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_sse_set_request(fio_http_s *); + +/* ***************************************************************************** +MIME File Type Helpers - NOT thread safe! +***************************************************************************** */ + +/** Registers a Mime-Type to be associated with the file extension. */ +SFUNC int fio_http_mimetype_register(char *file_ext, + size_t file_ext_len, + fio_str_info_s mime_type); + +/** Finds the Mime-Type associated with the file extension (if registered). */ +SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len); + +/* ***************************************************************************** +HTTP Body Parsing Helpers (TODO!) +***************************************************************************** */ + +/* ***************************************************************************** +Header Parsing Helpers +***************************************************************************** */ + +/** + * Copies all header data, from possibly an array of identical response headers, + * resulting in a parsed format outputted to `buf_parsed`. + * + * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough + * for the parsed output). + * + * Note that the parsed output isn't readable as a string, but is designed to + * work with the `FIO_HTTP_PARSED_HEADER_EACH` and + * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * + * See also `fio_http_response_header_parse`. + */ +SFUNC int fio_http_response_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name); + +/** + * Copies all header data, from possibly an array of identical response headers, + * resulting in a parsed format outputted to `buf_parsed`. + * + * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough + * for the parsed output). + * + * Note that the parsed output isn't readable as a string, but is designed to + * work with the `FIO_HTTP_PARSED_HEADER_EACH` and + * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * + * i.e.: + * + * ```c + * FIO_STR_INFO_TMP_VAR(buf, 1023); // tmp buffer for the parsed output + * fio_http_s *h = fio_http_new(); // using a mock HTTP handle + * fio_http_request_header_add( + * h, + * FIO_STR_INFO2("accept", 6), + * FIO_STR_INFO1("text/html, application/json;q=0.9; d=500, image/png")); + * fio_http_request_header_add(h, + * FIO_STR_INFO2("accept", 6), + * FIO_STR_INFO1("text/yaml")); + * FIO_ASSERT( // in production do NOT assert, but route to error instead! + * !fio_http_request_header_parse(h, &buf, FIO_STR_INFO2("accept", 6)), + * "parse returned error!"); + * FIO_HTTP_PARSED_HEADER_EACH(buf, value) { + * printf("* processing value (%zu bytes): %s\n", value.len, value.buf); + * FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value, prop) { + * printf("* for value %s: (%zu,%zu bytes) %s = %s\n", + * value.buf, + * prop.name.len, + * prop.value.len, + * prop.name.buf, + * prop.value.buf); + * } + * } + * ``` + */ +SFUNC int fio_http_request_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name); + +/** + * Parses header for multiple values and properties and iterates over all + * values. + * + * This MACRO will allocate 2048 bytes on the stack for parsing the header + * values and properties, if more space is necessary dig deeper. + * + * Use FIO_HTTP_HEADER_VALUE_EACH_PROPERTY to iterate over a value's properties. + */ +#define FIO_HTTP_HEADER_EACH_VALUE(/* fio_http_s */ http_handle, \ + /* int / bool */ is_request, \ + /* fio_str_info_s */ header_name, \ + /* chosen var named */ value) \ + for (char fio___buf__##value##__[2048], /* allocate buffer on stack */ \ + *fio___buf__##value##_ptr = NULL; \ + !fio___buf__##value##_ptr; \ + fio___buf__##value##_ptr = fio___buf__##value##__) \ + for (fio_str_info_s fio___buf__##value##__str = /* declare buffer var */ \ + FIO_STR_INFO3(fio___buf__##value##__, 0, 2048); \ + fio___buf__##value##__str.buf == fio___buf__##value##__; \ + fio___buf__##value##__str.buf = fio___buf__##value##__ + 1) \ + if (!((is_request ? fio_http_request_header_parse \ + : fio_http_response_header_parse)( \ + http_handle, /* parse headers */ \ + &fio___buf__##value##__str, \ + header_name))) \ + FIO_HTTP_PARSED_HEADER_EACH(fio___buf__##value##__str, value) /* loop \ + */ + +/** Iterated through the properties associated with a parsed header values. */ +#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(/* fio_str_info_s */ value, \ + /* chosen var named */ property) + +/** Used internally to iterate over a parsed header buffer. */ +#define FIO_HTTP_PARSED_HEADER_EACH(/* fio_str_info_s */ buf_parsed, \ + /* chosen var named */ value) + +/* ***************************************************************************** +General Helpers +***************************************************************************** */ + +/** Sends the requested error message and finishes the response. */ +SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status); + +/** Returns true (1) if the ETag response matches an if-none-match request. */ +SFUNC int fio_http_etag_is_match(fio_http_s *h); + +/** + * Attempts to send a static file from the `root` folder. On success the + * response is complete and 0 is returned. Otherwise returns -1. + */ +SFUNC int fio_http_static_file_response(fio_http_s *h, + fio_str_info_s root_folder, + fio_str_info_s file_name, + size_t max_age); + +/** Returns a human readable string related to the HTTP status number. */ +SFUNC fio_str_info_s fio_http_status2str(size_t status); + +/** Logs an HTTP (response) to STDOUT. */ +SFUNC void fio_http_write_log(fio_http_s *h); + +/** + * Writes peer address to `dest` starting with the `forwarded` header, with a + * fallback to actual socket address and a final fallback to `"[unknown]"`. + * + * If `unknown` is returned, the function returns -1. if `dest` capacity is too + * small, the number of bytes required will be returned. + * + * If all goes well, this function returns 0. + */ +SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h); + +/* date/time string caching for HTTP date header */ +SFUNC fio_str_info_s fio_http_date(uint64_t now_in_seconds); + +/* date/time string caching for HTTP logging */ +SFUNC fio_str_info_s fio_http_log_time(uint64_t now_in_seconds); +/* ***************************************************************************** +The HTTP Controller +***************************************************************************** */ + +/** + * The HTTP Controller manages all the callbacks required by the HTTP Handler in + * order for HTTP responses and requests to be sent. + */ +struct fio_http_controller_s { + /* MUST be initialized to zero, used internally by the HTTP Handle. */ + uintptr_t private_flags; + /** Called when an HTTP handle is freed. */ + void (*on_destroyed)(fio_http_s *h); + /** Informs the controller that request / response headers must be sent. */ + void (*send_headers)(fio_http_s *h); + /** called by the HTTP handle for each body chunk (or to finish a response. */ + void (*write_body)(fio_http_s *h, fio_http_write_args_s args); + /** called once a request / response had finished */ + void (*on_finish)(fio_http_s *h); + /** called to close an HTTP connection */ + void (*close_io)(fio_http_s *h); + /** called when the file descriptor is directly required */ + int (*get_fd)(fio_http_s *h); +}; + +/* ***************************************************************************** +HTTP Handle Implementation - inlined static functions +***************************************************************************** */ + +#define FIO___HTTP_GETSET_PTR(type, name, index_, pre_set_code) \ + /** Used internally to set / get the propecrty at its known pointer index. \ + */ \ + FIO_IFUNC type *fio_http_##name(fio_http_s *h) { \ + return ((type **)h)[index_]; \ + } \ + /** Used internally to set / get the propercty at its known pointer index. \ + */ \ + FIO_IFUNC type *fio_http_##name##_set(fio_http_s *h, type *ptr) { \ + pre_set_code; \ + return (((type **)h)[index_] = ptr); \ + } + +SFUNC fio_http_controller_s *fio___http_controller_validate( + fio_http_controller_s *c); + +/* Create fio_http_udata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, udata, 0, (void)0) +/* Create fio_http_pdata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, udata2, 1, (void)0) +/* Create fio_http_cdata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, cdata, 2, (void)0) +/* Create fio_http_controller_(get|set) functions */ +FIO___HTTP_GETSET_PTR(fio_http_controller_s, + controller, + 3, + ptr = fio___http_controller_validate(ptr)) + +#undef FIO___HTTP_GETSET_PTR +/* +REMEMBER: +======== + +All memory allocations should use: +* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE_(ptr, size) + +*/ + +/* ***************************************************************************** +Header Parsing Helpers - inlined helpers +***************************************************************************** */ + +#define FIO___HTTP_PARSED_HEADER_VALUE 0 +#define FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN 1 +#define FIO___HTTP_PARSED_HEADER_PROPERTY_DATA 2 + +typedef struct { + fio_str_info_s name; + fio_str_info_s value; +} fio___http_header_property_s; + +/** + * Assumes a Buffer of bytes containing length info and string data as such: + * [ NUL byte - 1 byte at head of format ] + * repeat + * [ 2 byte info: (type | (len << 2)) ] + * [ Optional 2 byte info: (len << 2) (if type was 1)] + * [ String of `len` bytes][ NUL byte (not counted in `len`)] + */ + +FIO_IFUNC fio_str_info_s fio___http_parsed_headers_next(fio_str_info_s value) { + for (;;) { + const size_t coded = (size_t)fio_buf2u16u(value.buf + value.len + 1U); + if (!coded) + return (value = (fio_str_info_s){0}); + const size_t block_len = coded >> 2; + value.buf += value.len + 3; + value.len = block_len; + if (!(coded & 3)) + return value; + value.buf -= 3; /* reposition to read NUL + value rather than text start */ + } +} + +FIO_IFUNC fio___http_header_property_s +fio___http_parsed_property_next(fio___http_header_property_s property) { + for (;;) { + size_t coded = + (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); + if (!(coded & 3)) + return (property = (fio___http_header_property_s){{0}, {0}}); + if ((coded & 3) == FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN) { + property.value.buf += 2; + coded = (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); + } + if ((coded & 3) != 2) + return (property = (fio___http_header_property_s){{0}, {0}}); + coded >>= 2; + property.name.buf = property.value.buf + property.value.len + 3; + property.name.len = coded; + coded = (size_t)fio_buf2u16u(property.name.buf + property.name.len + 1); + FIO_ASSERT_DEBUG((coded & 3) == 2, + "header property value parsing format error"); + property.value.buf = property.name.buf + property.name.len + 3; + property.value.len = coded >> 2; + return property; + } +} + +#undef FIO_HTTP_PARSED_HEADER_EACH +#define FIO_HTTP_PARSED_HEADER_EACH(buf_parsed, value) \ + for (fio_str_info_s value = \ + fio___http_parsed_headers_next(FIO_STR_INFO2(buf_parsed.buf, 0)); \ + value.len; \ + value = fio___http_parsed_headers_next(value)) + +#undef FIO_HTTP_HEADER_VALUE_EACH_PROPERTY +#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value_, property) \ + for (fio___http_header_property_s property = \ + fio___http_parsed_property_next( \ + (fio___http_header_property_s){.value = value_}); \ + property.name.len; \ + property = fio___http_parsed_property_next(property)) + +/* ***************************************************************************** +HTTP Handle Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Helpers - reading time +***************************************************************************** */ + +#if FIO_HTTP_EXACT_LOGGING +#define FIO___HTTP_TIME_DIV 1000000 +#define FIO___HTTP_TIME_UNIT "us" +FIO_IFUNC int64_t fio_http_get_timestump(void) { + return fio_time2micro(fio_time_real()); +} +#else +#define FIO___HTTP_TIME_DIV 1000 +#define FIO___HTTP_TIME_UNIT "ms" +#define fio_http_get_timestump() fio_io_last_tick() +#endif + +/* date/time string caching for HTTP date header */ +SFUNC fio_str_info_s fio_http_date(uint64_t now_in_seconds) { + static char date_buf[128]; + static size_t date_len; + static uint64_t date_buf_val; + if (date_buf_val == now_in_seconds) + return FIO_STR_INFO2(date_buf, date_len); + date_len = fio_time2rfc7231(date_buf, now_in_seconds); + date_buf[date_len] = 0; + date_buf_val = now_in_seconds; + return FIO_STR_INFO2(date_buf, date_len); +} + +/* date/time string caching for HTTP logging */ +SFUNC fio_str_info_s fio_http_log_time(uint64_t now_in_seconds) { + static char date_buf[128]; + static size_t date_len; + static uint64_t date_buf_val; + if (date_buf_val == now_in_seconds) + return FIO_STR_INFO2(date_buf, date_len); + date_len = fio_time2log(date_buf, now_in_seconds); + date_buf[date_len] = 0; + date_buf_val = now_in_seconds; + return FIO_STR_INFO2(date_buf, date_len); +} + +/* ***************************************************************************** +Helpers - fio_keystr_s memory allocation callbacks +***************************************************************************** */ + +FIO_LEAK_COUNTER_DEF(http___keystr_allocator) + +FIO_SFUNC void fio___http_keystr_free(void *ptr, size_t len) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(http___keystr_allocator); + FIO_MEM_FREE_(ptr, len); + (void)len; /* if unused */ +} +FIO_SFUNC void *fio___http_keystr_alloc(size_t capa) { + FIO_LEAK_COUNTER_ON_ALLOC(http___keystr_allocator); + return FIO_MEM_REALLOC_(NULL, 0, capa, 0); +} + +/* ***************************************************************************** +Helper Types +***************************************************************************** */ +#define FIO___RECURSIVE_INCLUDE 1 +/* ***************************************************************************** +String Cache +***************************************************************************** */ + +#define FIO_MAP_NAME fio___http_str_cache +#define FIO_MAP_LRU FIO_HTTP_CACHE_LIMIT +#define FIO_MAP_KEY_BSTR +#include FIO_INCLUDE_FILE + +static struct { + fio___http_str_cache_s cache; + FIO___LOCK_TYPE lock; +} FIO___HTTP_STRING_CACHE[2] = {{.lock = FIO___LOCK_INIT}, + {.lock = FIO___LOCK_INIT}}; +#define FIO___HTTP_STR_CACHE_COOKIE 0 +#define FIO___HTTP_STR_CACHE_VALUE 1 + +#if FIO_HTTP_PRE_CACHE_KNOWN_HEADERS + +#define FIO___HTTP_STATIC_CACHE_CAPA_BITS 7 +#define FIO___HTTP_STATIC_CACHE_CAPA (1U << FIO___HTTP_STATIC_CACHE_CAPA_BITS) +#define FIO___HTTP_STATIC_CACHE_MASK (FIO___HTTP_STATIC_CACHE_CAPA - 1) +#define FIO___HTTP_STATIC_CACHE_STEP_LIMIT 4 + +typedef struct FIO___HTTP_STATIC_CACHE_T { + fio___bstr_meta_s meta; + char str[32]; +} FIO___HTTP_STATIC_CACHE_T; +static FIO___HTTP_STATIC_CACHE_T FIO___HTTP_STATIC_CACHE[] = { +#define FIO___HTTP_STATIC_CACHE_SET(s) \ + { \ + .meta = {.len = (uint32_t)(sizeof(s) - 1), .ref = ((uint32_t)(~0) >> 8)}, \ + .str = s \ + } + FIO___HTTP_STATIC_CACHE_SET("a-im"), + FIO___HTTP_STATIC_CACHE_SET("accept"), + FIO___HTTP_STATIC_CACHE_SET("accept-charset"), + FIO___HTTP_STATIC_CACHE_SET("accept-datetime"), + FIO___HTTP_STATIC_CACHE_SET("accept-encoding"), + FIO___HTTP_STATIC_CACHE_SET("accept-language"), + FIO___HTTP_STATIC_CACHE_SET("accept-ranges"), + FIO___HTTP_STATIC_CACHE_SET("access-control-allow-origin"), + FIO___HTTP_STATIC_CACHE_SET("access-control-request-headers"), + FIO___HTTP_STATIC_CACHE_SET("access-control-request-method"), + FIO___HTTP_STATIC_CACHE_SET("age"), + FIO___HTTP_STATIC_CACHE_SET("allow"), + FIO___HTTP_STATIC_CACHE_SET("authorization"), + FIO___HTTP_STATIC_CACHE_SET("cache-control"), + FIO___HTTP_STATIC_CACHE_SET("connection"), + FIO___HTTP_STATIC_CACHE_SET("content-disposition"), + FIO___HTTP_STATIC_CACHE_SET("content-encoding"), + FIO___HTTP_STATIC_CACHE_SET("content-language"), + FIO___HTTP_STATIC_CACHE_SET("content-length"), + FIO___HTTP_STATIC_CACHE_SET("content-location"), + FIO___HTTP_STATIC_CACHE_SET("content-range"), + FIO___HTTP_STATIC_CACHE_SET("content-type"), + FIO___HTTP_STATIC_CACHE_SET("cookie"), + FIO___HTTP_STATIC_CACHE_SET("date"), + FIO___HTTP_STATIC_CACHE_SET("dnt"), + FIO___HTTP_STATIC_CACHE_SET("etag"), + FIO___HTTP_STATIC_CACHE_SET("expect"), + FIO___HTTP_STATIC_CACHE_SET("expires"), + FIO___HTTP_STATIC_CACHE_SET("forwarded"), + FIO___HTTP_STATIC_CACHE_SET("from"), + FIO___HTTP_STATIC_CACHE_SET("host"), + FIO___HTTP_STATIC_CACHE_SET("if-match"), + FIO___HTTP_STATIC_CACHE_SET("if-modified-since"), + FIO___HTTP_STATIC_CACHE_SET("if-none-match"), + FIO___HTTP_STATIC_CACHE_SET("if-range"), + FIO___HTTP_STATIC_CACHE_SET("if-unmodified-since"), + FIO___HTTP_STATIC_CACHE_SET("last-modified"), + FIO___HTTP_STATIC_CACHE_SET("link"), + FIO___HTTP_STATIC_CACHE_SET("location"), + FIO___HTTP_STATIC_CACHE_SET("max-forwards"), + FIO___HTTP_STATIC_CACHE_SET("origin"), + FIO___HTTP_STATIC_CACHE_SET("pragma"), + FIO___HTTP_STATIC_CACHE_SET("proxy-authenticate"), + FIO___HTTP_STATIC_CACHE_SET("proxy-authorization"), + FIO___HTTP_STATIC_CACHE_SET("range"), + FIO___HTTP_STATIC_CACHE_SET("referer"), + FIO___HTTP_STATIC_CACHE_SET("refresh"), + FIO___HTTP_STATIC_CACHE_SET("retry-after"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-mobile"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-platform"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-dest"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-mode"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-site"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-user"), + FIO___HTTP_STATIC_CACHE_SET("server"), + FIO___HTTP_STATIC_CACHE_SET("set-cookie"), + FIO___HTTP_STATIC_CACHE_SET("strict-transport-security"), + FIO___HTTP_STATIC_CACHE_SET("te"), + FIO___HTTP_STATIC_CACHE_SET("transfer-encoding"), + FIO___HTTP_STATIC_CACHE_SET("upgrade"), + FIO___HTTP_STATIC_CACHE_SET("upgrade-insecure-requests"), + FIO___HTTP_STATIC_CACHE_SET("user-agent"), + FIO___HTTP_STATIC_CACHE_SET("vary"), + FIO___HTTP_STATIC_CACHE_SET("via"), + FIO___HTTP_STATIC_CACHE_SET("warning"), + FIO___HTTP_STATIC_CACHE_SET("www-authenticate"), + FIO___HTTP_STATIC_CACHE_SET("x-csrf-token"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-for"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-host"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-proto"), + FIO___HTTP_STATIC_CACHE_SET("x-requested-with"), + {{0}}}; + +static uint32_t FIO___HTTP_STATIC_CACHE_IMAP[FIO___HTTP_STATIC_CACHE_CAPA]; + +static uint64_t fio___http_str_cached_hash(char *str, size_t len) { + /* use low-case hash specific for the HTTP handle (change resilient) */ + const fio_u256 primes = fio_u256_init64(FIO_U64_HASH_PRIME1, + FIO_U64_HASH_PRIME2, + FIO_U64_HASH_PRIME3, + FIO_U64_HASH_PRIME4); + uint64_t hash = 0; + if (len > 32) + return hash; + fio_u512 s = {0}; + fio_memcpy63x(s.u8, str, len); + for (size_t i = 0; i < 4; ++i) + s.u64[i] = fio_ltole64(s.u64[i]); +#if 0 + fio_u256_cor64(s.u256, s.u256, 0x2020202020202020ULL); /* lower case hash */ + fio_u256_cadd16(s.u256, s.u256, len); + for (size_t i = 0; i < 4; ++i) + s.u64[i] = fio_math_mulc64(s.u64[i], primes.u64[i], s.u64 + 4 + i); + hash = fio_u256_reduce_add64(s.u256 + 1); + hash ^= fio_u256_reduce_add64(s.u256); + fio_u256_cxor64(s.u256, s.u256, hash); + hash += fio_u256_reduce_add64(s.u256); +#else + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], 0x2020202020202020ULL, 64, |); + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], (uint16_t)len, 16, +); + for (size_t i = 0; i < 4; ++i) { + s.u64[i] = fio_math_mulc64(s.u64[i], primes.u64[i], s.u64 + 4 + i); + } + uint64_t tmp; + FIO_MATH_UXXX_REDUCE(hash, s.u256[1], 64, +); + FIO_MATH_UXXX_REDUCE(tmp, s.u256[0], 64, +); + hash ^= tmp; + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], hash, 64, ^); + FIO_MATH_UXXX_REDUCE(tmp, s.u256[0], 64, +); + hash += tmp; +#endif + // hash += hash >> 4; + hash += hash >> 5; + return hash; +} + +#ifndef FIO___HTTP_STATIC_CACHE_CMP_SECURE +#define FIO___HTTP_STATIC_CACHE_CMP_SECURE 0 +#endif +static bool fio___http_str_cached_cmp(void *arry, void *obj, uint32_t indx) { + FIO___HTTP_STATIC_CACHE_T *ary = (FIO___HTTP_STATIC_CACHE_T *)arry; + if (indx > + ((sizeof(FIO___HTTP_STATIC_CACHE) / sizeof(FIO___HTTP_STATIC_CACHE[0])) - + 1)) + return 0; + fio_buf_info_s *s = (fio_buf_info_s *)obj; + fio_buf_info_s t = FIO_BUF_INFO2(ary[indx].str, ary[indx].meta.len); +#if FIO___HTTP_STATIC_CACHE_CMP_SECURE + return FIO_BUF_INFO_IS_EQ((*s), t); +#else + return s[0].len == t.len && s[0].buf[0] == (t.buf[0] | 32); +#endif +} +#undef FIO___HTTP_STATIC_CACHE_CMP_SECURE + +static char *fio___http_str_cached_static(char *str, size_t len) { + if (len >= 32) + return NULL; + fio_buf_info_s obj = FIO_BUF_INFO2(str, len); + uint64_t hash = fio___http_str_cached_hash(obj.buf, obj.len); + fio___imap32_seeker_s pos = + fio___imap32_seek((void *)FIO___HTTP_STATIC_CACHE, + FIO___HTTP_STATIC_CACHE_IMAP, + FIO___HTTP_STATIC_CACHE_CAPA_BITS, + (void *)&obj, + (uint32_t)hash, + fio___http_str_cached_cmp, + FIO___HTTP_STATIC_CACHE_STEP_LIMIT); + if (!pos.is_valid) + return NULL; + ++FIO___HTTP_STATIC_CACHE[pos.pos].meta.ref; + return FIO___HTTP_STATIC_CACHE[pos.pos].str; +} + +static void fio___http_str_cached_init(void) { + FIO_MEMSET(FIO___HTTP_STATIC_CACHE_IMAP, + 0, + sizeof(FIO___HTTP_STATIC_CACHE_IMAP)); + + for (size_t i = 0; FIO___HTTP_STATIC_CACHE[i].meta.ref; ++i) { + fio_buf_info_s obj = FIO_BUF_INFO2(FIO___HTTP_STATIC_CACHE[i].str, + FIO___HTTP_STATIC_CACHE[i].meta.len); + uint64_t hash = fio___http_str_cached_hash(obj.buf, obj.len); + fio___imap32_seeker_s pos = + fio___imap32_seek((void *)FIO___HTTP_STATIC_CACHE, + FIO___HTTP_STATIC_CACHE_IMAP, + FIO___HTTP_STATIC_CACHE_CAPA_BITS, + (void *)&obj, + (uint32_t)hash, + fio___http_str_cached_cmp, + FIO___HTTP_STATIC_CACHE_STEP_LIMIT); + FIO_ASSERT(!pos.is_valid && pos.ipos < FIO___HTTP_STATIC_CACHE_CAPA && + !FIO___HTTP_STATIC_CACHE_IMAP[pos.ipos], + "HTTP static cache collision / overflow @ %zu (%s)", + i, + obj.buf); + + pos.set_val |= i; + fio___imap32_set(FIO___HTTP_STATIC_CACHE_IMAP, pos.ipos, pos.set_val); + + FIO_ASSERT(fio___http_str_cached_static(obj.buf, obj.len) == obj.buf, + "HTTP static cache initialization round-trip error @ %zu (%s)", + i, + obj.buf); + } +} + +#undef FIO___HTTP_STATIC_CACHE_CAPA_BITS +#undef FIO___HTTP_STATIC_CACHE_CAPA +#undef FIO___HTTP_STATIC_CACHE_MASK +#undef FIO___HTTP_STATIC_CACHE_STEP_LIMIT +#else +#define fio___http_str_cached_init() (void)0 +#endif /* FIO_HTTP_PRE_CACHE_KNOWN_HEADERS */ + +FIO_IFUNC char *fio___http_str_cached_inner(size_t group, + uint64_t hash, + fio_str_info_s s) { +#if !FIO_HTTP_CACHE_LIMIT + return fio_bstr_write(NULL, s.buf, s.len); +#endif + fio_str_info_s cached; + hash ^= (uint64_t)(uintptr_t)fio_http_new; +#if FIO_HTTP_CACHE_USES_MUTEX + FIO___LOCK_LOCK(FIO___HTTP_STRING_CACHE[group].lock); +#endif + cached = + fio___http_str_cache_set_if_missing(&FIO___HTTP_STRING_CACHE[group].cache, + hash, + s); +#if FIO_HTTP_CACHE_USES_MUTEX + FIO___LOCK_UNLOCK(FIO___HTTP_STRING_CACHE[group].lock); +#endif + return fio_bstr_copy(cached.buf); +} +FIO_IFUNC char *fio___http_str_cached(size_t group, fio_str_info_s s) { +#if !FIO_HTTP_CACHE_LIMIT + return fio_bstr_write(NULL, s.buf, s.len); +#endif + if (!s.len) + return NULL; + if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) + goto avoid_caching; + return fio___http_str_cached_inner(group, fio_risky_hash(s.buf, s.len, 0), s); +avoid_caching: + return fio_bstr_write(NULL, s.buf, s.len); +} + +FIO_IFUNC char *fio___http_str_cached_with_static(fio_str_info_s s) { +#if FIO_HTTP_PRE_CACHE_KNOWN_HEADERS + char *tmp; + if (!s.len) + return NULL; + if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) + goto skip_cache_test; + tmp = fio___http_str_cached_static(s.buf, s.len); + if (tmp) { + FIO_LOG_DDEBUG2("(%d) using statically cached header: %s", + fio_io_pid(), + tmp); + return tmp; /* reference count increased by fio___http_str_cached_static */ + } +skip_cache_test: +#endif /* FIO_HTTP_PRE_CACHE_KNOWN_HEADERS */ + return fio_bstr_write(NULL, s.buf, s.len); +} + +/* ***************************************************************************** +Headers Maps +***************************************************************************** */ + +#define FIO_ARRAY_NAME fio___http_sary +#define FIO_ARRAY_TYPE char * +#define FIO_ARRAY_TYPE_DESTROY(obj) fio_bstr_free(obj) + +#define FIO_MAP_NAME fio___http_hmap +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio___http_str_cached_with_static((src)) +#define FIO_MAP_KEY_DISCARD(key) +#define FIO_MAP_VALUE fio___http_sary_s +#define FIO_MAP_VALUE_COPY(a, b) \ + do { \ + (a) = (fio___http_sary_s)FIO_ARRAY_INIT; \ + (void)(b); \ + } while (0) /*no-op*/ +#define FIO_MAP_VALUE_DESTROY(o) fio___http_sary_destroy(&(o)) +#define FIO_MAP_HASH_FN(k) \ + fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_sary_destroy) +#include FIO_INCLUDE_FILE + +#if FIO_HTTP_ENFORCE_LOWERCASE_HEADERS +#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ + FIO_STR_INFO_TMP_VAR(var_name, 4096); \ + fio___http_hmap_key_to_lower(&var_name, &inpute_var); + +/** Converts a Header key to lower-case */ +FIO_IFUNC void fio___http_hmap_key_to_lower(fio_str_info_s *t, + fio_str_info_s *k) { + if (k->len >= t->capa) + goto too_big; + for (size_t i = 0; i < k->len; ++i) { + uint8_t c = (uint8_t)k->buf[i]; + c |= (uint8_t)(c >= 'A' || c <= 'Z') << 5; + t->buf[i] = c; + } + t->len = k->len; + return; +too_big: + *t = *k; +} + +#else +#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ + fio_str_info_s var_name = inpute_var; +#endif + +/** set `add` to positive to add multiple values or negative to overwrite. */ +FIO_IFUNC fio_str_info_s fio___http_hmap_set2(fio___http_hmap_s *map, + fio_str_info_s key_input, + fio_str_info_s val, + int add) { + fio_str_info_s r = {0}; + if (!key_input.buf || !key_input.len || !map) + return r; + /* make sure key is all lower-case? */ + FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); + fio___http_sary_s *o; + if (!val.buf || !val.len) + goto remove_key; + o = fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!o) { + fio___http_sary_s va = {0}; + o = fio___http_hmap_node2val_ptr( + fio___http_hmap_set_ptr(map, key, va, NULL, 1)); + add = 1; + } + if (FIO_UNLIKELY(!o)) { + FIO_LOG_ERROR("Couldn't add value to header: %.*s:%.*s", + (int)key.len, + key.buf, + (int)val.len, + val.buf); + return r; + } + if (add) { + if (add < 0) { + fio___http_sary_destroy(o); + } + r = fio_bstr_info(fio___http_str_cached(FIO___HTTP_STR_CACHE_VALUE, val)); + fio___http_sary_push(o, r.buf); + return r; + } + r = fio_bstr_info(fio___http_sary_get(o, -1)); + return r; + +remove_key: + if (add < 1) + fio___http_hmap_remove(map, key, NULL); + return r; +} + +FIO_IFUNC fio_str_info_s fio___http_hmap_get2(fio___http_hmap_s *map, + fio_str_info_s key_input, + int32_t index) { + fio_str_info_s r = {0}; + FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!a) + return r; + const uint32_t count = fio___http_sary_count(a); + if (!count) + return r; + if (index < 0) { + index += count; + if (index < 0) + index = 0; + } + if ((uint32_t)index >= count) + return r; + r = fio_bstr_info(fio___http_sary_get(a, index)); + return r; +} + +FIO_IFUNC size_t fio___http_hmap_count2(fio___http_hmap_s *map, + fio_str_info_s key) { + size_t r = 0; + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!a) + return r; + r = fio___http_sary_count(a); + return r; +} + +/* ***************************************************************************** +Header iteration Task +***************************************************************************** */ + +typedef struct { + fio_http_s *h; + int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *); + void *udata; +} fio___http_hmap_each_info_s; + +FIO_SFUNC int fio___http_h_each_task_wrapper(fio___http_hmap_each_s *e) { + fio___http_hmap_each_info_s *data = (fio___http_hmap_each_info_s *)(e->udata); + FIO_ARRAY_EACH(fio___http_sary, &e->value, pos) { + if (data->callback(data->h, e->key, fio_bstr_info(*pos), data->udata) == -1) + return -1; + } + return 0; +} + +/* ***************************************************************************** +Cookie Maps +***************************************************************************** */ + +#define FIO_MAP_NAME fio___http_cmap /* cached names */ +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio___http_str_cached(FIO___HTTP_STR_CACHE_COOKIE, (src)) +#define FIO_MAP_KEY_DISCARD(key) + +#define FIO_MAP_VALUE_BSTR /* not cached */ +#define FIO_MAP_HASH_FN(k) \ + fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_cmap_destroy) +#include FIO_INCLUDE_FILE + +/* ***************************************************************************** +Controller Validation +***************************************************************************** */ + +FIO_SFUNC int fio___mock_controller_get_fd_cb(fio_http_s *h) { + return -1; + (void)h; +} +FIO_SFUNC void fio___mock_controller_cb(fio_http_s *h) { (void)h; } +FIO_SFUNC void fio___mock_c_write_body(fio_http_s *h, + fio_http_write_args_s args) { + if (args.buf) { + if (args.dealloc) + args.dealloc((void *)args.buf); + } else if ((unsigned)(args.fd + 1) > 1U && !args.copy && + args.fd != fio_http_body_fd(h)) { + close(args.fd); + } + (void)h; +} + +static fio_http_controller_s FIO___MOCK_CONTROLLER = { + .on_destroyed = fio___mock_controller_cb, + .send_headers = fio___mock_controller_cb, + .write_body = fio___mock_c_write_body, + .on_finish = fio___mock_controller_cb, + .close_io = fio___mock_controller_cb, + .get_fd = fio___mock_controller_get_fd_cb, +}; + +SFUNC fio_http_controller_s *fio___http_controller_validate( + fio_http_controller_s *c) { + if (!c) + c = &FIO___MOCK_CONTROLLER; + if (c->private_flags) + return c; + if (!c->on_destroyed) + c->on_destroyed = fio___mock_controller_cb; + if (!c->send_headers) + c->send_headers = fio___mock_controller_cb; + if (!c->write_body) + c->write_body = fio___mock_c_write_body; + if (!c->on_finish) + c->on_finish = fio___mock_controller_cb; + if (!c->close_io) + c->close_io = fio___mock_controller_cb; + if (!c->get_fd) + c->get_fd = fio___mock_controller_get_fd_cb; + return c; +} + +/* ***************************************************************************** +HTTP Handle Type +***************************************************************************** */ + +#define FIO_HTTP_STATE_STREAMING 1 +#define FIO_HTTP_STATE_FINISHED 2 +#define FIO_HTTP_STATE_UPGRADED 4 +#define FIO_HTTP_STATE_WEBSOCKET 8 +#define FIO_HTTP_STATE_SSE 16 +#define FIO_HTTP_STATE_COOKIES_PARSED 32 +#define FIO_HTTP_STATE_FREEING 64 + +FIO_SFUNC int fio____http_write_start(fio_http_s *, fio_http_write_args_s *); +FIO_SFUNC int fio____http_write_cont(fio_http_s *, fio_http_write_args_s *); + +struct fio_http_s { + void *udata; + void *udata2; + void *cdata; + fio_http_controller_s *controller; + int (*writer)(fio_http_s *, fio_http_write_args_s *); + int64_t received_at; + size_t sent; + uint32_t state; + uint32_t status; + fio_keystr_s method; + fio_keystr_s path; + fio_keystr_s query; + fio_keystr_s version; + fio___http_hmap_s headers[2]; /* request, response */ + fio___http_cmap_s cookies[2]; /* read, write */ + struct { + char *buf; + size_t len; + size_t pos; + int fd; + } body; +}; + +#define HTTP_HDR_REQUEST(h) (h->headers + 0) +#define HTTP_HDR_RESPONSE(h) (h->headers + 1) + +#define FIO_REF_NAME fio_http +#define FIO_REF_INIT(h) \ + h = (fio_http_s) { \ + .controller = &FIO___MOCK_CONTROLLER, .writer = fio____http_write_start, \ + .received_at = fio_http_get_timestump(), .body.fd = -1 \ + } +#define FIO_REF_DESTROY(h) fio_http_destroy(&(h)) +SFUNC fio_http_s *fio_http_destroy(fio_http_s *h) { + if (!h) + return h; + h->state |= FIO_HTTP_STATE_FREEING; + h->controller->on_destroyed(h); + + fio_keystr_destroy(&h->method, fio___http_keystr_free); + fio_keystr_destroy(&h->path, fio___http_keystr_free); + fio_keystr_destroy(&h->query, fio___http_keystr_free); + fio_keystr_destroy(&h->version, fio___http_keystr_free); + fio___http_hmap_destroy(h->headers); + fio___http_hmap_destroy(h->headers + 1); + fio___http_cmap_destroy(h->cookies); + fio___http_cmap_destroy(h->cookies + 1); + fio_bstr_free(h->body.buf); + if (h->body.fd != -1) + close(h->body.fd); + FIO_REF_INIT(*h); + return h; +} +#include FIO_INCLUDE_FILE + +/** Clears any response data. */ +SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body) { + fio___http_hmap_destroy(HTTP_HDR_RESPONSE(h)); + h->state = 0; + h->writer = fio____http_write_start; + h->received_at = fio_http_get_timestump(); + h->status = 0; + if (!clear_body) + return h; + fio_bstr_free(h->body.buf); + if (h->body.fd != -1) + close(h->body.fd); + h->body.buf = NULL; + h->body.len = h->body.pos = 0; + h->body.fd = -1; + return h; +} + +/** Create a new http_s handle. */ +SFUNC fio_http_s *fio_http_new(void) { return fio_http_new2(); } + +/** Reduces an http_s handle's reference count or frees it. */ +SFUNC void fio_http_free(fio_http_s *h) { fio_http_free2(h); } + +/** Increases an http_s handle's reference count. */ +SFUNC fio_http_s *fio_http_dup(fio_http_s *h) { return fio_http_dup2(h); } + +/** Collects an updated timestamp for logging purposes. */ +SFUNC void fio_http_start_time_set(fio_http_s *h) { + h->received_at = fio_http_get_timestump(); +} + +/** Closes a persistent HTTP connection (i.e., if upgraded). */ +SFUNC void fio_http_close(fio_http_s *h) { h->controller->close_io(h); } + +/** Creates a copy of an existing handle, copying only its request data. */ +SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *o) { + fio_http_s *h = fio_http_new(); + FIO_ASSERT_ALLOC(h); + fio_http_path_set(h, fio_http_path(o)); + fio_http_method_set(h, fio_http_method(o)); + fio_http_query_set(h, fio_http_query(o)); + fio_http_version_set(h, fio_http_version(o)); + /* copy headers */ + fio___http_hmap_reserve(h->headers, fio___http_hmap_count(o->headers)); + FIO_MAP_EACH(fio___http_hmap, o->headers, i) { + fio___http_sary_s *a = fio___http_hmap_node2val_ptr( + fio___http_hmap_set_ptr(h->headers, + i.key, + (fio___http_sary_s){0}, + NULL, + 0)); + FIO_ARRAY_EACH(fio___http_sary, &i.value, v) { + fio___http_sary_push(a, fio_bstr_copy(*v)); + } + } + /* copy cookies */ + FIO_MAP_EACH(fio___http_cmap, o->cookies, i) { + fio___http_cmap_set(h->cookies, i.key, i.value, NULL); + } + return h; +} + +#undef FIO___RECURSIVE_INCLUDE +/* ***************************************************************************** +Simple Property Set / Get +***************************************************************************** */ + +#define HTTP___MAKE_GET_SET(property) \ + FIO_IFUNC fio_str_info_s fio_http_##property(fio_http_s *h) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ + return fio_keystr_info(&h->property); \ + } \ + \ + FIO_IFUNC fio_str_info_s fio_http_##property##_set(fio_http_s *h, \ + fio_str_info_s value) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ + fio_keystr_destroy(&h->property, fio___http_keystr_free); \ + h->property = fio_keystr_init(value, fio___http_keystr_alloc); \ + return fio_keystr_info(&h->property); \ + } + +HTTP___MAKE_GET_SET(method) +HTTP___MAKE_GET_SET(path) +HTTP___MAKE_GET_SET(query) +HTTP___MAKE_GET_SET(version) + +#undef HTTP___MAKE_GET_SET + +FIO_IFUNC_DEF_GET(fio_http, fio_http_s, size_t, status) + +/** Sets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status_set(fio_http_s *h, size_t status) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + if (status > 1023) + status = 500; + if (!status) + status = 200; + return (h->status = (uint32_t)status); +} +/* ***************************************************************************** +Handler State +***************************************************************************** */ + +SFUNC int fio_http_is_clean(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return !h->state; +} + +/** Returns true if the HTTP handle's response was sent. */ +SFUNC int fio_http_is_finished(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_FINISHED)); +} + +/** Returns true if handle is in the process of freeing itself. */ +SFUNC int fio_http_is_freeing(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_FREEING)); +} + +/** Returns true if the HTTP handle's response is streaming. */ +SFUNC int fio_http_is_streaming(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_STREAMING)); +} + +/** Returns true if the HTTP connection was (or should have been) upgraded. */ +SFUNC int fio_http_is_upgraded(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_UPGRADED)); +} + +/** Returns true if the HTTP handle establishes a WebSocket Upgrade. */ +SFUNC int fio_http_is_websocket(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_WEBSOCKET)); +} + +/** Returns true if the HTTP handle establishes an EventSource connection. */ +SFUNC int fio_http_is_sse(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_SSE)); +} + +/* ***************************************************************************** +Header Data Management +***************************************************************************** */ + +#define FIO___HTTP_HEADER_SET_FN(category, name_, headers, add_val) \ + /** Sets the header information associated with the HTTP handle. */ \ + fio_str_info_s fio_http_##category##_header_##name_(fio_http_s *h, \ + fio_str_info_s name, \ + fio_str_info_s value) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); \ + return fio___http_hmap_set2(headers(h), name, value, add_val); \ + } +FIO___HTTP_HEADER_SET_FN(request, set, HTTP_HDR_REQUEST, -1) +FIO___HTTP_HEADER_SET_FN(request, set_if_missing, HTTP_HDR_REQUEST, 0) +FIO___HTTP_HEADER_SET_FN(request, add, HTTP_HDR_REQUEST, 1) +FIO___HTTP_HEADER_SET_FN(response, set, HTTP_HDR_RESPONSE, -1) +FIO___HTTP_HEADER_SET_FN(response, set_if_missing, HTTP_HDR_RESPONSE, 0) +FIO___HTTP_HEADER_SET_FN(response, add, HTTP_HDR_RESPONSE, 1) +#undef FIO___HTTP_HEADER_SET_FN + +fio_str_info_s fio_http_request_header(fio_http_s *h, + fio_str_info_s name, + size_t index) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + return fio___http_hmap_get2(HTTP_HDR_REQUEST(h), name, (int32_t)index); +} +fio_str_info_s fio_http_response_header(fio_http_s *h, + fio_str_info_s name, + size_t index) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + return fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), name, (int32_t)index); +} + +/** Returns the number of headers named `name` that were received. */ +SFUNC size_t fio_http_request_header_count(fio_http_s *h, fio_str_info_s name) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!name.buf) + return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); + return fio___http_hmap_count2(HTTP_HDR_REQUEST(h), name); +} +/** Returns the number of headers named `name` that were received. */ +SFUNC size_t fio_http_response_header_count(fio_http_s *h, + fio_str_info_s name) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!name.buf) + return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); + return fio___http_hmap_count2(HTTP_HDR_RESPONSE(h), name); +} + +/** Iterates through all headers. A non-zero return will stop iteration. */ +size_t fio_http_request_header_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!callback) + return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); + fio___http_hmap_each_info_s d = {.h = h, + .callback = callback, + .udata = udata}; + return fio___http_hmap_each(HTTP_HDR_REQUEST(h), + fio___http_h_each_task_wrapper, + &d, + 0); +} + +/** Iterates through all headers. A non-zero return will stop iteration. */ +size_t fio_http_response_header_each( + fio_http_s *h, + int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *), + void *udata) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!callback) + return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); + fio___http_hmap_each_info_s d = {.h = h, + .callback = callback, + .udata = udata}; + return fio___http_hmap_each(HTTP_HDR_RESPONSE(h), + fio___http_h_each_task_wrapper, + &d, + 0); +} + +/* ***************************************************************************** +Cookies +***************************************************************************** */ + +/** (Helper) HTTP Cookie Parser */ +FIO_IFUNC void fio___http_cookie_parse_cookie(fio_http_s *h, fio_str_info_s s) { + /* loop and read Cookie: name=value; name2=value2; name3=value3 */ + while (s.len) { + fio_str_info_s k = {0}, v = {0}; + /* remove white-space */ + while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { + ++s.buf; + --s.len; + } + if (!s.len) + return; + char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); + char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); + if (!end) + end = s.buf + s.len; + v.buf = s.buf; + if (div) { + /* cookie name may be an empty string */ + k.buf = s.buf; + k.len = div - s.buf; + v.buf = div + 1; + } + v.len = end - v.buf; + s.len = (s.buf + s.len) - end; + s.buf = end; + /* skip the ';' if exists (if len is not zero, !!s.len == 1). */ + s.buf += !!s.len; + s.len -= !!s.len; + fio___http_cmap_set_if_missing(h->cookies, k, v); + } +} + +/** (Helper) HTTP Cookie Parser */ +FIO_IFUNC void fio___http_cookie_parse_set_cookie(fio_http_s *h, + fio_str_info_s s) { + /* TODO! */ + fio_str_info_s k = {0}, v = {0}; + /* remove white-space */ + while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { + ++s.buf; + --s.len; + } + if (!s.len) + return; + char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); + char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); + if (div == s.buf || !div) + return; + if (!end) + end = s.buf + s.len; + const uint64_t prefix_secure = fio_buf2u64u("_Secure-"); + const uint32_t prefix_host = fio_buf2u32u("Host"); + uint32_t cont; + k.buf = s.buf; + k.len = div - s.buf; + v.buf = div + 1; + v.len = end - v.buf; + do { /* loop to clear away cookie prefixes in any order */ + cont = 0; + if (k.len > 8 && k.buf[0] == '_' && + fio_buf2u64u(k.buf + 1) == prefix_secure) { + cont = 1; + k.len -= 9; + k.buf += 9; + } + if (k.len > 6 && k.buf[0] == '_' && k.buf[1] == '_' && k.buf[6] == '-' && + fio_buf2u32u(k.buf + 2) == prefix_host) { + cont = 1; + k.len -= 7; + k.buf += 7; + } + } while (cont); + if (k.len) + fio___http_cmap_set_if_missing(h->cookies, k, v); +} + +/** (Helper) Parses all HTTP Cookies */ +FIO_SFUNC void fio___http_cookie_collect(fio_http_s *h) { + fio___http_sary_s *header = NULL; + header = fio___http_hmap_node2val_ptr( + fio___http_hmap_get_ptr(h->headers, FIO_STR_INFO1((char *)"cookie"))); + if (header) { + FIO_ARRAY_EACH(fio___http_sary, header, pos) { + fio___http_cookie_parse_cookie(h, fio_bstr_info(*pos)); + } + } + /* if headers were sent, set-cookie data might belong to the handle */ + if (h->writer != fio____http_write_start) + return; + header = fio___http_hmap_node2val_ptr( + fio___http_hmap_get_ptr(h->headers + 1, + FIO_STR_INFO1((char *)"set-cookie"))); + if (!header) + return; + FIO_ARRAY_EACH(fio___http_sary, header, pos) { + fio___http_cookie_parse_set_cookie(h, fio_bstr_info(*pos)); + } + return; +} + +int fio_http_cookie_set___(void); /* IDE Marker */ +/* Sets a response cookie. */ +SFUNC int fio_http_cookie_set FIO_NOOP(fio_http_s *h, + fio_http_cookie_args_s cookie) { + FIO_ASSERT_DEBUG(h, "Can't set cookie for NULL HTTP handler!"); + if (!h || (h->state & (FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_STREAMING))) + return -1; + /* promises that some warnings print only once. */ + static unsigned int warn_illegal = 0; + unsigned int need2warn = 0; + + /* valid / invalid characters in cookies, create with Ruby using: + a = [] + 256.times {|i| a[i] = 1;} + ('a'.ord..'z'.ord).each {|i| a[i] = 0;} + ('A'.ord..'Z'.ord).each {|i| a[i] = 0;} + ('0'.ord..'9'.ord).each {|i| a[i] = 0;} + "!#$%&'*+-.^_`|~".bytes.each {|i| a[i] = 0;} + p a; nil + "!#$%&'()*+-./:<=>?@[]^_`{|}~".bytes.each {|i| a[i] = 0;} # for values + p a; nil + */ + static const char invalid_cookie_name_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static const char invalid_cookie_value_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + /* write name and value while auto-correcting encoding issues */ + if ((cookie.name.len + cookie.value.len + cookie.domain.len + + cookie.path.len + 128) > 5119) { + FIO_LOG_ERROR("cookie data too long!"); + } + char tmp_buf[5120]; + fio_str_info_s t = FIO_STR_INFO3(tmp_buf, 0, 5119); + +#define fio___http_h_copy_cookie_ch(ch_var) \ + if (!invalid_cookie_##ch_var##_char[(uint8_t)cookie.ch_var.buf[tmp]]) { \ + t.buf[t.len++] = cookie.ch_var.buf[tmp]; \ + } else { \ + need2warn |= 1; \ + t.buf[t.len++] = '%'; \ + t.buf[t.len++] = fio_i2c(((uint8_t)cookie.ch_var.buf[tmp] >> 4) & 0x0F); \ + t.buf[t.len++] = fio_i2c((uint8_t)cookie.ch_var.buf[tmp] & 0x0F); \ + } \ + tmp += 1; \ + if (t.capa <= t.len + 3) { \ + ((t.buf == tmp_buf) \ + ? FIO_STRING_ALLOC_COPY \ + : FIO_STRING_REALLOC)(&t, fio_string_capa4len(t.len + 3)); \ + } + + if (cookie.name.buf) { + size_t tmp = 0; + if (cookie.name.len) { + while (tmp < cookie.name.len) { + fio___http_h_copy_cookie_ch(name); + } + } else { + while (cookie.name.buf[tmp]) { + fio___http_h_copy_cookie_ch(name); + } + } + if (need2warn && !warn_illegal) { + warn_illegal |= 1; + FIO_LOG_WARNING("illegal char 0x%.2x in cookie name (in %s)\n" + " automatic %% encoding applied", + cookie.name.buf[tmp], + cookie.name.buf); + } + } + t.buf[t.len++] = '='; + if (cookie.value.buf) { + size_t tmp = 0; + if (cookie.value.len) { + while (tmp < cookie.value.len) { + fio___http_h_copy_cookie_ch(value); + } + } else { + while (cookie.value.buf[tmp]) { + fio___http_h_copy_cookie_ch(value); + } + } + if (need2warn && !warn_illegal) { + warn_illegal |= 1; + FIO_LOG_WARNING("illegal char 0x%.2x in cookie value (in %s)\n" + " automatic %% encoding applied", + cookie.value.buf[tmp], + cookie.value.buf); + } + } else + cookie.max_age = -1; +#undef fio___http_h_copy_cookie_ch + + /* server cookie data */ + t.buf[t.len++] = ';'; + t.buf[t.len++] = ' '; + + if (cookie.max_age) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"Max-Age=", 8), + FIO_STRING_WRITE_NUM(cookie.max_age), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + + if (cookie.domain.buf && cookie.domain.len) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"domain=", 7), + FIO_STRING_WRITE_STR2((char *)cookie.domain.buf, cookie.domain.len), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + if (cookie.path.buf && cookie.path.len) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"path=", 5), + FIO_STRING_WRITE_STR2((char *)cookie.path.buf, cookie.path.len), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + if (cookie.http_only) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "HttpOnly; ", + 10); + } + if (cookie.secure) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "secure; ", + 8); + } + if (cookie.partitioned) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "partitioned; ", + 13); + } + switch (cookie.same_site) { + case FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT: /* fall through */ + default: break; + case FIO_HTTP_COOKIE_SAME_SITE_NONE: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=None;", + 14); + break; + case FIO_HTTP_COOKIE_SAME_SITE_LAX: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=Lax;", + 13); + break; + case FIO_HTTP_COOKIE_SAME_SITE_STRICT: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=Strict;", + 16); + break; + } + if (t.buf[t.len - 1] == ' ') + --t.len; + + /* set the "write" cookie store data */ + fio___http_cmap_set(h->cookies + 1, cookie.name, t, NULL); + /* set the "read" cookie store data */ + fio___http_cmap_set(h->cookies, cookie.name, cookie.value, NULL); + if (t.buf != tmp_buf) + FIO_STRING_FREE2(t); + return 0; +} + +/** Returns a cookie value (either received of newly set), if any. */ +SFUNC fio_str_info_s fio_http_cookie(fio_http_s *h, + const char *name, + size_t name_len) { + if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & + FIO_HTTP_STATE_COOKIES_PARSED)) + fio___http_cookie_collect(h); + fio_str_info_s r = + fio___http_cmap_get(h->cookies, FIO_STR_INFO2((char *)name, name_len)); + return r; +} + +/** Iterates through all cookies. A non-zero return will stop iteration. */ +SFUNC size_t fio_http_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata) { + if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & + FIO_HTTP_STATE_COOKIES_PARSED)) + fio___http_cookie_collect(h); + size_t i = 0; + FIO_MAP_EACH(fio___http_cmap, h->cookies, pos) { + ++i; + if (callback(h, pos.key, pos.value, udata)) + return i; + } + return i; +} + +/** + * Iterates through all response set cookies. + * + * A non-zero return value from the callback will stop iteration. + */ +SFUNC size_t +fio_http_set_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *h, + fio_str_info_s set_cookie_header, + fio_str_info_s value, + void *udata), + void *udata) { + size_t i = 0; + fio___http_cmap_s *set_cookies = h->cookies + 1; + fio_str_info_s header_name = FIO_STR_INFO2((char *)"set-cookie", 10); + FIO_MAP_EACH(fio___http_cmap, set_cookies, pos) { + ++i; + if (callback(h, header_name, pos.value, udata)) + return i; + } + return i; +} + +/* ***************************************************************************** +Peer Address +***************************************************************************** */ + +/** + * Writes peer address to `dest` starting with the `forwarded` header, with a + * fallback to actual socket address and a final fallback to `"[unknown]"`. + * + * If `unknown` is returned, the function returns -1. if `dest` capacity is too + * small, the number of bytes required will be returned. + * + * If all goes well, this function returns 0. + */ +SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h) { + int r = 0; + /* Guess IP address from headers (forwarded) where possible */ + fio_str_info_s forwarded = + fio_http_request_header((fio_http_s *)h, + FIO_STR_INFO2((char *)"forwarded", 9), + -1); + fio_buf_info_s buf; + char *end; + if (forwarded.len) { + forwarded.len &= 1023; /* limit possible attack surface */ + for (; forwarded.len > 5;) { + if ((forwarded.buf[0] | 32) != 'f' || (forwarded.buf[1] | 32) != 'o' || + (forwarded.buf[2] | 32) != 'r' || forwarded.buf[3] != '=') { + ++forwarded.buf; + --forwarded.len; + continue; + } + forwarded.buf += 4 + (forwarded.buf[4] == '"'); + break; + } + client_address_found: + buf.buf = end = forwarded.buf; + while (*end && *end != '"' && *end != ',' && *end != ' ' && *end != ';' && + (end - forwarded.buf) < 48) + ++end; + buf.len = (size_t)(end - forwarded.buf); + } else { + forwarded = + fio_http_request_header((fio_http_s *)h, + FIO_STR_INFO2((char *)"x-forwarded-for", 15), + -1); + if (forwarded.len) { + forwarded.buf += (forwarded.buf[0] == '"'); + goto client_address_found; + } +#if defined(H___FIO_SOCK___H) + if (!(buf = fio_sock_peer_addr( + fio_http_controller((fio_http_s *)h)->get_fd((fio_http_s *)h))) + .len) +#endif + buf = FIO_BUF_INFO1((char *)"[unknown]"); + r = -1; + } + if (dest->capa > dest->len + buf.len) { /* enough space? */ + FIO_MEMCPY(dest->buf + dest->len, buf.buf, buf.len); + dest->len += buf.len; + dest->buf[dest->len] = 0; + } else + r = (int)buf.len - (!buf.len); + return r; +} + +/* ***************************************************************************** +Body Management - file descriptor +***************************************************************************** */ + +FIO_SFUNC fio_str_info_s fio___http_body_read_fd(fio_http_s *h, size_t len) { + h->body.buf = fio_bstr_len_set(h->body.buf, 0); + h->body.buf = fio_bstr_readfd(h->body.buf, h->body.fd, h->body.pos, len); + fio_str_info_s r = fio_bstr_info(h->body.buf); + h->body.pos += r.len; + return r; +} +FIO_SFUNC fio_str_info_s fio___http_body_read_until_fd(fio_http_s *h, + char token, + size_t limit) { + h->body.buf = fio_bstr_len_set(h->body.buf, 0); + h->body.buf = + fio_bstr_getdelim_fd(h->body.buf, h->body.fd, h->body.pos, token, limit); + fio_str_info_s r = fio_bstr_info(h->body.buf); + h->body.pos += r.len; + return r; +} +FIO_SFUNC void fio___http_body_expect_fd(fio_http_s *h, size_t len) { + (void)h, (void)len; +} +FIO_SFUNC void fio___http_body_write_fd(fio_http_s *h, + const void *data, + size_t len) { + ssize_t written = fio_fd_write(h->body.fd, data, len); + if (written > 0) + h->body.len += written; +} + +/* ***************************************************************************** +Body Management - buffer +***************************************************************************** */ + +FIO_SFUNC int fio___http_body___move_buf2fd(fio_http_s *h) { + h->body.fd = fio_filename_tmp(); + if (h->body.fd == -1) { +#if 1 + static int error_printed = 0; + if (!error_printed) { + error_printed = 1; + FIO_LOG_ERROR("fio_http_s couldn't open temporary file! (%d) %s", + errno, + strerror(errno)); + } +#endif + return -1; + } + fio_buf_info_s b = fio_bstr_buf(h->body.buf); + if (!b.len) + return 0; + ssize_t written = fio_fd_write(h->body.fd, b.buf, b.len); + if (written == (ssize_t)b.len) + return 0; + close(h->body.fd); + FIO_LOG_ERROR("fio_http_s couldn't transfer data to temporary file " + "(transferred %zd / %zu)", + written, + b.len); + return (h->body.fd = -1); +} +FIO_SFUNC fio_str_info_s fio___http_body_read_buf(fio_http_s *h, size_t len) { + fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), len); + h->body.pos += len; + return r; +} +FIO_SFUNC fio_str_info_s fio___http_body_read_until_buf(fio_http_s *h, + char token, + size_t limit) { + fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), limit); + char *end = (char *)FIO_MEMCHR(r.buf, token, limit); + if (end) { + ++end; + r.len = end - r.buf; + h->body.pos = end - h->body.buf; + } + return r; +} +FIO_SFUNC void fio___http_body_expect_buf(fio_http_s *h, size_t len) { + if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) { + fio___http_body___move_buf2fd(h); + return; + } + h->body.buf = fio_bstr_reserve(h->body.buf, len); +} +FIO_SFUNC void fio___http_body_write_buf(fio_http_s *h, + const void *data, + size_t len) { + if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) + goto switch_to_fd; +write_to_buf: + h->body.buf = fio_bstr_write(h->body.buf, data, len); + h->body.len += len; + return; +switch_to_fd: + if (fio___http_body___move_buf2fd(h)) + goto write_to_buf; + fio___http_body_write_fd(h, data, len); +} + +/* ***************************************************************************** +Body Management - Public API +***************************************************************************** */ + +/** Gets the body (payload) length associated with the HTTP handle. */ +SFUNC size_t fio_http_body_length(fio_http_s *h) { return h->body.len; } + +/** + * If the body is stored in a temporary file, returns the file's handle. + * + * Otherwise returns -1. + */ +SFUNC int fio_http_body_fd(fio_http_s *h) { return h->body.fd; } + +/** Adjusts the body's reading position. Negative values start at the end. */ +SFUNC size_t fio_http_body_seek(fio_http_s *h, ssize_t pos) { + if (pos < 0) { + pos += h->body.len; + if (pos < 0) + pos = 0; + } + if ((size_t)pos >= h->body.len) + pos = h->body.len; + h->body.pos = pos; + return pos; +} + +/** Reads up to `length` of data from the body, returns nothing on EOF. */ +SFUNC fio_str_info_s fio_http_body_read(fio_http_s *h, size_t length) { + fio_str_info_s r = {0}; + if (h->body.pos == h->body.len) + return r; + if (h->body.pos + length > h->body.len) + length = h->body.len - h->body.pos; + r = ((h->body.fd == -1) ? fio___http_body_read_buf + : fio___http_body_read_fd)(h, length); + return r; +} + +/** + * Reads from the body until finding `token`, reaching `limit` or EOF. + * + * Note: `limit` is ignored if zero or larger than remaining data. + */ +SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *h, + char token, + size_t limit) { + fio_str_info_s r = {0}; + if (h->body.pos == h->body.len) + return r; + if (!limit || (h->body.pos + limit) > h->body.len) + limit = h->body.len - h->body.pos; + r = ((h->body.fd == -1) ? fio___http_body_read_until_buf + : fio___http_body_read_until_fd)(h, token, limit); + return r; +} + +/** Allocates a body (payload) of (at least) the `expected_length`. */ +SFUNC void fio_http_body_expect(fio_http_s *h, size_t expected_length) { + ((h->body.fd == -1) ? fio___http_body_expect_buf + : fio___http_body_expect_fd)(h, expected_length); +} + +/** Writes `data` to the body (payload) associated with the HTTP handle. */ +SFUNC void fio_http_body_write(fio_http_s *h, const void *data, size_t len) { + if (!data || !len) + return; + ((h->body.fd == -1) ? fio___http_body_write_buf + : fio___http_body_write_fd)(h, data, len); +} + +/* ***************************************************************************** +A Response Payload +***************************************************************************** */ + +/** ETag Helper */ +FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h); + +FIO_SFUNC int fio____http_write_done(fio_http_s *h, + fio_http_write_args_s *args) { + return -1; + (void)h, (void)args; +} + +FIO_SFUNC int fio____http_write_upgraded(fio_http_s *h, + fio_http_write_args_s *args) { + h->controller->write_body(h, *args); + h->sent += args->len; + return 0; +} + +FIO_SFUNC int fio____http_write_start(fio_http_s *h, + fio_http_write_args_s *args) { + /* if response has an `etag` header matching `if-none-match`, skip */ + fio___http_hmap_s *hdrs = h->headers + (!!h->status); + if (h->status) { + if (args->len && fio___http_response_etag_if_none_match(h)) + return -1; + if (!args->len && args->finish && h->status >= 400) { + fio_http_send_error_response(h, h->status); + return 0; + } + /* validate Date header */ + fio___http_hmap_set2( + hdrs, + FIO_STR_INFO2((char *)"date", 4), + fio_http_date(fio_http_get_timestump() / FIO___HTTP_TIME_DIV), + 0); + } + /* test if streaming / single body response */ + if (!fio___http_hmap_get_ptr(hdrs, + FIO_STR_INFO2((char *)"content-length", 14))) { + if (args->finish) { + /* validate / set Content-Length (not streaming) */ + char ibuf[32]; + fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); + fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); + v.len = fio_digits10u(args->len); + fio_ltoa10u(v.buf, args->len, v.len); + fio___http_hmap_set2(hdrs, k, v, -1); + } else { + h->state |= FIO_HTTP_STATE_STREAMING; + } + } + + /* start a response, unless status == 0 (which starts a request). */ + h->controller->send_headers(h); + return (h->writer = fio____http_write_cont)(h, args); +} + +FIO_SFUNC int fio____http_write_cont(fio_http_s *h, + fio_http_write_args_s *args) { + if (args->buf || args->fd) { + h->controller->write_body(h, *args); + h->sent += args->len; + } + if (args->finish) { + h->state |= FIO_HTTP_STATE_FINISHED; + h->writer = (h->state & FIO_HTTP_STATE_UPGRADED) + ? fio____http_write_upgraded + : fio____http_write_done; + h->controller->on_finish(h); + } + return 0; +} + +void fio_http_write___(void); /* IDE Marker */ +/** + * Writes `data` to the response body associated with the HTTP handle after + * sending all headers (no further headers may be sent). + */ +SFUNC void fio_http_write FIO_NOOP(fio_http_s *h, fio_http_write_args_s args) { + if (!h || !h->controller) + goto handle_error; + if (h->writer(h, &args)) + goto handle_error; + return; + +handle_error: + if (args.fd) + close(args.fd); + if (args.dealloc && args.buf) + args.dealloc((void *)args.buf); +} + +/** ETag Helper */ +FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h) { + if (!fio_http_etag_is_match(h)) + return 0; + h->status = 304; + fio___http_hmap_set2(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"content-length", 14), + FIO_STR_INFO0, + -1); + + h->controller->send_headers(h); + h->state |= FIO_HTTP_STATE_FINISHED; + h->writer = fio____http_write_done; + h->controller->on_finish(h); + return -1; +} + +/* ***************************************************************************** +WebSocket / SSE Helpers +***************************************************************************** */ + +/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ +SFUNC int fio_http_websocket_requested(fio_http_s *h) { + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); + /* test for "Connection: Upgrade" (TODO? allow for multi-value?) */ + if (val.len < 7 || + !(((fio_buf2u32u(val.buf) | 0x20202020UL) == fio_buf2u32u("upgr")) || + ((fio_buf2u32u(val.buf + 3) | 0x20202020) == fio_buf2u32u("rade")))) + return 0; + /* test for "Upgrade: websocket" (TODO? allow for multi-value?) */ + val = fio_http_request_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); + if (val.len < 7 || + !(((fio_buf2u64u(val.buf) | 0x2020202020202020ULL) == + fio_buf2u64u("websocke")) || + ((fio_buf2u32u(val.buf + 5) | 0x20202020UL) == fio_buf2u32u("cket")))) + return 0; + val = fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + if (val.len != 24) + return 0; + /* test for version value */ + val = fio_http_request_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + 0); + if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) + return -1; /* note the error value is still true, requested WebSocket... */ + return 1; +} + +/** Sets response data to agree to a WebSockets Upgrade.*/ +SFUNC void fio_http_upgrade_websocket(fio_http_s *h) { + { /* validate WebSocket version */ + fio_str_info_s val = fio_http_request_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + 0); + if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) { + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + FIO_STR_INFO2((char *)"13", 2)); + fio_http_send_error_response(h, 400); + } + } + h->status = 101; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"Upgrade", 7)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"upgrade", 7), + FIO_STR_INFO2((char *)"websocket", 9)); + { /* Sec-WebSocket-Accept */ + fio_str_info_s k = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + FIO_STR_INFO_TMP_VAR(accept_val, 63); + if (k.len != 24) + goto handshake_error; + fio_string_write(&accept_val, NULL, k.buf, k.len); + fio_string_write(&accept_val, + NULL, + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + 36); + fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); + fio_sha1_digest(&sha); + accept_val.len = 0; + fio_string_write_base64enc(&accept_val, + NULL, + fio_sha1_digest(&sha), + fio_sha1_len(), + 0); + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-accept", 20), + accept_val); + } + { /* finish up */ + h->state |= FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET; + fio_http_write_args_s args = {.finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return; +handshake_error: + fio_http_send_error_response(h, 403); + return; +} + +/** Sets request data to request a WebSockets Upgrade.*/ +SFUNC void fio_http_websocket_set_request(fio_http_s *h) { + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"Upgrade", 7)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"pragma", 6), + FIO_STR_INFO2((char *)"no-cache", 8)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-cache", 8)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"upgrade", 7), + FIO_STR_INFO2((char *)"websocket", 9)); + { + fio_http_request_header_set_if_missing( + h, + FIO_STR_INFO2((char *)"origin", 6), + fio_http_request_header(h, FIO_STR_INFO2((char *)"host", 4), 0)); + } + fio_http_request_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + FIO_STR_INFO2((char *)"13", 2)); + + { + uint64_t tmp[2] = {fio_rand64(), fio_rand64()}; + FIO_STR_INFO_TMP_VAR(key, 64); + fio_string_write_base64enc(&key, NULL, tmp, 16, 0); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + key); + } + /* sec-websocket-extensions ? */ + /* send request? */ +} + +/** Returns non-zero if the response accepts a WebSocket upgrade request. */ +SFUNC int fio_http_websocket_accepted(fio_http_s *h) { + if (h->status != 101) + return 0; + if (!fio_http_websocket_requested(h)) + return 0; + fio_str_info_s tst = + fio_http_response_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); + if (tst.len < 7 || + (fio_buf2u64_le(tst.buf) | (uint64_t)0x20202020202020FFULL) != + (fio_buf2u64_le("upgrade") | (uint64_t)0x20202020202020FFULL)) + return 0; + tst = fio_http_response_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); + if (tst.len < 9 || (tst.buf[0] | 32) != 'w' || + (fio_buf2u64u(tst.buf + 1) | (uint64_t)0x2020202020202020ULL) != + fio_buf2u64u("ebsocket")) + return 0; + { /* Sec-WebSocket-Accept */ + tst = fio_http_response_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-accept", 20), + 0); + if (!tst.len) + return 0; + + fio_str_info_s k = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + FIO_STR_INFO_TMP_VAR(accept_val, 63); + if (k.len != 24) + return 0; + fio_string_write(&accept_val, NULL, k.buf, k.len); + fio_string_write(&accept_val, + NULL, + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + 36); + fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); + fio_sha1_digest(&sha); + accept_val.len = 0; + fio_string_write_base64enc(&accept_val, + NULL, + fio_sha1_digest(&sha), + fio_sha1_len(), + 0); + if (!FIO_STR_INFO_IS_EQ(tst, accept_val)) { + FIO_LOG_DDEBUG2( + "(%d) sec-websocket-key invalid, WebSocket handshake failed.\n\t" + "%s != %s", + getpid(), + tst.buf, + accept_val.buf); + return 0; + } + } + h->state |= (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET | + FIO_HTTP_STATE_FINISHED); + h->writer = fio____http_write_upgraded; + return 1; +} + +/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ +SFUNC int fio_http_sse_requested(fio_http_s *h) { + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); + if (val.len < 17) + return 0; + if ((val.buf[0] | 32) != 't') + return 0; + uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; + uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; + if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) + return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ + FIO_LOG_DDEBUG2("(%d) EventSource connection requested.", + fio_thread_getpid()); + return 1; +} + +/** Returns non-zero if the response accepts an SSE request. */ +SFUNC int fio_http_sse_accepted(fio_http_s *h) { + if (!fio_http_sse_requested(h)) + return 0; + if (h->status != 200) + return 0; + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); + for (size_t i = 0; i < 2; ++i) { + if (val.len < 17) + return 0; + if ((val.buf[0] | 32) != 't') + return 0; + uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; + uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; + if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) + return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ + val = fio_http_response_header(h, + FIO_STR_INFO2((char *)"content-type", 12), + 0); + } + h->state |= + (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE | FIO_HTTP_STATE_FINISHED); + h->writer = fio____http_write_upgraded; + FIO_LOG_DDEBUG2("EventSource connection accepted."); + return 1; +} + +/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_upgrade_sse(fio_http_s *h) { + if (h->state) + return; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + FIO_STR_INFO2((char *)"text/event-stream", 17)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-store", 8)); + fio___http_hmap_remove(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"content-length", 14), + NULL); + h->state |= + FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE; + h->controller->send_headers(h); + h->writer = fio____http_write_upgraded; + h->controller->on_finish(h); +} + +/** Sets request data to request an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_sse_set_request(fio_http_s *h) { + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"accept", 6), + FIO_STR_INFO2((char *)"text/event-stream", 17)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"keep-alive", 10)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-cache", 8)); +} + +/* ***************************************************************************** +Header Parsing Helpers - Implementation +***************************************************************************** */ + +/** + * Assumes a Buffer of bytes containing length info and string data as such: + * + * [ 2 byte info: (type | (len << 2)) ] + * [ Optional 2 byte info: (len << 2) (if type was 1)] + * [ String of `len` bytes][ NUL byte (not counted in `len`)] + */ + +FIO_SFUNC int fio___http_header_parse_properties(fio_str_info_s *dst, + char *start, + char *const end) { + for (;;) { + char *nxt = (char *)FIO_MEMCHR(start, ';', end - start); + if (!nxt) + nxt = end; + char *eq = (char *)FIO_MEMCHR(start, '=', nxt - start); + if (!eq) + eq = nxt; + /* write value to dst */ + size_t len = eq - start; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); + dst->len += 2; + if (len) + FIO_MEMCPY(dst->buf + dst->len, start, len); + dst->len += len; + dst->buf[dst->len++] = 0; + + eq += (eq[0] == '='); + eq += (eq[0] == ' ' || eq[0] == '\t'); + len = nxt - eq; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); + dst->len += 2; + if (len) + FIO_MEMCPY(dst->buf + dst->len, eq, len); + dst->len += len; + dst->buf[dst->len++] = 0; + + if (nxt == end) + return 0; + nxt += (*nxt == ';'); + while (*nxt == ' ' || *nxt == '\t') + ++nxt; + start = nxt; + } + return 0; +} + +FIO_IFUNC int fio___http_header_parse(fio___http_hmap_s *map, + fio_str_info_s *dst, + fio_str_info_s header_name) { + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, header_name)); + if (!a) + return -1; + dst->len = 0; + if (dst->capa < 3) + return -1; + dst->buf[dst->len++] = 0; /* first byte is a pretend NUL */ + FIO_ARRAY_EACH(fio___http_sary, a, pos) { + fio_buf_info_s i = fio_bstr_buf(*pos); + if (!i.len) + continue; + char *const end = i.buf + i.len; + char *sep; + do { + sep = (char *)FIO_MEMCHR(i.buf, ',', end - i.buf); + if (!sep) + sep = end; + char *prop = (char *)FIO_MEMCHR(i.buf, ';', sep - i.buf); + if (!prop) + prop = sep; + size_t len = prop - i.buf; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, (len << 2)); + dst->len += 2; + FIO_MEMCPY(dst->buf + dst->len, i.buf, len); + dst->len += len; + dst->buf[dst->len++] = 0; + if (prop != sep) { /* parse properties */ + ++prop; + len = sep - prop; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; + const size_t old_len = dst->len; + dst->len += 2; + if (fio___http_header_parse_properties(dst, prop, sep)) + return -1; + len = dst->len - old_len; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; + fio_u2buf16u( + dst->buf + old_len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN)); + } + sep += (*sep == ','); + while (*sep == ' ' || *sep == '\t') + ++sep; + i.buf = sep; + } while (sep < end); + } + if (dst->len + 2 > dst->capa) + return -1; + /* last u16 must be zero (end marker) */ + dst->buf[dst->len++] = 0; + dst->buf[dst->len++] = 0; + return 0; +} + +SFUNC int fio_http_response_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name) { + return fio___http_header_parse(HTTP_HDR_RESPONSE(h), buf_parsed, header_name); +} + +SFUNC int fio_http_request_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name) { + return fio___http_header_parse(HTTP_HDR_REQUEST(h), buf_parsed, header_name); +} + +/* ***************************************************************************** +Error Handling +***************************************************************************** */ + +/** Sends the requested error message and finishes the response. */ +SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status) { + if (!h || h->writer != fio____http_write_start) + return -1; + if (!status || status > 1000) + status = 404; + h->status = (uint32_t)status; + FIO_STR_INFO_TMP_VAR(filename, 127); + /* read static error code file */ + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_UNUM(status), + FIO_STRING_WRITE_STR2(".html", 5)); + char *body = fio_bstr_readfile(NULL, filename.buf, 0, 0); + fio_http_write_args_s args = {.buf = body, + .len = fio_bstr_len(body), + .dealloc = (void (*)(void *))fio_bstr_free, + .finish = 1}; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + body ? FIO_STR_INFO2((char *)"text/html", 9) + : FIO_STR_INFO2((char *)"text/plain", 10)); + if (!body) { /* write a short error response (plain text fallback) */ + fio_str_info_s status_str = fio_http_status2str(status); + filename.len = 0; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("Error ", 6), + FIO_STRING_WRITE_UNUM(status), + FIO_STRING_WRITE_STR2(": ", 2), + FIO_STRING_WRITE_STR_INFO(status_str), + FIO_STRING_WRITE_STR2(".", 1)); + args.buf = filename.buf; + args.len = filename.len; + args.copy = 1; + args.dealloc = NULL; + } + fio_http_write FIO_NOOP(h, args); + return 0; +} + +/* ***************************************************************************** +HTTP Logging +***************************************************************************** */ + +SFUNC void fio___http_write_pid(fio_str_info_s *dest) { + static int last_pid = 0; + static char buf[64]; + static size_t len = 0; +#ifdef H___FIO_SERVER___H + int pid = fio_srv_pid(); +#else + int pid = fio_thread_getpid(); +#endif + if (last_pid != pid) + goto rewrite; +copy: + if (len) + FIO_MEMCPY(dest->buf + dest->len, buf, len); + dest->len += len; + return; +rewrite: + len = 0; + buf[len++] = '['; + if (pid > 0) { + size_t d = fio_digits10u((uint64_t)pid); + fio_ltoa10u(buf + 1, (uint64_t)pid, d); + len += d; + } + buf[len++] = ']'; + last_pid = pid; + goto copy; +} +/** Logs an HTTP (response) to STDOUT. */ +SFUNC void fio_http_write_log(fio_http_s *h) { + FIO_STR_INFO_TMP_VAR(buf, 1023); + intptr_t bytes_sent = h->sent; + uint64_t time_start, time_end, time_proxy = 0; + time_start = h->received_at; + time_end = fio_http_get_timestump(); + fio_str_info_s date = fio_http_log_time(time_end / FIO___HTTP_TIME_DIV); + fio_string_write_s to_write[16] = { + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->method)), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->path)), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->version)), + FIO_STRING_WRITE_STR2((const char *)"\" ", 2), + FIO_STRING_WRITE_NUM(h->status), + FIO_STRING_WRITE_STR2(" ", 1), + ((bytes_sent > 0) ? (FIO_STRING_WRITE_UNUM(bytes_sent)) + : (FIO_STRING_WRITE_STR2((const char *)"---", 3))), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_NUM(time_end - time_start), + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT "\r\n"), 4), + }; + if (FIO_HTTP_LOG_X_REQUEST_START) { + /* log request wait time using x-request-start header */ + fio_str_info_s xstart = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"x-request-start", 15), + 0); + unsigned step = + (xstart.len > 1 && (xstart.buf[0] | 32) == 't' && xstart.buf[1] == '='); + step <<= 1; + xstart.buf += step; + xstart.len -= step; + time_proxy = fio_atol(&xstart.buf); + time_proxy *= (FIO___HTTP_TIME_DIV / 1000); /* assumes info in ms */ + time_proxy = time_start - time_proxy; + if (time_proxy < (512 * FIO___HTTP_TIME_DIV)) { /* was ms? */ + to_write[11] = + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT " (wait "), + 9); + to_write[12] = FIO_STRING_WRITE_NUM(time_proxy); + to_write[13] = + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT ")\r\n"), + 5); + } + } + + /* Write log line to buffer */ + fio___http_write_pid(&buf); + buf.buf[buf.len++] = ' '; + fio_http_from(&buf, h); + FIO_MEMCPY(buf.buf + buf.len, " - - ", 5); + FIO_MEMCPY(buf.buf + buf.len + 5, date.buf, date.len); + buf.len += date.len + 6; + buf.buf[buf.len++] = ' '; + buf.buf[buf.len++] = '\"'; + fio_string_write2 FIO_NOOP(&buf, NULL, to_write); + + if (buf.buf[buf.len - 1] != '\n') + buf.buf[buf.len++] = '\n'; /* log was truncated, data too long */ + + /* Write log line to STDOUT */ + fwrite(buf.buf, 1, buf.len, stdout); + h->received_at = time_end; +} + +/* ***************************************************************************** +ETag helper +***************************************************************************** */ + +/** Returns true (1) if the ETag response matches an if-none-match request. */ +SFUNC int fio_http_etag_is_match(fio_http_s *h) { + fio_str_info_s method = fio_keystr_info(&h->method); + if ((method.len < 3) | (method.len > 4)) + return 0; + if (!(((method.buf[0] | 32) == 'g') & ((method.buf[1] | 32) == 'e') & + ((method.buf[2] | 32) == 't')) && + !(((method.buf[0] | 32) == 'h') & ((method.buf[1] | 32) == 'e') & + ((method.buf[2] | 32) == 'a') & ((method.buf[3] | 32) == 'd'))) + return 0; + fio_str_info_s etag = fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"etag", 4), + 0); + if (!etag.len) + return 0; + fio_str_info_s cond = + fio___http_hmap_get2(HTTP_HDR_REQUEST(h), + FIO_STR_INFO2((char *)"if-none-match", 13), + 0); + if (!cond.len) + return 0; + char *end = cond.buf + cond.len; + for (;;) { + cond.buf += (cond.buf[0] == ','); + while (cond.buf[0] == ' ') + ++cond.buf; + if (cond.buf > end || (size_t)(end - cond.buf) < (size_t)etag.len) + return 0; + if (FIO_MEMCMP(cond.buf, etag.buf, etag.len)) { + cond.buf = (char *)FIO_MEMCHR(cond.buf, ',', end - cond.buf); + if (!cond.buf) + return 0; + continue; + } + return 1; + } +} + +/* ***************************************************************************** +Param Parsing (TODO! - parse query, parse mime/multipart parse text/json) +***************************************************************************** */ + +/* ***************************************************************************** + + + TODO WIP Marker!!! + + +***************************************************************************** */ + +/* ***************************************************************************** +Static file helper +***************************************************************************** */ + +/** + * Attempts to send a static file from the `root` folder. On success the + * response is complete and 0 is returned. Otherwise returns -1. + */ +SFUNC int fio_http_static_file_response(fio_http_s *h, + fio_str_info_s rt, + fio_str_info_s fnm, + size_t max_age) { + int fd = -1; + size_t file_length = 0; + /* combine public folder with path to get file name */ + fio_str_info_s mime_type = {0}; + FIO_STR_INFO_TMP_VAR(etag, 31); + FIO_STR_INFO_TMP_VAR(filename, 4095); + { /* test for HEAD and OPTIONS requests */ + fio_str_info_s m = fio_keystr_info(&h->method); + if ((m.len == 7 && (fio_buf2u64u(m.buf) | 0x2020202020202020ULL) == + (fio_buf2u64u("options") | 0x2020202020202020ULL))) + goto file_not_found; + } + rt.len -= ((rt.len > 0) && (fnm.len > 0 && fnm.buf[0] == '/') && + (rt.buf[rt.len - 1] == '/' || + rt.buf[rt.len - 1] == FIO_FOLDER_SEPARATOR)); + fio_string_write(&filename, NULL, rt.buf, rt.len); + fio_string_write_url_dec(&filename, NULL, fnm.buf, fnm.len); + if (fio_filename_is_unsafe_url(filename.buf)) + goto file_not_found; + + { /* Test for incomplete file name */ + size_t file_type = fio_filename_type(filename.buf); +#if defined(S_IFDIR) && defined(FIO_HTTP_DEFAULT_INDEX_FILENAME) + if (file_type == S_IFDIR) { + filename.len -= (filename.buf[filename.len - 1] == '/' || + filename.buf[filename.len - 1] == FIO_FOLDER_SEPARATOR); +#if FIO_HTTP_STATIC_FILE_COMPLETION + fio_string_write(&filename, + NULL, + "/" FIO_HTTP_DEFAULT_INDEX_FILENAME, + sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME)); + file_type = 0; +#else + fio_string_write( + &filename, + NULL, /* note that sizeof will count NUL, so we skip 1 char: */ + "/" FIO_HTTP_DEFAULT_INDEX_FILENAME ".html", + sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME ".html")); + file_type = fio_filename_type(filename.buf); +#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ + } +#endif /* S_IFDIR */ +#if FIO_HTTP_STATIC_FILE_COMPLETION + const fio_buf_info_s extensions[] = {FIO_BUF_INFO1((char *)".html"), + FIO_BUF_INFO1((char *)".htm"), + FIO_BUF_INFO1((char *)".txt"), + FIO_BUF_INFO1((char *)".md"), + FIO_BUF_INFO0}; + const fio_buf_info_s *pext = extensions; + while (!file_type) { + fio_string_write(&filename, NULL, pext->buf, pext->len); + file_type = fio_filename_type(filename.buf); + if (file_type) + break; + filename.len -= pext->len; + ++pext; + if (!pext->buf) + goto file_not_found; + } + switch (file_type) { + case S_IFREG: break; +#ifdef S_IFLNK + case S_IFLNK: break; +#endif + default: goto file_not_found; + } +#else /* FIO_HTTP_STATIC_FILE_COMPLETION */ + if (!file_type) + goto file_not_found; +#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ + } + { + /* find mime type if registered */ + char *end = filename.buf + filename.len; + char *ext = end; + do { + --ext; + } while (ext[0] != '.' && ext[0] != '/'); + if ((ext++)[0] == '.') { + mime_type = fio_http_mimetype(ext, end - ext); + if (!mime_type.len) + FIO_LOG_WARNING("missing mime-type for extension %s (not registered).", + ext); + } + } + { + fio_str_info_s ac = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"accept-encoding", 15), + 0); + if (!ac.len) + goto accept_encoding_header_test_done; + struct { + char *value; + fio_buf_info_s ext; + } options[] = {{(char *)"gzip", FIO_BUF_INFO2((char *)".gz", 3)}, + {(char *)"br", FIO_BUF_INFO2((char *)".br", 3)}, + {(char *)"deflate", FIO_BUF_INFO2((char *)".zip", 4)}, + {NULL}}; + for (size_t i = 0; options[i].value; ++i) { + if (!strstr(ac.buf, options[i].value)) + continue; + fio_string_write(&filename, NULL, options[i].ext.buf, options[i].ext.len); + if (!fio_filename_type(filename.buf)) { + filename.len -= options[i].ext.len; + filename.buf[filename.len] = 0; + continue; + } + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"vary", 4), + FIO_STR_INFO2((char *)"accept-encoding", 15)); + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"content-encoding", 16), + FIO_STR_INFO1(options[i].value)); + break; + } + } + +accept_encoding_header_test_done: + /* attempt to open file */ + fd = fio_filename_open(filename.buf, O_RDONLY); + if (fd == -1) + goto file_not_found; + + { /* test / validate etag */ + struct stat stt; + if (fstat(fd, &stt)) + goto file_not_found; + uint64_t etag_hash = fio_risky_hash(&stt, sizeof(stt), 0); + fio_string_write_hex(&etag, NULL, etag_hash); + fio_http_response_header_set(h, FIO_STR_INFO2((char *)"etag", 4), etag); + filename.len = 0; + filename.len = fio_time2rfc7231(filename.buf, stt.st_mtime); + fio_http_response_header_set(h, + FIO_STR_INFO1((char *)"last-modified"), + filename); + if (max_age) { + filename.len = 0; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("max-age=", 8), + FIO_STRING_WRITE_UNUM(max_age)); + fio_http_response_header_set(h, + FIO_STR_INFO1((char *)"cache-control"), + filename); + } + file_length = stt.st_size; + filename.capa = 0; + if (fio___http_response_etag_if_none_match(h)) + return 0; + } + /* test for range requests. */ + { + /* test / validate range requests */ + fio_str_info_s rng = + fio_http_request_header(h, FIO_STR_INFO2((char *)"range", 5), 0); + if (!rng.len) + goto range_request_review_finished; + { + fio_str_info_s ifrng = + fio_http_request_header(h, FIO_STR_INFO2((char *)"if-range", 8), 0); + if (ifrng.len && !FIO_STR_INFO_IS_EQ(ifrng, etag)) + goto range_request_review_finished; + } + if (rng.len < 7 || fio_buf2u32u(rng.buf) != fio_buf2u32u("byte") || + fio_buf2u16u(rng.buf + 4) != fio_buf2u16u("s=")) + goto range_request_review_finished; + char *ipos = rng.buf + 6; + size_t start_range = fio_atol10u(&ipos); + if (ipos == rng.buf + 6) + start_range = (size_t)-1; + if (*ipos != '-') + goto range_request_review_finished; + ++ipos; + size_t end_range = fio_atol10u(&ipos); + if (end_range > file_length) + goto range_request_review_finished; + if (!end_range) + end_range = file_length - 1; + if (start_range == (size_t)-1) { + start_range = file_length - end_range; + end_range = file_length - 1; + } + if (start_range > end_range || end_range > file_length) + goto invalid_range; + if (!start_range && end_range + 1 == file_length) + goto range_request_review_finished; + /* update response headers and info */ + h->status = 206; + filename.len = 0; + filename.capa = 1024; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("bytes ", 6), + FIO_STRING_WRITE_UNUM(start_range), + FIO_STRING_WRITE_STR2("-", 1), + FIO_STRING_WRITE_UNUM((end_range)), + FIO_STRING_WRITE_STR2("/", 1), + FIO_STRING_WRITE_UNUM(file_length)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-range", 13), + filename); + file_length = (end_range - start_range) + 1; + filename.capa = start_range; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"etag", 4), + FIO_STR_INFO2(NULL, 0)); + } + +range_request_review_finished: + /* allow interrupted downloads to resume */ + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"accept-ranges", 13), + FIO_STR_INFO2((char *)"bytes", 5)); + /* test for HEAD requests */ + { + fio_str_info_s m = fio_keystr_info(&h->method); + if ((m.len == 4 && (fio_buf2u32u(m.buf) | 0x20202020UL) == + (fio_buf2u32u("head") | 0x20202020UL))) + goto head_request; + } + + /* finish up (set mime type and send file) */ + if (mime_type.len) + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + mime_type); + { /* send response (avoid macro for C++ compatibility) */ + fio_http_write_args_s args = { + .len = file_length, + .offset = filename.capa, /* now holds starting offset */ + .fd = fd, + .finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return 0; + +file_not_found: + if (fd != -1) + close(fd); + return -1; + +head_request: + /* TODO! HEAD responses should close?. */ + if (fd != -1) + close(fd); + { + fio_http_write_args_s args = {.finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return 0; + +invalid_range: + filename.len = 0; + filename.capa = 1024; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("bytes */", 8), + FIO_STRING_WRITE_UNUM(file_length)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-range", 13), + filename); + return fio_http_send_error_response(h, 416); +} + +/* ***************************************************************************** +Status Strings +***************************************************************************** */ + +/** Returns a human readable string related to the HTTP status number. */ +SFUNC fio_str_info_s fio_http_status2str(size_t status) { + fio_str_info_s r = {0}; +#define HTTP_RETURN_STATUS(str) \ + do { \ + r.len = FIO_STRLEN(str); \ + r.buf = (char *)str; \ + return r; \ + } while (0); + switch (status) { + // clang-format off + case 100: HTTP_RETURN_STATUS("Continue"); + case 101: HTTP_RETURN_STATUS("Switching Protocols"); + case 102: HTTP_RETURN_STATUS("Processing"); + case 103: HTTP_RETURN_STATUS("Early Hints"); + case 110: HTTP_RETURN_STATUS("Response is Stale"); /* caching code*/ + case 111: HTTP_RETURN_STATUS("Re-validation Failed"); /* caching code*/ + case 112: HTTP_RETURN_STATUS("Disconnected Operation"); /* caching code*/ + case 113: HTTP_RETURN_STATUS("Heuristic Expiration"); /* caching code*/ + case 199: HTTP_RETURN_STATUS("Miscellaneous Warning"); /* caching code*/ + case 200: HTTP_RETURN_STATUS("OK"); + case 201: HTTP_RETURN_STATUS("Created"); + case 202: HTTP_RETURN_STATUS("Accepted"); + case 203: HTTP_RETURN_STATUS("Non-Authoritative Information"); + case 204: HTTP_RETURN_STATUS("No Content"); + case 205: HTTP_RETURN_STATUS("Reset Content"); + case 206: HTTP_RETURN_STATUS("Partial Content"); + case 207: HTTP_RETURN_STATUS("Multi-Status"); + case 208: HTTP_RETURN_STATUS("Already Reported"); + case 214: HTTP_RETURN_STATUS("Transformation Applied"); /* caching code*/ + case 218: HTTP_RETURN_STATUS("This is fine (Apache Web Server)"); /* unofficial */ + case 226: HTTP_RETURN_STATUS("IM Used"); + case 299: HTTP_RETURN_STATUS("Miscellaneous Persistent Warning"); /* caching code*/ + case 300: HTTP_RETURN_STATUS("Multiple Choices"); + case 301: HTTP_RETURN_STATUS("Moved Permanently"); + case 302: HTTP_RETURN_STATUS("Found"); + case 303: HTTP_RETURN_STATUS("See Other"); + case 304: HTTP_RETURN_STATUS("Not Modified"); + case 305: HTTP_RETURN_STATUS("Use Proxy"); + case 307: HTTP_RETURN_STATUS("Temporary Redirect"); + case 308: HTTP_RETURN_STATUS("Permanent Redirect"); + case 400: HTTP_RETURN_STATUS("Bad Request"); + case 401: HTTP_RETURN_STATUS("Unauthorized"); + case 402: HTTP_RETURN_STATUS("Payment Required"); + case 403: HTTP_RETURN_STATUS("Forbidden"); + case 404: HTTP_RETURN_STATUS("Not Found"); + case 405: HTTP_RETURN_STATUS("Method Not Allowed"); + case 406: HTTP_RETURN_STATUS("Not Acceptable"); + case 407: HTTP_RETURN_STATUS("Proxy Authentication Required"); + case 408: HTTP_RETURN_STATUS("Request Timeout"); + case 409: HTTP_RETURN_STATUS("Conflict"); + case 410: HTTP_RETURN_STATUS("Gone"); + case 411: HTTP_RETURN_STATUS("Length Required"); + case 412: HTTP_RETURN_STATUS("Precondition Failed"); + case 413: HTTP_RETURN_STATUS("Content Too Large"); + case 414: HTTP_RETURN_STATUS("URI Too Long"); + case 415: HTTP_RETURN_STATUS("Unsupported Media Type"); + case 416: HTTP_RETURN_STATUS("Range Not Satisfiable"); + case 417: HTTP_RETURN_STATUS("Expectation Failed"); + case 418: HTTP_RETURN_STATUS("I am a Teapot"); /* April Fool's Day, 1998 */ + case 419: HTTP_RETURN_STATUS("Page Expired (Laravel Framework)"); /* unofficial */ + case 420: HTTP_RETURN_STATUS("Enhance Your Calm (Twitter) - Method Failure (Spring Framework)"); /* unofficial */ + case 421: HTTP_RETURN_STATUS("Misdirected Request"); + case 422: HTTP_RETURN_STATUS("Unprocessable Content"); + case 423: HTTP_RETURN_STATUS("Locked"); + case 424: HTTP_RETURN_STATUS("Failed Dependency"); + case 425: HTTP_RETURN_STATUS("Too Early"); + case 426: HTTP_RETURN_STATUS("Upgrade Required"); + case 427: HTTP_RETURN_STATUS("Unassigned"); + case 428: HTTP_RETURN_STATUS("Precondition Required"); + case 429: HTTP_RETURN_STATUS("Too Many Requests"); + case 430: HTTP_RETURN_STATUS("Request Header Fields Too Large (Shopify)"); /* unofficial */ + case 431: HTTP_RETURN_STATUS("Request Header Fields Too Large"); + case 444: HTTP_RETURN_STATUS("No Response"); /* nginx code */ + case 450: HTTP_RETURN_STATUS("Blocked by Windows Parental Controls (Microsoft)"); /* unofficial */ + case 451: HTTP_RETURN_STATUS("Unavailable For Legal Reasons"); + case 494: HTTP_RETURN_STATUS("Request header too large"); /* nginx code */ + case 495: HTTP_RETURN_STATUS("SSL Certificate Error"); /* nginx code */ + case 496: HTTP_RETURN_STATUS("SSL Certificate Required"); /* nginx code */ + case 497: HTTP_RETURN_STATUS("HTTP Request Sent to HTTPS Port"); /* nginx code */ + case 498: HTTP_RETURN_STATUS("Invalid Token (Esri)"); /* unofficial */ + case 499: HTTP_RETURN_STATUS("Client Closed Request"); /* nginx code */ + case 500: HTTP_RETURN_STATUS("Internal Server Error"); + case 501: HTTP_RETURN_STATUS("Not Implemented"); + case 502: HTTP_RETURN_STATUS("Bad Gateway"); + case 503: HTTP_RETURN_STATUS("Service Unavailable"); + case 504: HTTP_RETURN_STATUS("Gateway Timeout"); + case 505: HTTP_RETURN_STATUS("HTTP Version Not Supported"); + case 506: HTTP_RETURN_STATUS("Variant Also Negotiates"); + case 507: HTTP_RETURN_STATUS("Insufficient Storage"); + case 508: HTTP_RETURN_STATUS("Loop Detected"); + case 509: HTTP_RETURN_STATUS("Bandwidth Limit Exceeded (Apache Web Server/cPanel)"); /* unofficial */ + case 510: HTTP_RETURN_STATUS("Not Extended"); + case 511: HTTP_RETURN_STATUS("Network Authentication Required"); + case 529: HTTP_RETURN_STATUS("Site is overloaded (Qualys)"); /* unofficial */ + case 530: HTTP_RETURN_STATUS("Site is frozen (Pantheon web)"); /* unofficial */ + case 598: HTTP_RETURN_STATUS("Network read timeout error"); /* unofficial */ + // clang-format on + } + HTTP_RETURN_STATUS("Unknown"); +#undef HTTP_RETURN_STATUS +} + +/* ***************************************************************************** +MIME File Type Helpers +***************************************************************************** */ + +typedef struct { + uint64_t ext; + uint16_t len; + char mime[118]; /* all together 128 bytes per node */ +} fio___http_mime_info_s; + +#define FIO___HTTP_MIME_IS_VALID(o) ((o)->ext != 0) +#define FIO___HTTP_MIME_CMP(a, b) ((a)->ext == (b)->ext) +#define FIO___HTTP_MIME_HASH(o) fio_risky_num(((o)->ext), 0) + +#undef FIO_TYPEDEF_IMAP_REALLOC +#define FIO_TYPEDEF_IMAP_REALLOC(ptr, old_size, new_size, copy_len) \ + realloc(ptr, new_size) +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 +#undef FIO_TYPEDEF_IMAP_FREE +#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) + +FIO_TYPEDEF_IMAP_ARRAY(fio___http_mime_map, + fio___http_mime_info_s, + uint32_t, + FIO___HTTP_MIME_HASH, + FIO___HTTP_MIME_CMP, + FIO___HTTP_MIME_IS_VALID) + +static fio___http_mime_map_s FIO___HTTP_MIMETYPES; +#undef FIO___HTTP_MIME_IS_VALID +#undef FIO___HTTP_MIME_CMP +#undef FIO___HTTP_MIME_HASH + +#undef FIO_TYPEDEF_IMAP_REALLOC +#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE +#undef FIO_TYPEDEF_IMAP_FREE +#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE + +/** Registers a Mime-Type to be associated with the file extension. */ +SFUNC int fio_http_mimetype_register(char *file_ext, + size_t file_ext_len, + fio_str_info_s mime_type) { + fio___http_mime_info_s tmp, *old; + if (file_ext_len > 7 || mime_type.len > 117) + return -1; + tmp.ext = 0; + FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); + if (!mime_type.len) + goto remove_mime; + FIO_MEMCPY(&tmp.mime, mime_type.buf, mime_type.len); + tmp.len = mime_type.len; + tmp.mime[mime_type.len] = 0; + old = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); + if (old && old->len == tmp.len && !FIO_MEMCMP(old->mime, tmp.mime, tmp.len)) { + FIO_LOG_WARNING("mime-type collision: %.*s was %s, now %s", + (int)file_ext_len, + file_ext, + old->mime, + tmp.mime); + } + fio___http_mime_map_set(&FIO___HTTP_MIMETYPES, tmp, 1); + return 0; + +remove_mime: + return fio___http_mime_map_remove(&FIO___HTTP_MIMETYPES, tmp); +} + +/** Finds the Mime-Type associated with the file extension (if registered). */ +SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len) { + fio_str_info_s r = {0}; + fio___http_mime_info_s tmp, *val; + tmp.ext = 0; + FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); + val = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); + if (!val) + return r; + r.len = val->len; + r.buf = val->mime; + return r; +} + +#define REGISTER_MIME(ext, type) \ + fio_http_mimetype_register((char *)ext, \ + sizeof(ext) - 1, \ + FIO_STR_INFO2((char *)type, sizeof(type) - 1)) + +/** Registers known mime-types that aren't often used by Web Servers. */ +FIO_SFUNC void fio_http_mime_register_essential(void) { + /* clang-format off */ + REGISTER_MIME("3ds", "image/x-3ds"); + REGISTER_MIME("3g2", "video/3gpp"); + REGISTER_MIME("3gp", "video/3gpp"); + REGISTER_MIME("7z", "application/x-7z-compressed"); + REGISTER_MIME("aac", "audio/aac"); + REGISTER_MIME("abw", "application/x-abiword"); + REGISTER_MIME("aif", "audio/x-aiff"); + REGISTER_MIME("aifc", "audio/x-aiff"); + REGISTER_MIME("aiff", "audio/x-aiff"); + REGISTER_MIME("arc", "application/x-freearc"); + REGISTER_MIME("atom", "application/atom+xml"); + REGISTER_MIME("avi", "video/x-msvideo"); + REGISTER_MIME("avif", "image/avif"); + REGISTER_MIME("azw", "application/vnd.amazon.ebook"); + REGISTER_MIME("bin", "application/octet-stream"); + REGISTER_MIME("bmp", "image/bmp"); + REGISTER_MIME("bz", "application/x-bzip"); + REGISTER_MIME("bz2", "application/x-bzip2"); + REGISTER_MIME("cda", "application/x-cdf"); + REGISTER_MIME("csh", "application/x-csh"); + REGISTER_MIME("css", "text/css"); + REGISTER_MIME("csv", "text/csv"); + REGISTER_MIME("dmg", "application/x-apple-diskimage"); + REGISTER_MIME("doc", "application/msword"); + REGISTER_MIME("docx", "application/" "vnd.openxmlformats-officedocument.wordprocessingml.document"); + REGISTER_MIME("eot", "application/vnd.ms-fontobject"); + REGISTER_MIME("epub", "application/epub+zip"); + REGISTER_MIME("gif", "image/gif"); + REGISTER_MIME("gz", "application/gzip"); + REGISTER_MIME("htm", "text/html"); + REGISTER_MIME("html", "text/html"); + REGISTER_MIME("ico", "image/vnd.microsoft.icon"); + REGISTER_MIME("ics", "text/calendar"); + REGISTER_MIME("iso", "application/x-iso9660-image"); + REGISTER_MIME("jar", "application/java-archive"); + REGISTER_MIME("jpe", "image/jpeg"); + REGISTER_MIME("jpeg", "image/jpeg"); + REGISTER_MIME("jpg", "image/jpeg"); + REGISTER_MIME("jpgm", "video/jpm"); + REGISTER_MIME("jpgv", "video/jpeg"); + REGISTER_MIME("jpm", "video/jpm"); + REGISTER_MIME("js", "application/javascript"); + REGISTER_MIME("json", "application/json"); + REGISTER_MIME("jsonld", "application/ld+json"); + REGISTER_MIME("jsonml", "application/jsonml+json"); + REGISTER_MIME("md", "text/markdown"); + REGISTER_MIME("mid", "audio/midi"); + REGISTER_MIME("midi", "audio/midi"); + REGISTER_MIME("mjs", "text/javascript"); + REGISTER_MIME("mp3", "audio/mpeg"); + REGISTER_MIME("mp4", "video/mp4"); + REGISTER_MIME("m4v", "video/mp4"); + REGISTER_MIME("mpeg", "video/mpeg"); + REGISTER_MIME("mpkg", "application/vnd.apple.installer+xml"); + REGISTER_MIME("odp", "application/vnd.oasis.opendocument.presentation"); + REGISTER_MIME("ods", "application/vnd.oasis.opendocument.spreadsheet"); + REGISTER_MIME("odt", "application/vnd.oasis.opendocument.text"); + REGISTER_MIME("oga", "audio/ogg"); + REGISTER_MIME("ogv", "video/ogg"); + REGISTER_MIME("ogx", "application/ogg"); + REGISTER_MIME("opus", "audio/opus"); + REGISTER_MIME("otf", "font/otf"); + REGISTER_MIME("pdf", "application/pdf"); + REGISTER_MIME("php", "application/x-httpd-php"); + REGISTER_MIME("png", "image/png"); + REGISTER_MIME("ppt", "application/vnd.ms-powerpoint"); + REGISTER_MIME("pptx","application/""vnd.openxmlformats-officedocument.presentationml.presentation"); + REGISTER_MIME("rar", "application/vnd.rar"); + REGISTER_MIME("rtf", "application/rtf"); + REGISTER_MIME("sh", "application/x-sh"); + REGISTER_MIME("svg", "image/svg+xml"); + REGISTER_MIME("svgz", "image/svg+xml"); + REGISTER_MIME("tar", "application/x-tar"); + REGISTER_MIME("tif", "image/tiff"); + REGISTER_MIME("tiff", "image/tiff"); + REGISTER_MIME("ts", "video/mp2t"); + REGISTER_MIME("ttf", "font/ttf"); + REGISTER_MIME("txt", "text/plain"); + REGISTER_MIME("vsd", "application/vnd.visio"); + REGISTER_MIME("wav", "audio/wav"); + REGISTER_MIME("weba", "audio/webm"); + REGISTER_MIME("webm", "video/webm"); + REGISTER_MIME("webp", "image/webp"); + REGISTER_MIME("woff", "font/woff"); + REGISTER_MIME("woff2", "font/woff2"); + REGISTER_MIME("xhtml", "application/xhtml+xml"); + REGISTER_MIME("xls", "application/vnd.ms-excel"); + REGISTER_MIME("xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + REGISTER_MIME("xml", "application/xml"); + REGISTER_MIME("xul", "application/vnd.mozilla.xul+xml"); + REGISTER_MIME("zip", "application/zip"); + /* clang-format on */ +} + +#undef REGISTER_MIME + +/* ***************************************************************************** +Constructor / Destructor +***************************************************************************** */ + +FIO_SFUNC void fio___http_cleanup(void *ignr_) { + (void)ignr_; +#if FIO_HTTP_CACHE_LIMIT + for (size_t i = 0; i < 2; ++i) { + const char *names[] = {"cookie names", "header values"}; + FIO_LOG_DEBUG2( + "freeing %zu strings from %s cache (capacity was: %zu)", + fio___http_str_cache_count(&FIO___HTTP_STRING_CACHE[i].cache), + names[i], + fio___http_str_cache_capa(&FIO___HTTP_STRING_CACHE[i].cache)); +#ifdef FIO_LOG_LEVEL_DEBUG + if (FIO_LOG_LEVEL_DEBUG == FIO_LOG_LEVEL) { + FIO_MAP_EACH(fio___http_str_cache, + (&FIO___HTTP_STRING_CACHE[i].cache), + pos) { + fprintf(stderr, "\t \"%s\" (%zu bytes)\n", pos.key.buf, pos.key.len); + } + } +#endif + fio___http_str_cache_destroy(&FIO___HTTP_STRING_CACHE[i].cache); + FIO___LOCK_DESTROY(FIO___HTTP_STRING_CACHE[i].lock); + (void)names; /* if unused */ + } +#endif /* FIO_HTTP_CACHE_LIMIT */ + FIO_LOG_DEBUG2("(%d) HTTP MIME hash storage count/capa: %zu / %zu", + fio_getpid(), + FIO___HTTP_MIMETYPES.count, + fio___http_mime_map_capa(&FIO___HTTP_MIMETYPES)); + fio___http_mime_map_destroy(&FIO___HTTP_MIMETYPES); +} + +FIO_CONSTRUCTOR(fio___http_str_cache_static_builder) { + fio___http_str_cached_init(); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___http_cleanup, NULL); + fio_http_mime_register_essential(); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#undef FIO___HTTP_TIME_DIV +#undef FIO___HTTP_TIME_UNIT + +#endif /* FIO_EXTERN_COMPLETE */ + +#undef FIO_HTTP_HANDLE +#endif /* FIO_HTTP_HANDLE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_HTTP1_PARSER /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + HTTP/1.1 Parser + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_HTTP1_PARSER) && !defined(H___FIO_HTTP1_PARSER___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +/* ***************************************************************************** +The HTTP/1.1 provides static functions only, always as part or implementation. +***************************************************************************** */ +#define H___FIO_HTTP1_PARSER___H + +/* ***************************************************************************** +HTTP/1.x Parser API +***************************************************************************** */ + +/** The HTTP/1.1 parser type */ +typedef struct fio_http1_parser_s fio_http1_parser_s; +/** Initialization value for the parser */ +#define FIO_HTTP1_PARSER_INIT ((fio_http1_parser_s){0}) + +/** + * Parses HTTP/1.x data, calling any callbacks. + * + * Returns bytes consumed or `FIO_HTTP1_PARSER_ERROR` (`(size_t)-1`) on error. + */ +FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, + fio_buf_info_s buf, + void *udata); + +/** Returns true if the parser is waiting to parse a new request/response .*/ +FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p); + +/** The error return value for fio_http1_parse. */ +#define FIO_HTTP1_PARSER_ERROR ((size_t)-1) + +/** Returns the number of bytes of payload still expected to be received. */ +FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p); +/** A return value for `fio_http1_expected` when chunked data is expected. */ +#define FIO_HTTP1_EXPECTED_CHUNKED ((size_t)(-1)) + +/* ***************************************************************************** +HTTP/1.x callbacks (to be implemented by parser user) +***************************************************************************** */ + +/** called when either a request or a response was received. */ +static void fio_http1_on_complete(void *udata); +/** called when a request method is parsed. */ +static int fio_http1_on_method(fio_buf_info_s method, void *udata); +/** called when a response status is parsed. the status_str is the string + * without the prefixed numerical status indicator.*/ +static int fio_http1_on_status(size_t istatus, + fio_buf_info_s status, + void *udata); +/** called when a request URL is parsed. */ +static int fio_http1_on_url(fio_buf_info_s path, void *udata); +/** called when a the HTTP/1.x version is parsed. */ +static int fio_http1_on_version(fio_buf_info_s version, void *udata); +/** called when a header is parsed. */ +static int fio_http1_on_header(fio_buf_info_s name, + fio_buf_info_s value, + void *udata); +/** called when the special content-length header is parsed. */ +static int fio_http1_on_header_content_length(fio_buf_info_s name, + fio_buf_info_s value, + size_t content_length, + void *udata); +/** called when `Expect` arrives and may require a 100 continue response. */ +static int fio_http1_on_expect(void *udata); +/** called when a body chunk is parsed. */ +static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata); + +/* ***************************************************************************** +Implementation Stage Helpers +***************************************************************************** */ + +/* parsing stage 0 - read first line (proxy?). */ +static int fio_http1___start(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_header(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 2 - read body. */ +static int fio_http1___read_body(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 2 - read chunked body. */ +static int fio_http1___read_body_chunked(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_trailer(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* completed parsing. */ +static int fio_http1___finish(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); + +/* ***************************************************************************** +HTTP Parser Type +***************************************************************************** */ + +/** The HTTP/1.1 parser type implementation */ +struct fio_http1_parser_s { + int (*fn)(fio_http1_parser_s *, fio_buf_info_s *, void *); + size_t expected; +}; + +/** Returns true if the parser is waiting to parse a new request/response .*/ +FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p) { + return !p->fn || p->fn == fio_http1___start; +} + +/** Returns the number of bytes of payload still expected to be received. */ +FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p) { + return p->expected; +} + +/* ***************************************************************************** +Main Parsing Loop +***************************************************************************** */ + +FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, + fio_buf_info_s buf, + void *udata) { + int i = 0; + char *buf_start = buf.buf; + if (!buf.len) + return 0; + if (!p->fn) + p->fn = fio_http1___start; + while (!(i = p->fn(p, &buf, udata))) + ; + if (i < 0) + return FIO_HTTP1_PARSER_ERROR; + return buf.buf - buf_start; +} + +/* completed parsing. */ +static int fio_http1___finish(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + (void)buf; + *p = (fio_http1_parser_s){0}; + fio_http1_on_complete(udata); + return 1; +} + +/* ***************************************************************************** +Reading the first line +***************************************************************************** */ + +/* parsing stage 0 - read first line (TODO: proxy protocol support?). */ +static int fio_http1___start(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + /* find line start/end and test */ + fio_buf_info_s wrd[3]; + char *start = buf->buf; + char *tmp; + while ((start[0] == ' ' || start[0] == '\r' || start[0] == '\n') && + start < buf->buf + buf->len) /* skip white space */ + ++start; + if (start == buf->buf + buf->len) { + buf->buf = start; + return 1; + } + char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); + if (!eol) + return 1; + if (start + 13 > eol) /* test for minimal data GET HTTP/1 or ### HTTP/1 */ + return -1; + + /* prep next stage */ + buf->len -= (eol - buf->buf) + 1; + buf->buf = eol + 1; + eol -= eol[-1] == '\r'; + + /* parse first line */ + /* request: method path version ; response: version code txt */ + if (!(tmp = (char *)FIO_MEMCHR(start, ' ', (size_t)(eol - start)))) + return -1; + wrd[0] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); + start = tmp + 1; + if (!(tmp = (char *)FIO_MEMCHR(start, ' ', eol - start))) + return -1; + wrd[1] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); + start = tmp + 1; + if (start >= eol) + return -1; + wrd[2] = FIO_BUF_INFO2(start, (size_t)(eol - start)); + if (fio_c2i(wrd[1].buf[0]) < 10) /* test if path or code */ + goto parse_response_line; + if (wrd[2].len > 14) + wrd[2].len = 14; + if (fio_http1_on_method(wrd[0], udata)) + return -1; + if (fio_http1_on_url(wrd[1], udata)) + return -1; + if (fio_http1_on_version(wrd[2], udata)) + return -1; + return (p->fn = fio_http1___read_header)(p, buf, udata); + +parse_response_line: + if (wrd[0].len > 14) + wrd[0].len = 14; + if (fio_http1_on_version(wrd[0], udata)) + return -1; + if (fio_http1_on_status(fio_atol10u(&wrd[1].buf), wrd[2], udata)) + return -1; + return (p->fn = fio_http1___read_header)(p, buf, udata); +} + +/* ***************************************************************************** +Reading Headers +***************************************************************************** */ + +/* parsing stage 1 - read headers (after `expect` header). */ +static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); + +/* handle headers before calling callback. */ +static inline int fio_http1___on_header(fio_http1_parser_s *p, + fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + /* test for special headers */ + switch (name.len) { + case 6: /* test for "expect" */ + if (value.len == 12 && fio_buf2u32u(name.buf) == fio_buf2u32u("expe") && + fio_buf2u32u(name.buf + 2) == fio_buf2u32u("pect") && + fio_buf2u64u(value.buf) == fio_buf2u64u("100-cont") && + fio_buf2u32u(value.buf + 8) == fio_buf2u32u("inue")) { /* Expect */ + p->fn = fio_http1___read_header_post_expect; + return 0; + } + break; + case 14: /* test for "content-length" */ + if (fio_buf2u64u(name.buf) == fio_buf2u64u("content-") && + fio_buf2u64u(name.buf + 6) == fio_buf2u64u("t-length")) { + char *tmp = value.buf; + uint64_t clen = fio_atol10u(&tmp); + if (tmp != value.buf + value.len) + return -1; + if (p->expected) + return 0 - (p->expected != clen); + p->expected = clen; + return 0 - + (fio_http1_on_header_content_length(name, value, clen, udata) == + -1); + } + break; + case 17: /* test for "transfer-encoding" (chunked?) */ + if (value.len >= 7 && (name.buf[16] == 'g') && + !((fio_buf2u64u(name.buf) ^ fio_buf2u64u("transfer")) | + (fio_buf2u64u(name.buf + 8) ^ fio_buf2u64u("-encodin")))) { + char *c_start = value.buf + value.len - 7; + if ((fio_buf2u32u(c_start) | 0x20202020UL) == fio_buf2u32u("chun") && + (fio_buf2u32u(c_start + 3) | 0x20202020UL) == fio_buf2u32u("nked")) { + if (p->expected && p->expected != FIO_HTTP1_EXPECTED_CHUNKED) + return -1; + p->expected = FIO_HTTP1_EXPECTED_CHUNKED; + /* endpoint does not need to know if the body was chunked or not */ + if (value.len == 7) + return 0; + if (c_start[-1] != ' ' && c_start[-1] != ',' && c_start[-1] != '\t') + return -1; + while ( + (c_start[-1] == ' ' || c_start[-1] == ',' || c_start[-1] == '\t') && + c_start > value.buf) + --c_start; + if (c_start == value.buf) + return 0; + value.len = c_start - value.buf; + } + } + break; + } + /* perform callback */ + return 0 - (fio_http1_on_header(name, value, udata) == -1); +} + +/* handle trailers (chunked encoding only) before calling callback. */ +static inline int fio_http1___on_trailer(fio_http1_parser_s *p, + fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + (void)p; + fio_buf_info_s forbidden[] = { + FIO_BUF_INFO1((char *)"authorization"), + FIO_BUF_INFO1((char *)"cache-control"), + FIO_BUF_INFO1((char *)"content-encoding"), + FIO_BUF_INFO1((char *)"content-length"), + FIO_BUF_INFO1((char *)"content-range"), + FIO_BUF_INFO1((char *)"content-type"), + FIO_BUF_INFO1((char *)"expect"), + FIO_BUF_INFO1((char *)"host"), + FIO_BUF_INFO1((char *)"max-forwards"), + FIO_BUF_INFO1((char *)"set-cookie"), + FIO_BUF_INFO1((char *)"te"), + FIO_BUF_INFO1((char *)"trailer"), + FIO_BUF_INFO1((char *)"transfer-encoding"), + FIO_BUF_INFO2(NULL, 0), + }; /* known forbidden headers in trailer */ + for (size_t i = 0; forbidden[i].buf; ++i) { + if (FIO_BUF_INFO_IS_EQ(name, forbidden[i])) + return -1; + } + return fio_http1_on_header(name, value, udata); +} + +/* returns either a lower case (ASCI) or the original char. */ +static uint8_t fio_http1_tolower(uint8_t c) { + if ((c - ((uint8_t)'A' - 1U)) < ((uint8_t)'Z' - (uint8_t)'A')) + c |= 32; + return c; +} + +/* seeks to the ':' divisor while testing and converting to downcase. */ +static char *fio_http1___seek_header_div(char *p) { + /* this is the subset of the forbidden chars that allows UTF-8 headers */ + static const _Bool forbidden_name_chars[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (;;) { + *p = (char)fio_http1_tolower((uint8_t)(*p)); + ++p; + if (FIO_UNLIKELY(forbidden_name_chars[((uint8_t)(*p))])) + return p; + } +} + +/* extract header name and value from a line and pass info to handler */ +static inline int fio_http1___read_header_line( + fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata, + int (*handler)(fio_http1_parser_s *, + fio_buf_info_s, + fio_buf_info_s, + void *)) { + for (;;) { + char *start = buf->buf; + char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); + char *div; + fio_buf_info_s name, value; + if (!eol) + return 1; + + buf->len -= (eol - buf->buf) + 1; + buf->buf = eol + 1; + eol -= (eol[-1] == '\r'); + if (FIO_UNLIKELY(eol == start)) + goto headers_finished; + + div = fio_http1___seek_header_div(start); + if (div[0] != ':') + return -1; + name = FIO_BUF_INFO2(start, (size_t)(div - start)); + do { + ++div; + } while (*div == ' ' || *div == '\t'); + + if (div != eol) + while (eol[-1] == ' ' || eol[-1] == '\t') + --eol; + value = FIO_BUF_INFO2((div == eol) ? NULL : div, (size_t)(eol - div)); + int r = handler(p, name, value, udata); + if (FIO_UNLIKELY(r)) + return r; + } + +headers_finished: + if (p->fn == fio_http1___read_header_post_expect && p->expected && + fio_http1_on_expect(udata)) + goto expect_failed; + p->fn = (!p->expected) ? fio_http1___finish + : (!(p->expected + 1)) ? fio_http1___read_body_chunked + : fio_http1___read_body; + return p->fn(p, buf, udata); + +expect_failed: + *p = (fio_http1_parser_s){0}; + return 1; +} + +/* parsing stage 1 - read headers. */ +static int fio_http1___read_header(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); +} + +/* parsing stage 1 - read headers (after `expect` header). */ +static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); +} + +/* parsing stage 1 - read headers. */ +static int fio_http1___read_trailer(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_trailer); +} + +/* ***************************************************************************** +Reading the Body +***************************************************************************** */ + +/* parsing stage 2 - read body - known content length. */ +static int fio_http1___read_body(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (!buf->len) + return 1; + if (buf->len >= p->expected) { + buf->len = p->expected; + if (fio_http1_on_body_chunk(*buf, udata)) + return -1; + buf->buf += buf->len; + return fio_http1___finish(p, buf, udata); + } + if (fio_http1_on_body_chunk(*buf, udata)) + return -1; + buf->buf += buf->len; + p->expected -= buf->len; + buf->len = 0; + return 1; +} + +/* ***************************************************************************** +Reading the Body (chunked) +***************************************************************************** */ + +/* parsing stage 2 - read chunked body - read chunk data. */ +static int fio_http1___read_body_chunked_read(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (!buf->len) + return 1; + if (buf->len >= p->expected) { + if (fio_http1_on_body_chunk(FIO_BUF_INFO2(buf->buf, p->expected), udata)) + return -1; + buf->buf += p->expected; + buf->len -= p->expected; + p->fn = fio_http1___read_body_chunked; + return 0; + } + if (fio_http1_on_body_chunk(buf[0], udata)) + return -1; + p->expected -= buf->len; + buf->buf += buf->len; + return 1; +} + +/* parsing stage 2 - read chunked body - read next chunk length. */ +static int fio_http1___read_body_chunked(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + (void)udata; + if (buf->len < 3) + return 1; + { /* remove possible extra EOL after chunk payload */ + size_t tmp = (buf->buf[0] == '\r'); + tmp += (buf->buf[tmp] == '\n'); + buf->len -= tmp; + buf->buf += tmp; + } + + // if (!FIO_MEMCHR(buf->buf, '\n', buf->len)) /* prevent read overflow? */ + // return 1; + + char *eol = buf->buf; + size_t expected = fio_atol16u(&eol); /* may read overflow, tests after */ + if (eol == buf->buf) + return -1; + eol += (eol[0] == '\r'); + if (eol >= buf->buf + buf->len) + return 1; /* read overflowed */ + if (eol[0] != '\n') + return -1; + ++eol; + p->expected = expected; + if (p->expected) { + /* further data expected */ + buf->len -= eol - buf->buf; + buf->buf = eol; + return (p->fn = fio_http1___read_body_chunked_read)(p, buf, udata); + } + if ((eol + 1 < buf->buf + buf->len) && (eol[0] == '\r' || eol[0] == '\n')) { + /* no trailers, finish now. */ + eol += (eol[0] == '\r'); + ++eol; + buf->len -= eol - buf->buf; + buf->buf = eol; + return fio_http1___finish(p, buf, udata); + } + /* possible trailers */ + buf->len -= eol - buf->buf; + buf->buf = eol; + return (p->fn = fio_http1___read_trailer)(p, buf, udata); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#undef FIO_HTTP1_PARSER +#endif /* FIO_HTTP1_PARSER && FIO_EXTERN_COMPLETE*/ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_BITWISE /* Development inclusion - ignore line */ +#define FIO_RAND /* Development inclusion - ignore line */ +#define FIO_WEBSOCKET_PARSER /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + WebSocket Parser + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_WEBSOCKET_PARSER) && !defined(H___FIO_WEBSOCKET_PARSER___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +/* ***************************************************************************** +The parser provides static functions only, always as part or implementation. +***************************************************************************** */ +#define H___FIO_WEBSOCKET_PARSER___H + +/* ***************************************************************************** +WebSocket Parsing API +***************************************************************************** */ + +typedef struct fio_websocket_parser_s fio_websocket_parser_s; +/** + * Parses WebSocket data, calling any callbacks. + * + * Returns bytes consumed or `FIO_WEBSOCKET_PARSER_ERROR` (`(size_t)-1`) on + * error. + */ +FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, + fio_buf_info_s buf, + void *udata); + +// FIO_SFUNC + +/** The parsers return value on error. */ +#define FIO_WEBSOCKET_PARSER_ERROR ((size_t)-1) + +/* ***************************************************************************** +WebSocket Parsing Callbacks +***************************************************************************** */ + +/** Called when a message frame was received. */ +FIO_SFUNC void fio_websocket_on_message(void *udata, + fio_buf_info_s msg, + unsigned char is_text); + +/** + * Called when the parser needs to copy the message to an external buffer. + * + * MUST return the external buffer, as it may need to be unmasked. + * + * Partial message length may be equal to zero (`partial.len == 0`). + */ +FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, + fio_buf_info_s partial, + size_t more_expected); + +/** Called when the permessage-deflate extension requires decompression. */ +FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, + fio_buf_info_s msg); + +/** Called when a `ping` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg); + +/** Called when a `pong` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg); + +/** Called when a `close` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, fio_buf_info_s msg); + +/* ***************************************************************************** +WebSocket Formatting API +***************************************************************************** */ +/** + * Returns the length of the buffer required to wrap a message `len` long + * + * Client connections should add 4 to this number to accommodate for the mask. + */ +FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len); + +/** + * Wraps a WebSocket server message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Further opcode values: + * * %x0 denotes a continuation frame + * * %x1 denotes a text frame + * * %x2 denotes a binary frame + * * %x3-7 are reserved for further non-control frames + * * %x8 denotes a connection close + * * %x9 denotes a ping + * * %xA denotes a pong + * * %xB-F are reserved for further control frames + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len)` + */ +FIO_SFUNC uint64_t fio_websocket_server_wrap(void *target, + const void *msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv); + +/** + * Wraps a WebSocket client message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4` + */ +FIO_SFUNC uint64_t fio_websocket_client_wrap(void *target, + const void *msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv); + +/* ***************************************************************************** +API - Parsing (unwrapping) +***************************************************************************** */ + +/* ***************************************************************************** + + Implementation + +***************************************************************************** */ + +/** returns the length of the buffer required to wrap a message `len` long */ +FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len) { + return len + 2ULL + ((len > 125) << 1) + + ((0ULL - (len > ((1UL << 16) - 1))) & 6ULL); +} + +/* ***************************************************************************** +Message Wrapping +***************************************************************************** */ + +FIO_IFUNC uint64_t fio_websocket_header(void *target, + uint64_t message_len, + uint32_t mask, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + ((uint8_t *)target)[0] = 0U | + /*fin*/ ((last & 1U) << 7) | + /* opcode */ ((16U - !!first) & (opcode & 15U)) | + /* rsv */ ((rsv & 7) << 4); + ((uint8_t *)target)[1] = ((!!mask) << 7U); + size_t mask_l = ((!!mask) << 2); + if (message_len < 126) { + ((uint8_t *)target)[1] |= message_len; + if (mask) + fio_u2buf32u(((uint8_t *)target + 2), mask); + return 2 + mask_l; + } else if (message_len < (1UL << 16)) { + /* head is 4 bytes */ + ((uint8_t *)target)[1] |= 126; + fio_u2buf16_be(((uint8_t *)target + 2), message_len); + if (mask) + fio_u2buf32u(((uint8_t *)target + 4), mask); + return 4 + mask_l; + } else { + /* Really Long Message */ + ((uint8_t *)target)[1] |= 127; + fio_u2buf64_be(((uint8_t *)target + 2), message_len); + if (mask) + fio_u2buf32u(((uint8_t *)target + 10), mask); + return 10 + mask_l; + } +} + +/** + * Wraps a WebSocket server message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Further opcode values: + * * %x0 denotes a continuation frame + * * %x1 denotes a text frame + * * %x2 denotes a binary frame + * * %x3-7 are reserved for further non-control frames + * * %x8 denotes a connection close + * * %x9 denotes a ping + * * %xA denotes a pong + * * %xB-F are reserved for further control frames + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len)` + */ +FIO_SFUNC uint64_t fio_websocket_server_wrap(void *restrict target, + const void *restrict msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + uint64_t r = fio_websocket_header(target, len, 0, opcode, first, last, rsv); + FIO_MEMCPY(((uint8_t *)target) + r, msg, len); + r += len; + return r; +} + +/** + * Wraps a WebSocket client message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len) + + * 4` + */ +FIO_SFUNC uint64_t fio_websocket_client_wrap(void *restrict target, + const void *restrict msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + uint64_t mask = (fio_rand64() | 0x01020408ULL) & 0xFFFFFFFFULL; /* non-zero */ + mask |= mask << 32; + uint64_t r = fio_websocket_header(target, + len, + (uint32_t)mask, + opcode, + first, + last, + rsv); + fio_xmask_cpy((((char *)target) + r), (const char *)msg, len, mask); + r += len; + return r; +} + +/* ***************************************************************************** +WebSocket Parser Type +***************************************************************************** */ + +/** The WebSocket parser type implementation */ +struct fio_websocket_parser_s { + int (*fn)(fio_websocket_parser_s *, fio_buf_info_s *, void *); + uint64_t start_at; + uint64_t expect; + uint32_t mask; + uint8_t first; + uint8_t current; + uint8_t must_mask; +}; + +/* ***************************************************************************** +Frame Consumption +***************************************************************************** */ +FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata); + +FIO_SFUNC int fio___websocket_consume_frame_partial(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + fio_websocket_write_partial(udata, *buf, (p->expect -= buf->len)); + buf->buf += buf->len; + buf->len = 0; + return 1; +} + +FIO_SFUNC int fio___websocket_consume_frame_finish(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + fio_buf_info_s msg = FIO_BUF_INFO2(buf->buf, p->expect); + buf->buf += p->expect; + buf->len -= p->expect; + p->expect = 0; + msg = fio_websocket_write_partial(udata, msg, 0); + if (!msg.buf) /* protocol error response from callback */ + return -1; + fio_xmask(msg.buf + p->start_at, + msg.len - p->start_at, + (((uint64_t)p->mask) << 32) | (uint64_t)p->mask); + p->start_at += msg.len; + p->fn = fio___websocket_consume_header; + if (!(p->current & 128)) /* done? if not, consume next frame */ + return 0; + /* done */ + if (p->first & 64) { /* RSV1 set: decompress */ + msg = fio_websocket_decompress(udata, msg); + if (!msg.buf) + return -1; + } + size_t cond = (p->first & 15); + *p = (fio_websocket_parser_s){.fn = fio___websocket_consume_header}; + switch (cond) { + case 0: return -1; /* continuation - error? */ + case 1: /* fall through */ /* text / data frame */ + case 2: fio_websocket_on_message(udata, msg, (cond & 1)); return 1; + case 8: fio_websocket_on_protocol_close(udata, msg); return 1; + case 9: fio_websocket_on_protocol_ping(udata, msg); return 1; + case 10: fio_websocket_on_protocol_pong(udata, msg); return 1; + default: + FIO_LOG_DDEBUG2("ERROR: WebSocket protocol error - unknown opcode %u\n", + (unsigned int)(p->first & 15)); + return -1; + } + return 1; +} + +FIO_SFUNC int fio___websocket_consume_frame(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return (p->expect > buf->len + ? fio___websocket_consume_frame_partial + : fio___websocket_consume_frame_finish)(p, buf, udata); +} + +/* ***************************************************************************** +Header Consumption +***************************************************************************** */ +FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (buf->len < 2) + return 1; + const uint8_t mask_f = (((uint8_t *)buf->buf)[1] >> 7) & 1; + const uint8_t mask_l = (mask_f << 2); + const uint8_t info = (uint8_t)(buf->buf[0]); + uint8_t len_indicator = ((((uint8_t *)buf->buf)[1]) & 127U); + switch (len_indicator) { + case 126: + if (buf->len < 8UL) + return 1; + p->expect = fio_buf2u16_be(buf->buf + 2); + p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 4); + buf->buf += 4 + mask_l; + buf->len -= 4 + mask_l; + break; + + case 127: + if (buf->len < 14UL) + return 1; + p->expect = fio_buf2u64_be(buf->buf + 2); + if (p->expect & 0xFF00000000000000ULL) + return -1; /* really?! */ + p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 10); + buf->buf += 10 + mask_l; + buf->len -= 10 + mask_l; + break; + + default: + if (buf->len < (2ULL + mask_l)) + return 1; + p->expect = len_indicator; + p->mask = mask_f ? fio_buf2u32u(buf->buf + 2) : 0; + buf->buf += 2 + mask_l; + buf->len -= 2 + mask_l; + break; + } + if (p->first) { + p->current = info; + if ((info & 15)) /* continuation frame == 0 ; is it missing? */ + return -1; + } else { + p->first = p->current = info; + p->start_at = 0; + if (!(info & 15)) /* continuation frame == 0 ; where's the first? */ + return -1; + } + if (p->must_mask && !p->mask) + return -1; + return (p->fn = fio___websocket_consume_frame)(p, buf, udata); +} +/* ***************************************************************************** +Main Parsing Loop +***************************************************************************** */ + +FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, + fio_buf_info_s buf, + void *udata) { + int i = 0; + char *buf_start = buf.buf; + if (!buf.len) + return 0; + if (!p->fn) + p->fn = fio___websocket_consume_header; + while (!(i = p->fn(p, &buf, udata))) + ; + if (i < 0) + return FIO_WEBSOCKET_PARSER_ERROR; + return buf.buf - buf_start; +} + +/* ***************************************************************************** +Reading the first line +***************************************************************************** */ + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ + +#undef FIO_WEBSOCKET_PARSER +#endif /* FIO_WEBSOCKET_PARSER && FIO_EXTERN_COMPLETE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_HTTP /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + HTTP Implementation for FIO_SERVER + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_HTTP) && !defined(H___FIO_HTTP___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_HTTP___H +/* ***************************************************************************** +HTTP Setting Defaults +***************************************************************************** */ + +#ifndef FIO_HTTP_DEFAULT_MAX_HEADER_SIZE +#define FIO_HTTP_DEFAULT_MAX_HEADER_SIZE 32768 /* (1UL << 15) */ +#endif +#ifndef FIO_HTTP_DEFAULT_MAX_LINE_LEN +#define FIO_HTTP_DEFAULT_MAX_LINE_LEN 8192 /* (1UL << 13) */ +#endif +#ifndef FIO_HTTP_DEFAULT_MAX_BODY_SIZE +#define FIO_HTTP_DEFAULT_MAX_BODY_SIZE 33554432 /* (1UL << 25) */ +#endif +#ifndef FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE +#define FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE 262144 /* (1UL << 18) */ +#endif +#ifndef FIO_HTTP_DEFAULT_TIMEOUT +#define FIO_HTTP_DEFAULT_TIMEOUT 50 +#endif +#ifndef FIO_HTTP_DEFAULT_TIMEOUT_LONG +#define FIO_HTTP_DEFAULT_TIMEOUT_LONG 50 +#endif + +#ifndef FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER +/** Adds a "content-length" header to the HTTP handle (usually redundant). */ +#define FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER 0 +#endif + +#ifndef FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT +/** UTF-8 validity tests will be performed only for data shorter than this. */ +#define FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT ((1UL << 16) - 10UL) +#endif + +#ifndef FIO_WEBSOCKET_STATS +/* If true, logs longest WebSocket round-trips (using FIO_LOG_INFO). */ +#define FIO_WEBSOCKET_STATS 0 +#endif + +/* ***************************************************************************** +HTTP Listen +***************************************************************************** */ +typedef struct fio_http_settings_s { + /** Called before body uploads, when a client sends an `Expect` header. */ + void (*pre_http_body)(fio_http_s *h); + /** Callback for HTTP requests (server) or responses (client). */ + void (*on_http)(fio_http_s *h); + /** Called when a request / response cycle is finished with no Upgrade. */ + void (*on_finish)(fio_http_s *h); + /** (optional) the callback to be performed when the HTTP service closes. */ + void (*on_stop)(struct fio_http_settings_s *settings); + + /** Authenticate EventSource (SSE) requests, return non-zero to deny.*/ + int (*on_authenticate_sse)(fio_http_s *h); + /** Authenticate WebSockets Upgrade requests, return non-zero to deny.*/ + int (*on_authenticate_websocket)(fio_http_s *h); + + /** Called once a WebSocket / SSE connection upgrade is complete. */ + void (*on_open)(fio_http_s *h); + + /** Called when a WebSocket message is received. */ + void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); + /** Called when an EventSource event is received. */ + void (*on_eventsource)(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); + /** Called when an EventSource reconnect event requests an ID. */ + void (*on_eventsource_reconnect)(fio_http_s *h, fio_buf_info_s id); + + /** Called for WebSocket / SSE connections when outgoing buffer is empty. */ + void (*on_ready)(fio_http_s *h); + /** Called for open WebSocket / SSE connections during shutting down. */ + void (*on_shutdown)(fio_http_s *h); + /** Called after a WebSocket / SSE connection is closed (for cleanup). */ + void (*on_close)(fio_http_s *h); + + /** Default opaque user data for HTTP handles (fio_http_s). */ + void *udata; + /** Optional SSL/TLS support. */ + fio_io_functions_s *tls_io_func; + /** Optional SSL/TLS support. */ + fio_io_tls_s *tls; + /** Optional HTTP task queue (for multi-threading HTTP responses) */ + fio_io_async_s *queue; + /** + * A public folder for file transfers - allows to circumvent any application + * layer logic and simply serve static files. + * + * Supports automatic `gz` pre-compressed alternatives. + */ + fio_str_info_s public_folder; + /** + * The max-age value (in seconds) for caching static files send from + * `public_folder`. + * + * Defaults to 0 (not sent). + */ + size_t max_age; + /** + * The maximum total of bytes for the overall size of the request string and + * headers, combined. + * + * Defaults to FIO_HTTP_DEFAULT_MAX_HEADER_SIZE bytes. + */ + uint32_t max_header_size; + /** + * The maximum number of bytes allowed per header / request line. + * + * Defaults to FIO_HTTP_DEFAULT_MAX_LINE_LEN bytes. + */ + uint32_t max_line_len; + /** + * The maximum size of an HTTP request's body (posting / downloading). + * + * Defaults to FIO_HTTP_DEFAULT_MAX_BODY_SIZE bytes. + */ + size_t max_body_size; + /** + * The maximum websocket message size/buffer (in bytes) for Websocket + * connections. Defaults to FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE bytes. + */ + size_t ws_max_msg_size; + /** reserved for future use. */ + intptr_t reserved1; + /** reserved for future use. */ + intptr_t reserved2; + /** + * An HTTP/1.x connection timeout. + * + * Defaults to FIO_HTTP_DEFAULT_TIMEOUT seconds. + * + * Note: the connection might be closed (by other side) before timeout occurs. + */ + uint8_t timeout; + /** + * Timeout for the WebSocket connections in seconds. Defaults to + * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. + * + * A ping will be sent whenever the timeout is reached. + * + * Connections are only closed when a ping cannot be sent (the network layer + * fails). Pongs are ignored. + */ + uint8_t ws_timeout; + /** + * Timeout for EventSource (SSE) connections in seconds. Defaults to + * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. + * + * A ping will be sent whenever the timeout is reached. + * + * Connections are only closed when a ping cannot be sent (the network layer + * fails). + */ + uint8_t sse_timeout; + /** Timeout for client connections (only relevant in client mode). */ + uint8_t connect_timeout; + /** Logging flag - set to TRUE to log HTTP requests. */ + uint8_t log; +} fio_http_settings_s; + +/** Listens to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC void *fio_http_listen(const char *url, fio_http_settings_s settings); + +/** Listens to HTTP / WebSockets / SSE connections on `url`. */ +#define fio_http_listen(url, ...) \ + fio_http_listen(url, (fio_http_settings_s){__VA_ARGS__}) + +/** Allows all clients to connect (bypasses authentication). */ +SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h); + +/** Returns the IO object associated with the HTTP object (request only). */ +SFUNC fio_io_s *fio_http_io(fio_http_s *); + +/** Macro helper for HTTP handle pub/sub subscriptions. */ +#define fio_http_subscribe(h, ...) \ + fio_subscribe(.io = fio_http_io(h), __VA_ARGS__) + +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC fio_io_s *fio_http_connect(const char *url, + fio_http_s *h, + fio_http_settings_s settings); + +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +#define fio_http_connect(url, h, ...) \ + fio_http_connect(url, h, (fio_http_settings_s){__VA_ARGS__}) + +/** Returns the HTTP settings associated with the HTTP object, if any. */ +SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *); + +/* ***************************************************************************** +WebSocket Helpers - HTTP Upgraded Connections +***************************************************************************** */ + +/** Writes a WebSocket message. Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_websocket_write(fio_http_s *h, + const void *buf, + size_t len, + uint8_t is_text); + +/** + * Sets a specific on_message callback for this connection. + * + * Returns -1 on error (i.e., upgrade still in negotiation). + */ +SFUNC int fio_http_on_message_set(fio_http_s *h, + void (*on_message)(fio_http_s *, + fio_buf_info_s, + uint8_t)); + +/** Optional WebSocket subscription callback. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg); +/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg); +/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg); + +/* ***************************************************************************** +EventSource (SSE) Helpers - HTTP Upgraded Connections +***************************************************************************** */ + +/** Named arguments for fio_http_sse_write. */ +typedef struct { + /** The message's `id` data (if any). */ + fio_buf_info_s id; + /** The message's `event` data (if any). */ + fio_buf_info_s event; + /** The message's `data` data (if any). */ + fio_buf_info_s data; +} fio_http_sse_write_args_s; + +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_sse_write(fio_http_s *h, fio_http_sse_write_args_s args); + +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +#define fio_http_sse_write(h, ...) \ + fio_http_sse_write((h), ((fio_http_sse_write_args_s){__VA_ARGS__})) + +/** Optional EventSource subscription callback - messages MUST be UTF-8. */ +SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg); + +/* ***************************************************************************** +Module Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* +REMEMBER: +======== + +All memory allocations should use: +* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE_(ptr, size) + +*/ + +/* ***************************************************************************** +HTTP Settings Validation +***************************************************************************** */ + +static void fio___http_default_on_http_request(fio_http_s *h) { + fio_http_send_error_response(h, 404); +} +static void fio___http_default_noop(fio_http_s *h) { ((void)h); } +static int fio___http_default_authenticate(fio_http_s *h) { + ((void)h); + return -1; +} + +// on_queue +static void fio___http_default_on_stop(struct fio_http_settings_s *settings) { + ((void)settings); +} + +static void fio___http_default_close(fio_http_s *h) { + fio_io_close(fio_http_io(h)); +} + +/** Called when a WebSocket message is received. */ +static void fio___http_default_on_message(fio_http_s *h, + fio_buf_info_s msg, + uint8_t is_text) { + (void)h, (void)msg, (void)is_text; +} +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data) { + (void)h, (void)id, (void)event, (void)data; +} +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource_redirect(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); + +/** Called when an EventSource reconnect event requests an ID. */ +static void fio___http_default_on_eventsource_reconnect(fio_http_s *h, + fio_buf_info_s id) { + (void)h, (void)id; +} + +static void http_settings_validate(fio_http_settings_s *s, int is_client) { + if (!s->pre_http_body) + s->pre_http_body = fio___http_default_noop; + + if (!s->on_http) + s->on_http = is_client ? fio___http_default_noop + : fio___http_default_on_http_request; + if (!s->on_finish) + s->on_finish = fio___http_default_noop; + if (!s->on_stop) + s->on_stop = fio___http_default_on_stop; + if (!s->on_authenticate_sse) + s->on_authenticate_sse = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW + : fio___http_default_authenticate; + if (!s->on_authenticate_websocket) + s->on_authenticate_websocket = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW + : fio___http_default_authenticate; + if (!s->on_open) + s->on_open = fio___http_default_noop; + if (!s->on_open) + s->on_open = fio___http_default_noop; + if (!s->on_message) + s->on_message = fio___http_default_on_message; + if (!s->on_eventsource) + s->on_eventsource = (s->on_message == fio___http_default_on_message + ? fio___http_default_on_eventsource + : fio___http_default_on_eventsource_redirect); + if (!s->on_eventsource_reconnect) + s->on_eventsource_reconnect = fio___http_default_on_eventsource_reconnect; + if (!s->on_ready) + s->on_ready = fio___http_default_noop; + if (!s->on_shutdown) + s->on_shutdown = fio___http_default_noop; + if (!s->on_close) + s->on_close = fio___http_default_noop; + if (!s->max_header_size) + s->max_header_size = FIO_HTTP_DEFAULT_MAX_HEADER_SIZE; + if (!s->max_line_len) + s->max_line_len = FIO_HTTP_DEFAULT_MAX_LINE_LEN; + if (!s->max_body_size) + s->max_body_size = FIO_HTTP_DEFAULT_MAX_BODY_SIZE; + if (!s->ws_max_msg_size) + s->ws_max_msg_size = FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE; + if (!s->timeout) + s->timeout = FIO_HTTP_DEFAULT_TIMEOUT; + if (!s->ws_timeout) + s->ws_timeout = FIO_HTTP_DEFAULT_TIMEOUT_LONG; + if (!s->sse_timeout) + s->sse_timeout = s->ws_timeout; + + if (s->max_header_size < s->max_line_len) + s->max_header_size = s->max_line_len; + + if (s->public_folder.buf) { + if (s->public_folder.len > 1 && + s->public_folder.buf[s->public_folder.len - 1] == '/' && + !(s->public_folder.len == 2 && s->public_folder.buf[0] == '~')) + --s->public_folder.len; + if (!fio_filename_is_folder(s->public_folder.buf)) { + FIO_LOG_ERROR( + "HTTP public folder is not a folder, setting ignored.\n\t%s", + s->public_folder.buf); + s->public_folder = ((fio_str_info_s){0}); + } + } +} + +/* ***************************************************************************** +HTTP Protocols used by the HTTP module +***************************************************************************** */ + +typedef enum fio___http_protocol_selector_e { + FIO___HTTP_PROTOCOL_ACCEPT = 0, + FIO___HTTP_PROTOCOL_HTTP1, + FIO___HTTP_PROTOCOL_HTTP2, + FIO___HTTP_PROTOCOL_WS, + FIO___HTTP_PROTOCOL_SSE, + FIO___HTTP_PROTOCOL_NONE +} fio___http_protocol_selector_e; + +/* ***************************************************************************** +HTTP Protocol Container (vtable + settings storage) +***************************************************************************** */ +#define FIO___RECURSIVE_INCLUDE 1 + +typedef struct { + fio_http_settings_s settings; + void (*on_http_callback)(void *, void *); + fio_queue_s *queue; + struct { + fio_io_protocol_s protocol; + fio_http_controller_s controller; + } state[FIO___HTTP_PROTOCOL_NONE + 1]; + char public_folder_buf[]; +} fio___http_protocol_s; +#include FIO_INCLUDE_FILE + +#define FIO_REF_NAME fio___http_protocol +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(o) \ + do { \ + if (o.settings.tls) \ + fio_io_tls_free(o.settings.tls); \ + if (o.settings.on_stop) \ + o.settings.on_stop(&o.settings); \ + } while (0) +#include FIO_INCLUDE_FILE + +FIO_IFUNC fio___http_protocol_s *fio___http_protocol_init( + fio___http_protocol_s *p, + const char *url, + fio_http_settings_s s, + bool is_client); +/* ***************************************************************************** +HTTP Connection Container +***************************************************************************** */ + +struct fio___http_connection_http_s { + void (*on_http_callback)(void *, void *); + void (*on_http)(fio_http_s *h); + void (*on_finish)(fio_http_s *h); + fio_http1_parser_s parser; + fio_str_info_s buf; + uint32_t max_header; +}; +struct fio___http_connection_ws_s { + void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); + void (*on_ready)(fio_http_s *h); + fio_websocket_parser_s parser; + char *msg; + uint16_t code; +}; +struct fio___http_connection_sse_s { + void (*on_message)(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); + void (*on_ready)(fio_http_s *h); + fio_buf_info_s id; + fio_buf_info_s event; + char *data; +}; + +/** Connection objects for managing HTTP / WebSocket connection state. */ +typedef struct { + fio_io_s *io; + fio_http_s *h; + fio_http_settings_s *settings; + fio_queue_s *queue; + void *udata; + union { + struct fio___http_connection_http_s http; + struct fio___http_connection_ws_s ws; + struct fio___http_connection_sse_s sse; + } state; + uint32_t len; + uint32_t capa; + uint8_t log; + uint8_t suspend; + uint8_t is_client; + char buf[]; +} fio___http_connection_s; + +#define FIO_REF_NAME fio___http_connection +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_DESTROY(o) \ + do { \ + fio___http_protocol_free( \ + FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, o.settings)); \ + } while (0) +#include FIO_INCLUDE_FILE + +#undef FIO___RECURSIVE_INCLUDE + +/* ***************************************************************************** +Revisit defaults +***************************************************************************** */ + +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource_redirect(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + c->settings->on_message(h, data, 1); + (void)h, (void)id, (void)event, (void)data; +} + +/* ***************************************************************************** +HTTP Request handling / handling +***************************************************************************** */ + +FIO_SFUNC void fio___http_perform_user_callback(void *cb_, void *h_) { + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (FIO_LIKELY(fio_io_is_open(c->io))) + cb.fn(h); + fio_http_free(h); +} + +FIO_SFUNC void fio___http_perform_user_upgrade_callback_websocket(void *cb_, + void *h_) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + struct fio___http_connection_http_s old = c->state.http; + if (cb.fn(h)) + goto refuse_upgrade; + if (c->h) /* request after WebSocket Upgrade? an attack vector? */ + goto refuse_upgrade; +#if HAVE_ZLIB && 0 /* TODO: logs and fix extension handling logic */ + FIO_HTTP_HEADER_EACH_VALUE(/* TODO: setup WebSocket extension */ + h, + 1, + FIO_STR_INFO2((char *)"sec-websocket-extensions", + 24), + val) { + FIO_LOG_DDEBUG2("WebSocket extension requested: %.*s", + (int)val.len, + val.buf); + if (!FIO_STR_INFO_IS_EQ(val, + FIO_STR_INFO2((char *)"permessage-deflate", 18))) + continue; + size_t client_bits = 0, server_bits = 0; + FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(val, p) { + FIO_LOG_DDEBUG2("\t %.*s: %.*s", + (int)p.name.len, + p.name.buf, + (int)p.value.len, + p.value.buf); + if (FIO_STR_INFO_IS_EQ(p.name, + FIO_STR_INFO2((char *)"client_max_window_bits", + 22))) { /* used by chrome */ + char *iptr = p.value.buf; + client_bits = iptr ? fio_atol10u(&iptr) : 0; + if (client_bits < 8 || client_bits > 15) + client_bits = (size_t)-1; + } + if (FIO_STR_INFO_IS_EQ( + p.name, + FIO_STR_INFO2((char *)"server_max_window_bits", 22))) { + char *iptr = p.value.buf; + server_bits = iptr ? fio_atol10u(&iptr) : 0; + if (server_bits < 8 || server_bits > 15) + server_bits = (size_t)-1; + } + } + if (client_bits) + ; /* TODO */ + if (server_bits) + ; /* TODO */ + break; + } /* HAVE_ZLIB */ +#endif + fio_http_upgrade_websocket(h); + return; + +refuse_upgrade: + c->state.http = old; + if (fio_http_send_error_response(h, 403)) + fio_io_free(c->io); + fio_http_free(h); +} + +FIO_SFUNC void fio___http_perform_user_upgrade_callback_sse(void *cb_, + void *h_) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (cb.fn(h)) + goto refuse_upgrade; + if (c->h) /* request after eventsource? an attack vector? */ + goto refuse_upgrade; + fio_http_upgrade_sse(h); + return; + +refuse_upgrade: + if (fio_http_send_error_response(h, 403)) + fio_io_free(c->io); + fio_http_free(h); +} + +FIO_IFUNC int fio___http_on_http_test4upgrade(fio_http_s *h, + fio___http_connection_s *c) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb; + if (fio_http_websocket_requested(h)) + goto websocket_requested; + if (fio_http_sse_requested(h)) + goto sse_requested; + return 0; +websocket_requested: + cb.fn = c->settings->on_authenticate_websocket; + fio_queue_push(c->queue, + fio___http_perform_user_upgrade_callback_websocket, + cb.ptr, + (void *)h); + return -1; + +sse_requested: + cb.fn = c->settings->on_authenticate_sse; + fio_queue_push(c->queue, + fio___http_perform_user_upgrade_callback_sse, + cb.ptr, + (void *)h); + return -1; + +#if 0 +http2_requested: + // Connection: Upgrade, HTTP2-Settings + // Upgrade: h2c + // HTTP2-Settings: + return 0; /* allowed to ignore upgrade request */ +#endif +} + +FIO_SFUNC void fio___http_on_http_direct(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio_http_status_set(h, 200); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio___http_on_http_test4upgrade(h, c)) + return; + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); + (void)ignr; +} + +FIO_SFUNC void fio___http_on_http_with_public_folder(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio_http_status_set(h, 200); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio___http_on_http_test4upgrade(h, c)) + return; + if ((fio_http_method(h).len != 4 || (fio_buf2u32u(fio_http_method(h).buf) | + 0x20202020UL) != fio_buf2u32u("post")) && + !fio_http_static_file_response(h, + c->settings->public_folder, + fio_http_path(h), + c->settings->max_age)) { + fio_http_free(h); + return; + } + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); + (void)ignr; +} + +FIO_SFUNC void fio___http_perform_user_callback_client(void *cb_, void *h_) { + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + fio___http_perform_user_callback(cb_, h_); + fio_io_free(c->io); +} + +FIO_SFUNC void fio___http_on_http_client(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + size_t pr = FIO___HTTP_PROTOCOL_WS; + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + + /* TODO! review WS and SSE responses. */ + if (fio_http_websocket_accepted(h)) + goto websocket_accepted; + if (fio_http_sse_accepted(h)) + goto sse_accepted; + fio_queue_push(c->queue, + fio___http_perform_user_callback_client, + cb.ptr, + (void *)h); + return; + (void)ignr; + +sse_accepted: + pr = FIO___HTTP_PROTOCOL_SSE; + +websocket_accepted: + c->h = h; /* was set to NULL in `on_http_complete` */ + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr] + .controller)); + fio_io_protocol_set( + c->io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr] + .protocol)); + + FIO_LOG_DDEBUG2("(%d) Client %s upgrade complete for fd %d", + fio_io_pid(), + (fio_http_is_websocket(h) ? "WebSocket" : "SSE"), + fio_io_fd(c->io)); + + fio_io_free(c->io); /* fio_dup called by fio_http1_on_complete */ + c->suspend = 0; + fio_io_unsuspend(c->io); +} + +/* ***************************************************************************** +ALPN Helpers +***************************************************************************** */ + +FIO_SFUNC void fio___http_on_select_h1(fio_io_s *io) { + FIO_LOG_DDEBUG2("TLS ALPN HTTP/1.1 selected for %p", io); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_io_protocol_set( + io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol)); +} +FIO_SFUNC void fio___http_on_select_h2(fio_io_s *io) { + FIO_LOG_ERROR("TLS ALPN HTTP/2 not supported for %p", io); + (void)io; +} + +/* ***************************************************************************** +HTTP Listen +***************************************************************************** */ + +static void fio___http_listen_on_start(fio_io_protocol_s *protocol, void *u) { + (void)u; + fio___http_protocol_s *p = (fio___http_protocol_s *)protocol; + p->queue = ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q + : fio_io_queue()); +} + +static void fio___http_listen_on_stop(fio_io_protocol_s *p, void *u) { + (void)u; + fio___http_protocol_free( + FIO_PTR_FROM_FIELD(fio___http_protocol_s, + state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + p)); +} + +void fio_http_listen___(void); /* IDE marker */ +SFUNC void *fio_http_listen FIO_NOOP(const char *url, fio_http_settings_s s) { + http_settings_validate(&s, 0); + fio___http_protocol_s *p = fio___http_protocol_new(s.public_folder.len + 1); + fio___http_protocol_init(p, url, s, 0); + void *listener = + fio_io_listen(.url = url, + .protocol = &p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + .tls = s.tls, + .on_start = fio___http_listen_on_start, + .on_stop = fio___http_listen_on_stop, + .queue_for_accept = p->settings.queue); + return listener; +} + +/* ***************************************************************************** +HTTP Connect +***************************************************************************** */ + +static void fio___http_connect_on_failed(fio_io_protocol_s *p, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_free(c->h); + c->h = NULL; + fio___http_connection_free(c); + (void)p; +} + +void fio_http_connect___(void); /* IDE Marker */ +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC fio_io_s *fio_http_connect FIO_NOOP(const char *url, + fio_http_s *h, + fio_http_settings_s s) { + FIO_STR_INFO_TMP_VAR(origin, 4096); + http_settings_validate(&s, 1); + fio_url_s u = (fio_url_s){0}; + if (url) + u = fio_url_parse(url, strlen(url)); + + if (!h) + h = fio_http_new(); + if (!fio_http_path(h).len) + fio_http_path_set(h, + u.path.len ? FIO_BUF2STR_INFO(u.path) + : FIO_STR_INFO2((char *)"/", 1)); + if (!fio_http_query(h).len && u.query.len) + fio_http_query_set(h, FIO_BUF2STR_INFO(u.query)); + if (!fio_http_method(h).len) + fio_http_method_set(h, FIO_STR_INFO2((char *)"GET", 3)); + if (u.host.len) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"host", 4), + FIO_BUF2STR_INFO(u.host)); + /* Origin header */ + fio_string_write2( + &origin, + NULL, + FIO_STRING_WRITE_STR2("https", (size_t)(4 + fio_url_is_tls(u).tls)), + FIO_STRING_WRITE_STR2("://", 3U), + FIO_STRING_WRITE_STR_INFO(u.host), + FIO_STRING_WRITE_STR2(":", (size_t)(!!u.port.len)), + FIO_STRING_WRITE_STR_INFO(u.port)); + } + + /* test for ws:// or wss:// - WebSocket scheme */ + if ((u.scheme.len == 2 || + (u.scheme.len == 3 && ((u.scheme.buf[2] | 0x20) == 's'))) && + (fio_buf2u16u(u.scheme.buf) | 0x2020) == fio_buf2u16u("ws")) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"origin", 6), + origin); + fio_http_websocket_set_request(h); + } + /* test for sse:// or sses:// - Server Sent Events scheme */ + else if ((u.scheme.len == 3 || + (u.scheme.len == 4 && ((u.scheme.buf[3] | 0x20) == 's'))) && + (fio_buf2u32u(u.scheme.buf) | fio_buf2u32u("\x20\x20\x20\xFF")) == + fio_buf2u32u("sse\xFF")) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"origin", 6), + origin); + fio_http_sse_set_request(h); + } + + /* TODO: test for and attempt to re-use connection */ + // if (fio_http_cdata(h)) { } + + fio___http_protocol_s *p = fio___http_protocol_new(u.host.len); + fio___http_protocol_init(p, url, s, 1); + fio___http_connection_s *c = + fio___http_connection_new(p->settings.max_line_len); + FIO_ASSERT_ALLOC(c); + *c = (fio___http_connection_s){ + .io = NULL, + .h = h, + .settings = &(p->settings), + .queue = p->queue, + .udata = p->settings.udata, + .state.http = + { + .on_http_callback = p->on_http_callback, + .on_http = p->settings.on_http, + .on_finish = p->settings.on_finish, + .max_header = p->settings.max_header_size, + }, + .capa = p->settings.max_line_len, + .log = p->settings.log, + .is_client = 1, + }; + fio_http_controller_set(h, &p->state[FIO___HTTP_PROTOCOL_HTTP1].controller); + if (!fio_http_udata(h)) /* avoid overwriting existing `udata` if set */ + fio_http_udata_set(h, c->udata); + fio_http_cdata_set(h, fio___http_connection_dup(c)); + return fio_io_connect(url, + .protocol = + &p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol, + .on_failed = fio___http_connect_on_failed, + .udata = c, + .tls = s.tls, + .timeout = s.connect_timeout); +} + +/* ***************************************************************************** +HTTP/1.1 Request / Response Completed +***************************************************************************** */ + +/** called when either a request or a response was received. */ +static void fio_http1_on_complete(void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_io_dup(c->io); /* make sure the IO and its data are valid in callback */ + fio_io_suspend(c->io); + fio_http_s *h = c->h; + c->h = NULL; + c->suspend = 1; + // fio_io_defer(c->state.http.on_http_callback, h, NULL); + fio_queue_push(fio_io_queue(), c->state.http.on_http_callback, h); +} + +/* ***************************************************************************** +HTTP/1.1 Parser callbacks +***************************************************************************** */ + +FIO_IFUNC void fio___http_request_too_big(fio___http_connection_s *c) { + fio_http_s *h = c->h; + fio_io_dup(c->io); /* sending the response will result in fio_undup */ + fio_io_suspend(c->io); + c->h = NULL; + c->suspend = 1; + if (fio_http_send_error_response(h, 413)) + fio_io_free(c->io); /* response not sent, we need to fio_undup */ + fio_http_free(h); +} + +FIO_IFUNC void fio_http1_attach_handle(fio___http_connection_s *c) { + c->h = fio_http_new(); + FIO_ASSERT_ALLOC(c->h); + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings)) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .controller); + fio_http_udata_set(c->h, c->udata); + fio_http_cdata_set(c->h, fio___http_connection_dup(c)); +} + +/** called when a request method is parsed. */ +static int fio_http1_on_method(fio_buf_info_s method, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (c->h) + return -1; + fio_http1_attach_handle(c); + fio_http_method_set(c->h, FIO_BUF2STR_INFO(method)); + return 0; +} +/** called when a response status is parsed. the status_str is the string + * without the prefixed numerical status indicator.*/ +static int fio_http1_on_status(size_t istatus, + fio_buf_info_s status, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_clear_response(c->h, istatus != 301 && istatus != 302); + fio_http_status_set(c->h, istatus); + return 0; + (void)status; +} +/** called when a request URL is parsed. */ +static int fio_http1_on_url(fio_buf_info_s url, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_url_s u = fio_url_parse(url.buf, url.len); + if (!u.path.len || u.path.buf[0] != '/') + return -1; + fio_http_path_set(c->h, FIO_BUF2STR_INFO(u.path)); + if (u.query.len) + fio_http_query_set(c->h, FIO_BUF2STR_INFO(u.query)); + if (u.host.len) + (!(c->h) ? fio_http_request_header_set + : fio_http_response_header_set)(c->h, + FIO_STR_INFO1((char *)"host"), + FIO_BUF2STR_INFO(u.host)); + return 0; +} +/** called when a the HTTP/1.x version is parsed. */ +static int fio_http1_on_version(fio_buf_info_s version, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_ASSERT_DEBUG(c->h, "on_version called without a pre-existing handle!"); + if (!c->h) + return -1; + fio_http_version_set(c->h, FIO_BUF2STR_INFO(version)); + return 0; +} +/** called when a header is parsed. */ +static int fio_http1_on_header(fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->h) + return 0; /* ignore possible post-error response headers */ + (!fio_http_status(c->h) + ? fio_http_request_header_add + : fio_http_response_header_add)(c->h, + FIO_BUF2STR_INFO(name), + FIO_BUF2STR_INFO(value)); + return 0; +} +/** called when the special content-length header is parsed. */ +static int fio_http1_on_header_content_length(fio_buf_info_s name, + fio_buf_info_s value, + size_t content_length, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_s *h = c->h; + if (!h) + return 0; + if (content_length > c->settings->max_body_size) + goto too_big; + if (content_length) + fio_http_body_expect(c->h, content_length); +#if FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER + (!(h->status) ? fio_http_request_header_add + : fio_http_response_header_add)(h, + FIO_BUF2STR_INFO(name), + FIO_BUF2STR_INFO(value)); +#endif + return 0; +too_big: + fio___http_request_too_big(c); + return 0; /* should we disconnect (return -1), or not? */ + (void)name, (void)value; +} +/** called when `Expect` arrives and may require a 100 continue response. */ +static int fio_http1_on_expect(void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + const fio_buf_info_s response = + FIO_BUF_INFO1((char *)"HTTP/1.1 100 Continue\r\n\r\n"); + fio_http_s *h = c->h; + if (!h) + return 1; + c->h = NULL; + /* TODO: test for body size violation and deny request if payload too big. */ + if (FIO_HTTP1_EXPECTED_CHUNKED != fio_http1_expected(&c->state.http.parser) && + c->settings->max_body_size > fio_http1_expected(&c->state.http.parser)) + goto payload_too_big; + c->settings->pre_http_body(h); + if (fio_http_status(h)) + goto response_sent; + c->h = h; + fio_io_write2(c->io, .buf = response.buf, .len = response.len, .copy = 0); + return 0; /* TODO?: improve support for `expect` headers? */ +payload_too_big: + fio_io_dup(c->io); + if (fio_http_send_error_response(h, 413)) + fio_io_free(c->io); /* response not sent, we need to fio_undup */ + /* fall through */ +response_sent: + // c->h = NULL; + fio_http_free(h); + return 1; +} + +/** called when a body chunk is parsed. */ +static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->h) + return -1; /* close connection if a large payload is unstoppable */ + if (c->is_client && + (fio_http_status(c->h) == 301 || fio_http_status(c->h) == 302)) + return 0; /* don't overwrite client payload on redirect */ + if (chunk.len + fio_http_body_length(c->h) > c->settings->max_body_size) + goto too_big; + fio_http_body_write(c->h, chunk.buf, chunk.len); + return 0; +too_big: + fio___http_request_too_big(c); + return 0; +} + +/* ***************************************************************************** +HTTP/1.1 Accepting new connections (tests for special HTTP/2 pre-knowledge) +***************************************************************************** */ + +/** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___http_on_attach_accept(fio_io_s *io) { + + fio___http_protocol_s *p = + FIO_PTR_FROM_FIELD(fio___http_protocol_s, + state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + fio_io_protocol(io)); + fio___http_protocol_dup(p); + // p->queue = fio_io_queue(); + + const uint32_t capa = p->settings.max_line_len; + fio___http_connection_s *c = fio___http_connection_new(capa); + FIO_ASSERT_ALLOC(c); + *c = (fio___http_connection_s){ + .io = io, + .settings = &(p->settings), + .queue = + ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q + : fio_io_queue()), + .udata = p->settings.udata, + .state.http = + { + .on_http_callback = p->on_http_callback, + .on_http = p->settings.on_http, + .on_finish = p->settings.on_finish, + .max_header = p->settings.max_header_size, + }, + .capa = capa, + .log = p->settings.log, + }; + fio_io_udata_set(io, (void *)c); + FIO_LOG_DDEBUG2("(%d) HTTP accepted a new connection (%p)", + (int)fio_thread_getpid(), + c->io); +#if 0 /* skip pre-knowledge test? */ + fio_io_protocol_set( + io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol)); +#endif +} + +/** Called when a data is available. */ +FIO_SFUNC void fio___http1_accept_on_data(fio_io_s *io) { + const fio_buf_info_s prior_knowledge = FIO_BUF_INFO2( + (char *)"\x50\x52\x49\x20\x2a\x20\x48\x54\x54\x50\x2f\x32\x2e\x30" + "\x0d\x0a\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a", + 24); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_io_protocol_s *phttp_new; + size_t r = fio_io_read(io, c->buf + c->len, c->capa - c->len); + if (!r) /* nothing happened */ + return; + c->len = (uint32_t)r; + if (prior_knowledge.buf[0] != c->buf[0] || + FIO_MEMCMP( + prior_knowledge.buf, + c->buf, + (c->len > prior_knowledge.len ? prior_knowledge.len : c->len))) { + /* no prior knowledge, switch to HTTP/1.1 */ + phttp_new = + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol); + fio_io_protocol_set(io, phttp_new); + return; + } + if (c->len < prior_knowledge.len) /* wait for more data */ + return; + + if (c->len > prior_knowledge.len) + FIO_MEMMOVE(c->buf, + c->buf + prior_knowledge.len, + c->len - prior_knowledge.len); + c->len -= prior_knowledge.len; + phttp_new = &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP2] + .protocol); + + fio_io_protocol_set(io, phttp_new); +} + +FIO_SFUNC void fio___http_on_close(void *buf, void *udata) { + FIO_LOG_DDEBUG2("(%d) HTTP connection closed for %p", + (int)fio_thread_getpid(), + udata); + fio___http_connection_s *c = (fio___http_connection_s *)udata; + c->io = NULL; + fio_http_free(c->h); + fio___http_connection_free(c); + (void)buf; +} + +/* ***************************************************************************** +HTTP/1.1 Protocol +***************************************************************************** */ + +FIO_SFUNC int fio___http1_process_data(fio_io_s *io, + fio___http_connection_s *c) { + (void)io, (void)c; + size_t consumed = fio_http1_parse(&c->state.http.parser, + FIO_BUF_INFO2(c->buf, c->len), + (void *)c); + if (!consumed) + return -1; + if (consumed == FIO_HTTP1_PARSER_ERROR) + goto http1_error; + c->len -= consumed; + if (c->len) + FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); + if (c->suspend) + return -1; + return 0; + +http1_error: + FIO_LOG_DDEBUG2("HTTP/1.1 parser error! disconnecting client at %d", + fio_io_fd(io)); + if (c->h) { + fio_http_s *h = c->h; + c->h = NULL; + if (!c->is_client) { + fio_io_dup(c->io); + if (fio_http_send_error_response(h, 400)) + fio_io_free(c->io); + } + fio_http_free(h); + } + fio_io_close(io); + return -1; +} + +// /** Called when a data is available. */ +FIO_SFUNC void fio___http1_on_data(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->capa == c->len) + return; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + if (fio___http1_process_data(io, c)) + return; + } +} + +// /** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___http1_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + if (c->len) + fio___http1_process_data(io, c); + return; +} + +/* ***************************************************************************** +HTTP/1.1 Client Protocol +***************************************************************************** */ + +/** Iterates through all cookies. A non-zero return will stop iteration. */ +FIO_SFUNC int fio_http1___write_client_cookie_callback(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *udata) { + fio_str_info_s *buf = (fio_str_info_s *)udata; + fio_string_write2(buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2("cookie:", 7), + FIO_STRING_WRITE_STR_INFO(name), + FIO_STRING_WRITE_STR2("=", 1), + FIO_STRING_WRITE_STR_INFO(value), + FIO_STRING_WRITE_STR2("\r\n", 2)); + return 0; + (void)h; +} + +/** called by the HTTP handle for each header. */ +FIO_SFUNC int fio_http1___write_header_callback(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *out_) { + (void)h; + /* manually copy, as this is an "all or nothing" copy (no truncation) */ + fio_str_info_s *out = (fio_str_info_s *)out_; + return fio_string_write2(out, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2(name.buf, name.len), + FIO_STRING_WRITE_STR2(":", 1), + FIO_STRING_WRITE_STR2(value.buf, value.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); +} + +FIO_SFUNC void fio___http1_send_request(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) + return; + fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); + /* set Content-Length (client is never streaming) */ + if (fio_http_body_length(h)) { + char ibuf[32]; + fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); + fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); + v.len = fio_digits10u(fio_http_body_length(h)); + fio_ltoa10u(v.buf, fio_http_body_length(h), v.len); + fio_http_request_header_set(h, k, v); + } + { /* set sensible defaults for common headers (Accept, User-Agent) */ + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO1((char *)"accept"), + FIO_STR_INFO1((char *)"*/*")); + fio_http_request_header_set_if_missing( + h, + FIO_STR_INFO1((char *)"user-agent"), + FIO_STR_INFO1((char *)"facil.io/" FIO_VERSION_STRING)); + } + { /* write status string */ + fio_str_info_s method = fio_http_method(h); + fio_str_info_s path = fio_http_path(h); + fio_str_info_s version = fio_http_version(h); + if (!path.len) + path = FIO_STR_INFO1((char *)"/"); + if ((version.len - 1) > 15) + version = FIO_STR_INFO1((char *)"HTTP/1.1"); + fio_string_write2(&buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR_INFO(method), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR_INFO(path), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR_INFO(version), + FIO_STRING_WRITE_STR2("\r\n", 2)); + } + /* write headers */ + fio_http_request_header_each(h, fio_http1___write_header_callback, &buf); + /* write cookies */ + fio_http_cookie_each(h, fio_http1___write_client_cookie_callback, &buf); + fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); + /* send data (moves memory ownership) */ + fio_io_write2(c->io, + .buf = buf.buf, + .len = buf.len, + .dealloc = FIO_STRING_FREE, + .copy = 0); + /* make sure we listen to incoming data */ + c->suspend = 0; + fio_io_unsuspend(c->io); + /* Write Body */ + if (!fio_http_body_length(h)) + return; + fio_http_body_seek(h, 0); + if (fio_http_body_fd(h) == -1) { + buf = fio_http_body_read(h, (size_t)-1); + fio_io_write2(c->io, + .buf = (char *)fio_http_dup(h), + .len = buf.len, + .offset = (size_t)((char *)h - buf.buf), + .dealloc = (void (*)(void *))fio_http_free); + } else { + fio_io_write2(c->io, + .fd = fio_http_body_fd(h), + .len = fio_http_body_length(h), + .copy = 1); + } +} + +FIO_SFUNC void fio___http1_on_attach_client(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + // c->io = fio_io_dup(io); + c->io = io; + fio___http1_send_request(c->h); + if (c->len) + fio___http1_process_data(io, c); + return; +} + +/* ***************************************************************************** +HTTP/1 Controller +***************************************************************************** */ +FIO_SFUNC int fio___http_controller_get_fd(fio_http_s *h) { + return fio_io_fd(fio_http_io(h)); +} + +/** Informs the controller that request / response headers must be sent. */ +FIO_SFUNC void fio___http_controller_http1_send_headers(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) + return; + fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); + { /* write status string */ + fio_str_info_s ver = fio_http_version(h); + fio_str_info_s status = fio_http_status2str(fio_http_status(h)); + if (ver.len > 15) { + FIO_LOG_ERROR("HTTP/1.1 client version string too long!"); + ver = FIO_STR_INFO1((char *)"HTTP/1.1"); + } + fio_string_write2(&buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2(ver.buf, ver.len), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_NUM(fio_http_status(h)), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR2(status.buf, status.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + } + + /* write headers */ + fio_http_response_header_each(h, fio_http1___write_header_callback, &buf); + /* write cookies */ + fio_http_set_cookie_each(h, fio_http1___write_header_callback, &buf); + /* add streaming headers? */ + if (fio_http_is_streaming(h)) + fio_string_write(&buf, + FIO_STRING_REALLOC, + "transfer-encoding: chunked\r\n", + 28); + fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); + /* send data (move memory ownership)? */ + c->state.http.buf = buf; + return; + fio_io_write2(c->io, + .buf = buf.buf, + .len = buf.len, + .dealloc = FIO_STRING_FREE, + .copy = 0); +} +/** called by the HTTP handle for each body chunk (or to finish a response. */ +FIO_SFUNC void fio___http_controller_http1_write_body( + fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) + goto no_write_err; + if (fio_http_is_streaming(h)) + goto stream_chunk; + if (c->state.http.buf.len && args.buf && args.len) { + fio_string_write(&c->state.http.buf, + FIO_STRING_REALLOC, + (char *)args.buf + args.offset, + args.len); + if (args.dealloc) + args.dealloc((void *)args.buf); + fio_io_write2(c->io, + .buf = (void *)c->state.http.buf.buf, + .len = c->state.http.buf.len, + .dealloc = FIO_STRING_FREE); + c->state.http.buf = FIO_STR_INFO0; + return; + } + + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); + return; + +stream_chunk: + if (args.len) { /* print chunk header */ + char buf[24]; + fio_str_info_s i = FIO_STR_INFO3(buf, 0, 24); + fio_string_write_hex(&i, NULL, args.len); + fio_string_write(&i, NULL, "\r\n", 2); + fio_io_write2(c->io, .buf = (void *)i.buf, .len = i.len, .copy = 1); + } else { + FIO_LOG_ERROR("HTTP1 streaming requires a correctly pre-determined " + "length per chunk."); + } + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); + /* print chunk trailer */ + { + fio_buf_info_s trailer = FIO_BUF_INFO2((char *)"\r\n", 2); + fio_io_write2(c->io, .buf = trailer.buf, .len = trailer.len, .copy = 1); + } + return; +no_write_err: + if (args.buf) { + if (args.dealloc) + args.dealloc((void *)args.buf); + } else if (args.fd != -1) { + close(args.fd); + } +} + +FIO_SFUNC void fio___http_controller_http1_on_finish_task(void *c_, + void *upgraded) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->suspend = 0; + if (c->state.http.buf.len) { + fio_io_write2(c->io, + .buf = (void *)c->state.http.buf.buf, + .len = c->state.http.buf.len, + .dealloc = FIO_STRING_FREE); + c->state.http.buf = FIO_STR_INFO0; + } + + if (upgraded) + goto upgraded; + + if (fio_io_is_open(c->io)) { + /* TODO: test for connection:close header and h->status values */ + fio___http1_process_data(c->io, c); + } + if (!c->suspend) + fio_io_unsuspend(c->io); + fio_io_free(c->io); + return; + +upgraded: + if (c->h || !fio_io_is_open(c->io)) + goto something_is_wrong; + c->h = (fio_http_s *)upgraded; + { + const size_t pr_i = fio_http_is_websocket(c->h) ? FIO___HTTP_PROTOCOL_WS + : FIO___HTTP_PROTOCOL_SSE; + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr_i] + .controller)); + fio_io_protocol_set( + c->io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr_i] + .protocol)); + if (pr_i == FIO___HTTP_PROTOCOL_SSE) { + fio_str_info_s last_id = + fio_http_request_header(c->h, + FIO_STR_INFO2((char *)"last-event-id", 13), + 0); + if (last_id.buf) + c->settings->on_eventsource_reconnect(c->h, FIO_STR2BUF_INFO(last_id)); + } + } + fio_io_unsuspend(c->io); + fio_io_free(c->io); + return; + +something_is_wrong: + if (fio_io_is_open(c->io)) + FIO_LOG_DEBUG2("(%d) Connection upgrade went wrong for fd %d - closing", + fio_io_pid(), + fio_io_fd(c->io)); + fio_io_protocol_set(c->io, NULL); /* make zombie, timeout will clear it. */ + fio_io_free(c->io); + fio___http_connection_free(c); /* free HTTP connection element */ +} + +/** called once a request / response had finished */ +FIO_SFUNC void fio___http_controller_http1_on_finish(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio_http_is_streaming(h)) + fio_io_write2(c->io, .buf = (char *)"0\r\n\r\n", .len = 5, .copy = 1); + if (c->log) + fio_http_write_log(h); + if (fio_http_is_upgraded(h)) + goto upgraded; + /* once the function returns, `h` may be freed (auto-finish on free). + * so we must call this callback here (sync), no matter the thread */ + c->state.http.on_finish(h); + fio_io_defer(fio___http_controller_http1_on_finish_task, (void *)(c), NULL); + return; + +upgraded: + fio_io_defer(fio___http_controller_http1_on_finish_task, + (void *)(c), + (void *)h); +} + +/* ***************************************************************************** +HTTP/2 Protocol (disconnect, as HTTP/2 is unsupported) +***************************************************************************** */ + +// /** Called when an IO is attached to a protocol. */ +// void (*on_attach)(fio_io_s *io); +// /** Called when a data is available. */ +// void (*on_data)(fio_io_s *io); +// /** called once all pending `fio_io_write` calls are finished. */ +// void (*on_ready)(fio_io_s *io); +// /** Called after the connection was closed, and pending tasks +// completed. +// */ void (*on_close)(void *udata); + +/* ***************************************************************************** +HTTP/2 Controller (TODO!) +***************************************************************************** */ + +// /** Called when an HTTP handle is freed. */ +// void (*on_destroyed)(fio_http_s *h, void *cdata); +// /** Informs the controller that request / response headers must be +// sent. +// */ void (*send_headers)(fio_http_s *h); +// /** called by the HTTP handle for each body chunk (or to finish a +// response. +// */ void (*write_body)(fio_http_s *h, fio_http_write_args_s args); +// /** called once a request / response had finished */ +// void (*on_finish)(fio_http_s *h); + +/* ***************************************************************************** +Authentication Helper +***************************************************************************** */ + +/** Allows all clients to connect (bypasses authentication). */ +SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h) { + ((void)h); + return 0; +} + +/* ***************************************************************************** +WebSocket Parser Callbacks +***************************************************************************** */ + +FIO_SFUNC int fio___websocket_process_data(fio_io_s *io, + fio___http_connection_s *c); + +FIO_SFUNC void fio___websocket_on_message_finalize(void *c_, void *ignr_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->suspend = 0; + if (c->len) + fio___websocket_process_data(c->io, c); + fio_io_unsuspend(c->io); + fio_io_free(c->io); + fio___http_connection_free(c); + (void)ignr_; +} + +FIO_SFUNC void fio___websocket_on_message_task(void *c_, void *is_text) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->state.ws.on_message(c->h, + fio_bstr_buf(c->state.ws.msg), + (uint8_t)(uintptr_t)is_text); + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; + fio_io_defer(fio___websocket_on_message_finalize, c, NULL); +} + +/** Called when a message frame was received. */ +FIO_SFUNC void fio_websocket_on_message(void *udata, + fio_buf_info_s msg, + unsigned char is_text) { + /* TODO: suspend IO and queue in async queue? */ + fio___http_connection_s *c = (fio___http_connection_s *)udata; + // c->state.ws.on_message(c->h, + // fio_bstr_buf(c->state.ws.msg), + // (uint8_t)(uintptr_t)is_text); + // fio_bstr_free(c->state.ws.msg); + // c->state.ws.msg = NULL; + // c->suspend = 0; + // fio___websocket_process_data(c->io, c); + // if (!c->suspend) + // fio_io_unsuspend(c->io); + // return; /* TODO: FIXME! */ + fio_io_dup(c->io); + fio___http_connection_dup(c); + fio_io_suspend(c->io); + c->suspend = 1; + fio_queue_push(c->queue, + fio___websocket_on_message_task, + udata, + (void *)(uintptr_t)is_text); + (void)msg; +} + +/** + * Called when the parser needs to copy the message to an external buffer. + * + * MUST return the external buffer, as it may need to be unmasked. + * + * Partial message length may be equal to zero (`partial.len == 0`). + */ +FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, + fio_buf_info_s partial, + size_t more_expected) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->state.ws.msg && more_expected) + c->state.ws.msg = fio_bstr_reserve(NULL, more_expected + partial.len); + c->state.ws.msg = fio_bstr_write(c->state.ws.msg, partial.buf, partial.len); + return fio_bstr_buf(c->state.ws.msg); +} + +/** Called when the permessage-deflate extension requires decompression. */ +FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, + fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_LOG_ERROR("WebSocket permessage-deflate not yet implemented!"); + (void)c; + return msg; +} + +/** Called when a `ping` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (msg.len < 248) { + char buf[256]; + size_t len = + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(buf, msg.buf, msg.len, 0x0A, 1, 1, 0); + fio_io_write2(c->io, .buf = buf, .len = len, .copy = 1); + } else { + char *pong = fio_bstr_reserve(NULL, msg.len + 11); + size_t len = (c->is_client ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(pong, + msg.buf, + msg.len, + 0x0A, + 1, + 1, + 0); + pong = fio_bstr_len_set(pong, len); + fio_io_write2(c->io, + .buf = pong, + .len = len, + .dealloc = (void (*)(void *))fio_bstr_free); + } + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; +} + +/** Called when a `pong` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg) { +#if (DEBUG - 1 + 1) || (FIO_WEBSOCKET_STATS - 1 + 1) + { + char *pos = msg.buf; + static uint64_t longest = 0; + uint64_t ping_time = fio_io_last_tick() - fio_atol16u(&pos); + if (ping_time < (1 << 16) && longest < ping_time) { + longest = ping_time; + FIO_LOG_INFO("WebSocket longest ping round-trip detected as: %zums", + (size_t)ping_time); + } + } +#endif + FIO_LOG_DDEBUG2("Pong (%zu): %s", msg.len, msg.buf); + (void)msg; /* do nothing */ + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; +} + +/** Called when a `close` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, + fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + char buf[32]; + size_t len = fio_websocket_server_wrap(buf, NULL, 0, 0x08, 1, 1, 0); + fio_io_write(c->io, buf, len); + if (msg.len > 1) + c->state.ws.code = fio_buf2u16_be(msg.buf); + fio_io_close(c->io); + if (msg.len > 2) + FIO_LOG_DDEBUG2("WebSocket %p closed with error message: %s", + c->io, + msg.buf + 2); + (void)msg; +} + +/* ***************************************************************************** +WebSocket Protocol +***************************************************************************** */ + +FIO_SFUNC int fio___websocket_process_data(fio_io_s *io, + fio___http_connection_s *c) { + (void)io, (void)c; + size_t consumed = fio_websocket_parse(&c->state.ws.parser, + FIO_BUF_INFO2(c->buf, c->len), + (void *)c); + if (!consumed) + return -1; + if (consumed == FIO_WEBSOCKET_PARSER_ERROR) + goto ws_error; + c->len -= consumed; + if (c->len) + FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); + if (c->suspend) + return -1; + return 0; + +ws_error: + FIO_LOG_DDEBUG2("WebSocket protocol error?"); + fio_websocket_on_protocol_close((void *)c, ((fio_buf_info_s){0})); + return -1; +} + +/** Called when a data is available. */ +FIO_SFUNC void fio___websocket_on_data(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->capa == c->len) + return; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + if (fio___websocket_process_data(io, c)) + return; + } +} + +FIO_SFUNC void fio___websocket_on_ready(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + if (!h) + return; + c->state.ws.on_ready(h); +} + +FIO_SFUNC void fio___websocket_on_timeout(fio_io_s *io) { + char buf[32]; + char tm[20] = "0x00000000000000000"; + fio_ltoa16u(tm + 2, fio_io_last_tick(), 16); + size_t len = fio_websocket_server_wrap(buf, tm, 18, 0x09, 1, 1, 0); + fio_io_write(io, buf, len); +} + +FIO_SFUNC void fio___websocket_on_shutdown(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + c->settings->on_shutdown(c->h); + fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +} + +/** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___websocket_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + c->state.ws = (struct fio___http_connection_ws_s){ + .on_message = c->settings->on_message, + .on_ready = c->settings->on_ready, + .parser = {.must_mask = !c->is_client}, + }; + c->settings->on_open(h); + fio___websocket_process_data(io, c); +} + +/** Called after the connection was closed, and pending tasks completed. */ +FIO_SFUNC void fio___websocket_on_close(void *buf, void *udata) { + FIO_LOG_DDEBUG2("(%d) WebSocket connection closed for %p", + (int)fio_thread_getpid(), + udata); + fio___http_connection_s *c = (fio___http_connection_s *)udata; + c->io = NULL; + fio_bstr_free(c->state.ws.msg); + if (c->h) { + fio_http_status_set(c->h, (size_t)(c->state.ws.code)); + c->settings->on_close(c->h); + c->settings->on_finish(c->h); + fio_http_free(c->h); + } + fio___http_connection_free(c); + (void)buf; +} + +/** + * Sets a specific on_message callback for this connection. + * + * Returns -1 on error (i.e., upgrade still in negotiation). + */ +SFUNC int fio_http_on_message_set(fio_http_s *h, + void (*on_message)(fio_http_s *, + fio_buf_info_s, + uint8_t)) { + if (!h) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c) + return -1; + if (!on_message) + on_message = c->settings->on_message; + c->state.ws.on_message = on_message; + return 0; +} + +/* ***************************************************************************** +WebSocket Writing / Subscription Helpers +***************************************************************************** */ + +FIO_IFUNC void fio___http_websocket_subscribe_imp(fio_msg_s *msg, + uint8_t is_text) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(msg->io); + if (!c) + return; + fio_http_websocket_write(c->h, msg->message.buf, msg->message.len, is_text); +} + +/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg) { + fio___http_websocket_subscribe_imp(msg, 1); +} +/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg) { + fio___http_websocket_subscribe_imp(msg, 0); +} + +/** Optional WebSocket subscription callback. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg) { + ((msg->message.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) && + (fio_string_utf8_valid( + FIO_STR_INFO2((char *)msg->message.buf, msg->message.len))) + ? FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT + : FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY)(msg); +} + +/* ***************************************************************************** +EventSource (SSE) Helpers - HTTP Upgraded Connections +***************************************************************************** */ + +void fio_http_sse_write___(void); /* IDE Marker */ +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_sse_write FIO_NOOP(fio_http_s *h, + fio_http_sse_write_args_s args) { + if (!args.data.len || !h || !fio_http_is_sse(h)) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c || !c->io) + return -1; + char *payload = + fio_bstr_reserve(NULL, args.id.len + args.event.len + args.data.len + 22); + if (args.id.len) + payload = fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("id:", 3), + FIO_STRING_WRITE_STR2(args.id.buf, args.id.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + if (args.event.len) + payload = + fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("event:", 6), + FIO_STRING_WRITE_STR2(args.event.buf, args.event.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + { /* separate lines (add "data:" at beginning of each new line) */ + char *pos; + while (args.data.len && + (pos = (char *)FIO_MEMCHR(args.data.buf, '\n', args.data.len))) { + const size_t len = (pos + 1) - args.data.buf; + pos -= (pos[-1] == '\r'); + payload = fio_bstr_write2( + payload, + FIO_STRING_WRITE_STR2("data:", 5), + FIO_STRING_WRITE_STR2(args.data.buf, (size_t)(pos - args.data.buf)), + FIO_STRING_WRITE_STR2("\r\n", 2)); + args.data.buf += len; + args.data.len -= len; + } + } + /* write reminder */ + if (args.data.len) + payload = + fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("data:", 5), + FIO_STRING_WRITE_STR2(args.data.buf, args.data.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + /* event ends on empty line */ + payload = fio_bstr_write(payload, "\r\n", 2); + fio_io_write2(c->io, + .buf = payload, + .len = fio_bstr_len(payload), + .dealloc = (void (*)(void *))fio_bstr_free); + return 0; +} + +/** Optional EventSource subscription callback - messages MUST be UTF-8. */ +SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(msg->io); + if (!c) + return; + FIO_STR_INFO_TMP_VAR(id_str, 64); + fio_string_write_hex(&id_str, NULL, msg->id); + fio_http_sse_write(c->h, + .id = FIO_STR2BUF_INFO(id_str), + .event = FIO_STR2BUF_INFO(msg->channel), + .data = FIO_STR2BUF_INFO(msg->message)); +} + +/* ***************************************************************************** +WebSocket Writing / Subscription Helpers +***************************************************************************** */ + +SFUNC int fio_http_websocket_write(fio_http_s *h, + const void *buf, + size_t len, + uint8_t is_text) { + if (!h || !fio_http_is_websocket(h)) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c) + return -1; + is_text = (!!is_text); + is_text |= (!is_text) << 1; + uint8_t rsv = 0; + if (len < 512) { /* fast-path: no allocation, no compression */ + char tmp[520]; + size_t wlen = + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(tmp, buf, len, is_text, 1, 1, rsv); + fio_io_write2(c->io, .buf = tmp, .len = wlen, .copy = 1); + return 0; + } +#if HAVE_ZLIB /* TODO: compress? */ + // if(c->state.ws.deflate) ; +#endif + char *payload = + fio_bstr_reserve(NULL, + fio_websocket_wrapped_len(len) + (c->is_client << 2)); + payload = fio_bstr_len_set( + payload, + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(payload, buf, len, is_text, 1, 1, rsv)); + fio_io_write2(c->io, + .buf = payload, + .len = fio_bstr_len(payload), + .dealloc = (void (*)(void *))fio_bstr_free); + return 0 - !fio_io_is_open(c->io); +} + +/* ***************************************************************************** +WebSocket Controller +***************************************************************************** */ + +/* Called by the HTTP handle for each body chunk (or to finish a response). */ +FIO_SFUNC void fio___http_controller_ws_write_body(fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (args.buf && args.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) { + unsigned char is_text = + !!fio_string_utf8_valid(FIO_STR_INFO2((char *)args.buf, args.len)); + fio_http_websocket_write(h, (void *)args.buf, args.len, is_text); + if (args.dealloc) + args.dealloc((void *)args.buf); + return; + } + char header[16]; + ((uint8_t *)header)[0] = 0 | 2 | 128; + if (args.len < 126) { + ((uint8_t *)header)[1] = args.len; + fio_io_write(c->io, header, 2); + } else if (args.len < (1UL << 16)) { + /* head is 4 bytes */ + ((uint8_t *)header)[1] = 126 | ((!!c->is_client) << 7); + fio_u2buf16_be(((uint8_t *)header + 2), args.len); + fio_io_write(c->io, header, 4); + } else { + /* Really Long Message */ + ((uint8_t *)header)[1] = 127 | ((!!c->is_client) << 7); + fio_u2buf64_be(((uint8_t *)header + 2), args.len); + fio_io_write(c->io, header, 10); + } + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); +} + +/* ***************************************************************************** +EventSource / SSE Protocol (TODO!) +***************************************************************************** */ + +FIO_SFUNC void fio___sse_consume_data(fio___http_connection_s *c) { + /* TODO: Fix Me! parse and process SSE data */ + FIO_LOG_DEBUG2("SSE data processing:\n%.*s", (int)c->len, c->buf); + struct fio___http_connection_sse_s *sse = &c->state.sse; + const char *next_line = c->buf; + const char *stop = c->buf + c->len; + for (; next_line < stop;) { + char *line = (char *)next_line; + const char *eol = + (const char *)FIO_MEMCHR(next_line, '\n', stop - next_line); + if (!eol) + break; + next_line = eol + 1; + eol -= (eol > c->buf && eol[-1] == '\n'); + eol -= (eol > c->buf && eol[-1] == '\r'); + if (eol == line) { /* empty line, end of input? */ + if (sse->data || sse->event.buf || sse->id.buf) { + sse->on_message(c->h, sse->id, sse->event, fio_bstr_buf(sse->data)); + fio_bstr_free(sse->data); + sse->data = NULL; + sse->event = sse->id = FIO_BUF_INFO0; + } + continue; + } + if (line[0] == ':') /* comment */ + continue; + const size_t line_len = (size_t)(eol - line); + if (line_len > 2 && line[2] == ':') { /* id */ + const char *start = line + 3; + start += (start[0] == ' ' || start[0] == '\t'); + if ((line[0] |= 32) == 'i' && (line[1] |= 32) == 'd') + sse->id = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); + + } else if (line_len > 4 && line[4] == ':') { /* data */ + const char *start = line + 5; + start += (start[0] == ' ' || start[0] == '\t'); + if ((fio_buf2u32u(line) | 0x20202020U) == fio_buf2u32u("data")) { + if (fio_bstr_len(sse->data) + (size_t)(eol - start) > + c->settings->ws_max_msg_size) + goto breach; + sse->data = fio_bstr_write2( + sse->data, + FIO_STRING_WRITE_STR2("\r\n", ((size_t) !!sse->data << 1)), + FIO_STRING_WRITE_STR2(start, (size_t)(eol - start))); + } + + } else if (line_len > 5 && line[5] == ':') { /* event */ + const char *start = line + 3; + start += (start[0] == ' ' || start[0] == '\t'); + if ((line[0] |= 32) == 'e' && + (fio_buf2u32u(line + 1) | 0x20202020U) == fio_buf2u32u("vent")) + sse->event = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); + + } else if (!FIO_MEMCHR(line, ':', line_len)) + goto error; + } + FIO_ASSERT(next_line <= stop, "overflow on next line read"); + if (next_line > stop) + next_line = stop; + c->len -= next_line - c->buf; + if (c->len) + FIO_MEMMOVE(c->buf, next_line, c->len); + return; + +error: + FIO_LOG_ERROR("SSE incoming data malformed!"); + FIO_LOG_DEBUG2("data dump:\n%.*s", (int)c->len, c->buf); + fio_io_close(c->io); + return; + +breach: + FIO_LOG_SECURITY("SSE incoming data payload too large!"); + fio_io_close(c->io); +} + +/** Called when a data is available. */ +FIO_SFUNC void fio___sse_on_data(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) Reading SSE data from socket", fio_io_pid()); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->len + 2 > c->capa) + goto error; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + fio___sse_consume_data(c); + } +error: + FIO_LOG_ERROR("Incoming SSE data too long (HTTP line limit set at %zu)!", + c->capa); + fio_io_close(io); +} + +/** Called when an IO is attached to a protocol. */ +static void fio___sse_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + c->state.sse = (struct fio___http_connection_sse_s){ + .on_message = c->settings->on_eventsource, + .on_ready = c->settings->on_ready, + }; + c->settings->on_open(h); + FIO_LOG_DDEBUG2("(%d) SSE attached; buffer length (unread): %zu", + fio_io_pid(), + c->len); + if (c->len && c->is_client) + fio___sse_consume_data(c); +} + +FIO_SFUNC void fio___sse_on_timeout(fio_io_s *io) { + char buf[32] = ":ping 0x0000000000000000\r\n\r\n"; + fio_ltoa16u(buf + 8, fio_io_last_tick(), 16); + buf[24] = '\r'; /* overwrite written NUL character */ + fio_io_write(io, buf, 28); +} + +FIO_SFUNC void fio___sse_on_shutdown(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + c->settings->on_shutdown(c->h); + // fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +} + +/** Called after the connection was closed, and pending tasks completed. */ +FIO_SFUNC void fio___sse_on_close(void *buf, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_LOG_DDEBUG2("(%d) SSE connection closed for %p", fio_io_pid(), c->io); + c->io = NULL; + fio_bstr_free(c->state.sse.data); + if (c->h) { + c->settings->on_close(c->h); + c->settings->on_finish(c->h); + fio_http_free(c->h); + } + fio___http_connection_free(c); + (void)buf; +} + +/* ***************************************************************************** +EventSource / SSE Controller (TODO!) +***************************************************************************** */ + +/* called by the HTTP handle for each body chunk (or to finish a response. */ +FIO_SFUNC void fio___http_controller_sse_write_body( + fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (args.buf && args.len) { + fio_http_sse_write(c->h, .data = FIO_BUF_INFO2((char *)args.buf, args.len)); + } + if (args.dealloc && args.buf) + args.dealloc((void *)args.buf); + if (!args.buf && (unsigned)(args.fd + 1) > 1) + close(args.fd); +} +/* ***************************************************************************** +Connection Lost +***************************************************************************** */ + +FIO_SFUNC void fio___http_controller_on_destroyed_task(void *c_, void *ignr_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + fio___http_connection_free(c); + (void)ignr_; +} + +FIO_SFUNC void fio___http_controller_http1_on_finish_client_task(void *c_, + void *h_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + fio_http_s *h = (fio_http_s *)h_; + c->settings->on_finish(h); + fio_http_free(h); + fio___http_connection_free(c); +} + +FIO_SFUNC void fio___http_controller_http1_on_finish_client(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + /* on_finish should be called after the `on_close` or after on_http */ + if (!fio_http_is_upgraded(h)) { + /* on_finish always manually called here */ + fio_io_defer(fio___http_controller_http1_on_finish_client_task, + (void *)fio___http_connection_dup(c), + (void *)fio_http_dup(h)); + } +} + +/** Called when an HTTP handle is freed. */ +FIO_SFUNC void fio__http_controller_on_destroyed(fio_http_s *h) { + if (!(fio_http_is_upgraded(h) | fio_http_is_finished(h))) { + /* auto-finish if freed without finishing */ + if (!fio_http_status(h)) + fio_http_status_set(h, 500); /* ignored if headers already sent */ + fio_http_write_args_s args = {.finish = 1}; /* never sets upgrade flag */ + fio_http_write FIO_NOOP(h, args); + } + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (c->state.http.buf.buf) + FIO_STRING_FREE2(c->state.http.buf); + c->state.http.buf = FIO_STR_INFO0; + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); +} + +/** Called when an HTTP handle is freed (no auto-finish, post upgrade). */ +FIO_SFUNC void fio__http_controller_on_destroyed2(fio_http_s *h) { + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); +} + +/** Called when an HTTP handle is freed. */ +FIO_SFUNC void fio__http_controller_on_destroyed_client(fio_http_s *h) { + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + c->state.http.on_finish(h); + c->h = NULL; + if (c->io) + fio_io_close(c->io); + fio_queue_push(fio_io_queue(), fio___http_controller_on_destroyed_task, c); +} + +/* ***************************************************************************** +The Protocols at play +***************************************************************************** */ + +/** Returns a facil.io protocol object with the proper protocol callbacks. */ +FIO_IFUNC fio_io_protocol_s FIO_NOOP +fio___http_protocol_get(fio___http_protocol_selector_e s, int is_client) { + fio_io_protocol_s r = {0}; + (void)is_client, (void)s; + switch (s) { + case FIO___HTTP_PROTOCOL_ACCEPT: + r = (fio_io_protocol_s){.on_attach = fio___http_on_attach_accept, + .on_data = fio___http1_accept_on_data, + .on_close = fio___http_on_close}; + return r; + case FIO___HTTP_PROTOCOL_HTTP1: + if (is_client) { + r = (fio_io_protocol_s){.on_attach = fio___http1_on_attach_client, + .on_data = fio___http1_on_data, + .on_close = fio___http_on_close}; + } else { + r = (fio_io_protocol_s){.on_attach = fio___http1_on_attach, + .on_data = fio___http1_on_data, + .on_close = fio___http_on_close}; + } + return r; + case FIO___HTTP_PROTOCOL_HTTP2: + r = (fio_io_protocol_s){.on_close = fio___http_on_close}; + return r; + case FIO___HTTP_PROTOCOL_WS: + r = (fio_io_protocol_s){ + .on_attach = fio___websocket_on_attach, + .on_data = fio___websocket_on_data, + .on_ready = fio___websocket_on_ready, + .on_close = fio___websocket_on_close, + .on_shutdown = fio___websocket_on_shutdown, + .on_timeout = fio___websocket_on_timeout, + .on_pubsub = FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT, + }; + return r; + case FIO___HTTP_PROTOCOL_SSE: + r = (fio_io_protocol_s){ + .on_attach = fio___sse_on_attach, + .on_data = (is_client ? fio___sse_on_data : NULL), + .on_ready = fio___websocket_on_ready, + .on_close = fio___sse_on_close, + .on_shutdown = fio___sse_on_shutdown, + .on_timeout = fio___sse_on_timeout, + .on_pubsub = FIO_HTTP_SSE_SUBSCRIBE_DIRECT, + }; + return r; + case FIO___HTTP_PROTOCOL_NONE: /* fall through*/ + r = (fio_io_protocol_s){.on_close = fio___http_on_close}; + return r; + default: + FIO_LOG_ERROR("internal function `fio___http_protocol_get` called with " + "illegal arguments!"); + return r; + } +} + +/** Returns an http controller object with the proper protocol callbacks. */ +FIO_IFUNC fio_http_controller_s +fio___http_controller_get(fio___http_protocol_selector_e s, int is_client) { + fio_http_controller_s r = {0}; + (void)is_client, (void)s; + switch (s) { + case FIO___HTTP_PROTOCOL_ACCEPT: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .send_headers = fio___http_controller_http1_send_headers, + .write_body = fio___http_controller_http1_write_body, + .on_finish = fio___http_controller_http1_on_finish, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_HTTP1: + if (is_client) { + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed_client, + .on_finish = fio___http_controller_http1_on_finish_client, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + } else { + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .send_headers = fio___http_controller_http1_send_headers, + .write_body = fio___http_controller_http1_write_body, + .on_finish = fio___http_controller_http1_on_finish, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + } + return r; + case FIO___HTTP_PROTOCOL_HTTP2: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_WS: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .write_body = fio___http_controller_ws_write_body, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_SSE: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .write_body = fio___http_controller_sse_write_body, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_NONE: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + default: + FIO_LOG_ERROR("internal function `fio___http_controller_get` called with " + "illegal arguments!"); + return r; + } +} + +FIO_IFUNC fio___http_protocol_s *fio___http_protocol_init( + fio___http_protocol_s *p, + const char *url, + fio_http_settings_s s, + bool is_client) { + int should_free_tls = !s.tls; + FIO_ASSERT_ALLOC(p); + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) { + p->state[i].protocol = + fio___http_protocol_get((fio___http_protocol_selector_e)i, is_client); + // p->state[i].protocol.iomem_size = + // sizeof(fio___http_connection_s) + s.max_line_len; + p->state[i].controller = + fio___http_controller_get((fio___http_protocol_selector_e)i, is_client); + } + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE; ++i) + p->state[i].protocol.timeout = (unsigned)s.ws_timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_SSE].protocol.timeout = + (unsigned)s.sse_timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol.timeout = + (unsigned)s.timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol.timeout = + (unsigned)s.timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_NONE].protocol.timeout = + (unsigned)s.timeout * 1000U; + if (url) { + fio_url_s u = fio_url_parse(url, strlen(url)); + s.tls = fio_io_tls_from_url(s.tls, u); + if (s.tls) { + s.tls = fio_io_tls_dup(s.tls); + /* fio_io_tls_alpn_add(s.tls, "h2", fio___http_on_select_h2); // not yet + */ + // fio_io_tls_alpn_add(s.tls, "http/1.1", fio___http_on_select_h1); + fio_io_functions_s tmp_fn = fio_io_tls_default_functions(NULL); + if (!s.tls_io_func) + s.tls_io_func = &tmp_fn; + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) + p->state[i].protocol.io_functions = *s.tls_io_func; + if (should_free_tls) + fio_io_tls_free(s.tls); + } + } + p->settings = s; + p->on_http_callback = is_client ? fio___http_on_http_client + : (p->settings.public_folder.len) + ? fio___http_on_http_with_public_folder + : fio___http_on_http_direct; + p->settings.public_folder.buf = p->public_folder_buf; + p->queue = fio_io_queue(); + + if (s.public_folder.len) + FIO_MEMCPY(p->public_folder_buf, s.public_folder.buf, s.public_folder.len); + return p; +} +/* ***************************************************************************** +HTTP Helpers +***************************************************************************** */ + +/** Returns the IO object associated with the HTTP object (request only). */ +SFUNC fio_io_s *fio_http_io(fio_http_s *h) { + if (!h) + return NULL; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + return c->io; +} + +/** Returns the HTTP settings associated with the HTTP object, if any. */ +SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *h) { + if (!h) + return NULL; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + return c->settings; +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_HTTP +#endif /* FIO_HTTP */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MODULE_NAME module /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + A Template for New Types / Modules + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_MODULE_NAME) /* && !defined(FIO___RECURSIVE_INCLUDE) */ + +/* ***************************************************************************** +Module Settings + +At this point, define any MACROs and customizable settings available to the +developer. +***************************************************************************** */ + +/* ***************************************************************************** +Pointer Tagging Support: !!! valid only for dynamic types, filename 2xx XXX.h +***************************************************************************** */ + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_MODULE_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_MODULE_PTR FIO_NAME(FIO_MODULE_NAME, s) * +#endif + +#define FIO___UNTAG_T FIO_NAME(FIO_MODULE_NAME, s) + +/* ***************************************************************************** +Module API +***************************************************************************** */ + +typedef struct { + /* module's type(s) if any */ + void *data; +} FIO_NAME(FIO_MODULE_NAME, s); + +/* at this point publish (declare only) the public API */ + +#ifndef FIO_MODULE_INIT +/* Initialization macro. */ +#define FIO_MODULE_INIT \ + { 0 } +#endif + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY + +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC FIO_MODULE_PTR FIO_NAME(FIO_MODULE_NAME, new)(void); + +/* Frees any internal data AND the object's container! */ +SFUNC int FIO_NAME(FIO_MODULE_NAME, free)(FIO_MODULE_PTR obj); + +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/** Destroys the object, reinitializing its container. */ +SFUNC void FIO_NAME(FIO_MODULE_NAME, destroy)(FIO_MODULE_PTR obj); + +/* ***************************************************************************** +Module Implementation - inlined static functions +***************************************************************************** */ +/* +REMEMBER: +======== + +All short term / type memory allocations should use: +* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE_(ptr, size) + +All long-term / system memory allocations should use: +* FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE(ptr, size) + +Module and File Names: +====================== + +00# XXX.h - the module is a core module, independent or doesn't define a type +1## XXX.h - the module doesn't define a type, but requires memory allocations +2## XXX.h - the module defines a type +3## XXX.h - hashes / crypto. +4## XXX.h - server related modules +5## XXX.h - FIOBJ related modules +9## XXX.h - testing (usually use 902 XXX.h unless tests depend on other tests) + +When +*/ + +/* ***************************************************************************** +Module Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +FIO_LEAK_COUNTER_DEF(FIO_MODULE_NAME) + +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC FIO_MODULE_PTR FIO_NAME(FIO_MODULE_NAME, new)(void) { + FIO_NAME(FIO_MODULE_NAME, s) *o = + (FIO_NAME(FIO_MODULE_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); + if (!o) + return (FIO_MODULE_PTR)NULL; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_MODULE_NAME); + *o = (FIO_NAME(FIO_MODULE_NAME, s))FIO_MODULE_INIT; + return (FIO_MODULE_PTR)FIO_PTR_TAG(o); +} +/* Frees any internal data AND the object's container! */ +SFUNC int FIO_NAME(FIO_MODULE_NAME, free)(FIO_MODULE_PTR obj) { + FIO_PTR_TAG_VALID_OR_RETURN(obj, 0); + FIO_NAME(FIO_MODULE_NAME, destroy)(obj); + FIO_NAME(FIO_MODULE_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO___UNTAG_T, obj); + FIO_LEAK_COUNTER_ON_FREE(FIO_MODULE_NAME); + FIO_MEM_FREE_(o, sizeof(*o)); + return 0; +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MODULE_NAME, destroy)(FIO_MODULE_PTR obj) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(obj); + FIO_NAME(FIO_MODULE_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO___UNTAG_T, obj); + /* TODO: add destruction logic */ + + *o = (FIO_NAME(FIO_MODULE_NAME, s))FIO_MODULE_INIT; + return; +} + +/* ***************************************************************************** +Module Testing - Please place testing in a dedicated testing file if possible. +***************************************************************************** */ +#if 0 +#ifdef FIO_TEST_ALL + +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MODULE_NAME)(void) { + /* + * TODO: test module here + */ +} + +#endif /* FIO_TEST_ALL */ +#endif /* 0 */ +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_MODULE_PTR +#undef FIO_MODULE_NAME +#undef FIO___UNTAG_T +#endif /* FIO_MODULE_NAME */ +/* ***************************************************************************** + + + + + Common Cleanup + + + + +***************************************************************************** */ + +/* ***************************************************************************** +Common cleanup +***************************************************************************** */ +#ifndef FIO___RECURSIVE_INCLUDE + +/* undefine FIO_EXTERN only if its value indicates it is temporary. */ +#if (FIO_EXTERN + 1) < 3 +#undef FIO_EXTERN +#endif +#if (FIO_EXTERN_COMPLETE + 1) < 3 +#undef FIO_EXTERN_COMPLETE +#endif + +#undef SFUNC +#undef IFUNC +#undef SFUNC_ +#undef IFUNC_ + +#undef FIO_MALLOC_TMP_USE_SYSTEM +#undef FIO_MEM_REALLOC_ +#undef FIO_MEM_FREE_ +#undef FIO_MEM_REALLOC_IS_SAFE_ +#undef FIO_MEMORY_NAME /* postponed due to possible use in macros */ + +#undef FIO___LOCK_TYPE +#undef FIO___LOCK_INIT +#undef FIO___LOCK_LOCK +#undef FIO___LOCK_LOCK_TRY +#undef FIO___LOCK_UNLOCK +#undef FIO_USE_THREAD_MUTEX_TMP + +#else + +#undef SFUNC +#undef IFUNC +#define SFUNC SFUNC_ +#define IFUNC IFUNC_ + +#endif /* !FIO___RECURSIVE_INCLUDE */ + +/* ***************************************************************************** +C++ extern end +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +} +#endif + +/* ***************************************************************************** +Recursive inclusion / cleanup +***************************************************************************** */ +#if !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO___INCLUDE_AGAIN) +/* recursive include statement */ +#undef FIO___INCLUDE_AGAIN +#include FIO_INCLUDE_FILE +#else +#ifdef FIO_EVERYTHING___REMOVE_EXTERN +#undef FIO_EXTERN +#undef FIO_EVERYTHING___REMOVE_EXTERN +#endif +#ifdef FIO_EVERYTHING___REMOVE_EXTERN_COMPLETE +#undef FIO_EXTERN_COMPLETE +#undef FIO_EVERYTHING___REMOVE_EXTERN_COMPLETE +#endif + +#endif /* !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO___INCLUDE_AGAIN) */ +/* ***************************************************************************** + + + + + + + Start Test Code + + + + + + +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_TESTS_START___H) +#define H___FIO_TESTS_START___H +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** +C++ extern start +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +FIO_SFUNC void fio_test_dynamic_types(void); + +FIO_SFUNC uintptr_t fio___dynamic_types_test_tag(uintptr_t i) { return i | 1; } +FIO_SFUNC uintptr_t fio___dynamic_types_test_untag(uintptr_t i) { + return i & (~((uintptr_t)1UL)); +} + +#define FIO_TEST_REPEAT (1ULL << 12U) + +/* ***************************************************************************** +Memory Allocator Tests +***************************************************************************** */ +#define FIO___TEST_REINCLUDE + +#define FIO_MEMORY_NAME fio_mem_test_safe +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 1 +#undef FIO_MEMORY_USE_THREAD_MUTEX +#define FIO_MEMORY_USE_THREAD_MUTEX 0 +#define FIO_MEMORY_ARENA_COUNT 4 +#include FIO_INCLUDE_FILE + +#define FIO_MEMORY_NAME fio_mem_test_unsafe +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 0 +#undef FIO_MEMORY_USE_THREAD_MUTEX +#define FIO_MEMORY_USE_THREAD_MUTEX 0 +#define FIO_MEMORY_ARENA_COUNT 4 +#include FIO_INCLUDE_FILE + +#undef FIO___TEST_REINCLUDE +/* ***************************************************************************** +Dynamically Produced Test Types +***************************************************************************** */ +#define FIO___TEST_REINCLUDE + +static int ary____test_was_destroyed = 0; +#define FIO_ARRAY_NAME ary____test +#define FIO_ARRAY_TYPE int +#define FIO_REF_NAME ary____test +#define FIO_REF_INIT(obj) obj = (ary____test_s)FIO_ARRAY_INIT +#define FIO_REF_DESTROY(obj) \ + do { \ + ary____test_destroy(&obj); \ + ary____test_was_destroyed = 1; \ + } while (0) +#define FIO_PTR_TAG(p) fio___dynamic_types_test_tag(((uintptr_t)p)) +#define FIO_PTR_UNTAG(p) fio___dynamic_types_test_untag(((uintptr_t)p)) +#include FIO_INCLUDE_FILE + +#define FIO_ARRAY_NAME ary2____test +#define FIO_ARRAY_TYPE uint8_t +#define FIO_ARRAY_TYPE_INVALID 0xFF +#define FIO_ARRAY_TYPE_COPY(dest, src) (dest) = (src) +#define FIO_ARRAY_TYPE_DESTROY(obj) (obj = FIO_ARRAY_TYPE_INVALID) +#define FIO_ARRAY_TYPE_CMP(a, b) (a) == (b) +#define FIO_PTR_TAG(p) fio___dynamic_types_test_tag(((uintptr_t)p)) +#define FIO_PTR_UNTAG(p) fio___dynamic_types_test_untag(((uintptr_t)p)) +#include FIO_INCLUDE_FILE + +/* test all defaults */ +#define FIO_ARRAY_NAME ary3____test +#include FIO_INCLUDE_FILE + +#define FIO_UMAP_NAME uset___test_size_t +#define FIO_MEMORY_NAME uset___test_size_t_mem +#define FIO_MAP_KEY size_t +#define FIO_MAP_TEST +#include FIO_INCLUDE_FILE +#define FIO_UMAP_NAME umap___test_size +#define FIO_MEMORY_NAME umap___test_size_mem +#define FIO_MAP_KEY size_t +#define FIO_MAP_VALUE size_t +#define FIO_MAP_TEST +#include FIO_INCLUDE_FILE +#define FIO_OMAP_NAME omap___test_size_t +#define FIO_MEMORY_NAME omap___test_size_t_mem +#define FIO_MAP_KEY size_t +#define FIO_MAP_ORDERED 1 +#define FIO_MAP_TEST +#include FIO_INCLUDE_FILE +#define FIO_OMAP_NAME omap___test_size_lru +#define FIO_MEMORY_NAME omap___test_size_lru_mem +#define FIO_MAP_KEY size_t +#define FIO_MAP_VALUE size_t +#define FIO_MAP_LRU (1UL << 24) +#define FIO_MAP_TEST +#include FIO_INCLUDE_FILE + +#define FIO_STR_NAME fio_big_str +#define FIO_STR_WRITE_TEST_FUNC +#include FIO_INCLUDE_FILE + +#define FIO_STR_SMALL fio_small_str +#define FIO_STR_WRITE_TEST_FUNC +#include FIO_INCLUDE_FILE + +#undef FIO___TEST_REINCLUDE +/* ***************************************************************************** +Environment printout +***************************************************************************** */ +#ifndef FIO_PRINT_SIZE_OF +#define FIO_PRINT_SIZE_OF(T) \ + fprintf(stderr, "\t%-19s%zu Bytes\n", #T, sizeof(T)) +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, type_sizes)(void) { + switch (sizeof(void *)) { + case 2: + fprintf(stderr, "* 16bit words size (unexpected, unknown effects).\n"); + break; + case 4: + fprintf(stderr, "* 32bit words size (some features might be slower).\n"); + break; + case 8: fprintf(stderr, "* 64bit words size okay.\n"); break; + case 16: fprintf(stderr, "* 128bit words size... wow!\n"); break; + default: + fprintf(stderr, "* Unknown words size %zubit!\n", sizeof(void *) << 3); + break; + } + fprintf(stderr, "* Using the following type sizes:\n"); + FIO_PRINT_SIZE_OF(char); + FIO_PRINT_SIZE_OF(short); + FIO_PRINT_SIZE_OF(int); + FIO_PRINT_SIZE_OF(float); + FIO_PRINT_SIZE_OF(long); + FIO_PRINT_SIZE_OF(double); + FIO_PRINT_SIZE_OF(size_t); + FIO_PRINT_SIZE_OF(void *); + FIO_PRINT_SIZE_OF(uintmax_t); + FIO_PRINT_SIZE_OF(long double); +#ifdef __SIZEOF_INT128__ + FIO_PRINT_SIZE_OF(__uint128_t); +#endif + FIO_PRINT_SIZE_OF(fio_thread_t); + FIO_PRINT_SIZE_OF(fio_thread_mutex_t); +#if FIO_OS_POSIX || defined(_SC_PAGESIZE) + long page = sysconf(_SC_PAGESIZE); + if (page > 0) { + fprintf(stderr, "\t%-17s%ld bytes.\n", "Page", page); + if (page != (1UL << FIO_MEM_PAGE_SIZE_LOG)) + FIO_LOG_INFO("unexpected page size != 4096\n " + "facil.io could be recompiled with:\n " + "`CFLAGS=\"-DFIO_MEM_PAGE_SIZE_LOG=%.0lf\"`", + log2(page)); + } +#endif /* FIO_OS_POSIX */ +} +/* ***************************************************************************** + +***************************************************************************** */ +#endif /* H___FIO_TESTS_START___H */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_ATOL Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_ATOL_TEST___H) +#define H___FIO_ATOL_TEST___H +#ifndef H___FIO_ATOL___H +#define FIO_ATOL +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#define FIO_ATOL_TEST_MAX 1048576 + +FIO_IFUNC int64_t FIO_NAME_TEST(stl, atol_time)(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return ((int64_t)t.tv_sec * 1000000) + (int64_t)t.tv_nsec / 1000; +} + +FIO_SFUNC double fio___aton_float_wrapper(char **pstr) { + fio_aton_s r = fio_aton(pstr); + if (r.is_float) + return r.f; + return (double)r.i; +} + +FIO_SFUNC double fio___strtod_wrapper(char **pstr) { + return strtod(*pstr, pstr); +} + +FIO_SFUNC void FIO_NAME_TEST(stl, aton_speed)(void) { + struct { + const char *n; + double (*fn)(char **); + } to_test[] = { + {.n = "fio_aton", .fn = fio___aton_float_wrapper}, + {.n = "strtod ", .fn = fio___strtod_wrapper}, + }; + const char *floats[] = { + "inf", + "nan", + "-inf", + "-nan", + "infinity", + "1E+1000", + "1E-1000", + "1E+10", + "1E-10", + "-1E10", + "-1e10", + "-1E+10", + "-1E-10", + "1.234E+10", + "1.234E-10", + "1.79769e+308", + "2.22507e-308", + "1.79769e+308", + "2.22507e-308", + "4.9406564584124654e-324", + "2.2250738585072009e-308", + "2.2250738585072014e-308", + "1.7976931348623157e+308", + "2.171e-308", + "2.2250738585072012e-308", /* possible infinit loop bug for strtod */ + "1.0020284025808569e-134", + "1.00000000000000011102230246251565404236316680908203124", + "72057594037927928.0", + "7205759403792793200001e-5", + "5708990770823839207320493820740630171355185152001e-3", + "0x10.1p0", + "0x1.8p1", + "0x1.8p5", + "0x4.0p5", + "0x1.0p50a", + "0x1.0p500", + "0x1.0P-1074", + "0x3a.0P-1074", + "0x0.f9c7573d7fe52p-1022", + }; + printf("* Testing fio_aton/strtod performance:\n"); + /* Sanity Test */ + bool rounding_errors_detected = 0; + for (size_t n_i = 0; n_i < sizeof(floats) / sizeof(floats[0]); ++n_i) { + union { + double f; + uint64_t u64; + } u1, u2; + char *tmp = (char *)floats[n_i]; + u1.f = to_test[0].fn(&tmp); + for (size_t fn_i = 1; fn_i < sizeof(to_test) / sizeof(to_test[0]); ++fn_i) { + char *tmp2 = (char *)floats[n_i]; + u2.f = to_test[fn_i].fn(&tmp2); + if (tmp2 == tmp) { + if ((isnan(u1.f) && isnan(u2.f)) || u1.u64 == u2.u64) + continue; + rounding_errors_detected = 1; +#ifdef DEBUG + FIO_LOG_WARNING("Rounding error for %s:\n\t%.17g ?= %.17g", + floats[n_i], + u1.f, + u2.f); +#endif + if (u1.u64 + 1 == u2.u64) + continue; + if (u2.u64 + 1 == u1.u64) + continue; + } + FIO_ASSERT(tmp2 == tmp && u1.u64 == u2.u64, + "Sanity test failed for %s\n\t %.17g ?!= %.17g\n\t %s ?!= %s", + (char *)floats[n_i], + u1.f, + u2.f, + tmp, + tmp2); + } + } + /* Speed Test */ + for (size_t fn_i = 0; fn_i < sizeof(to_test) / sizeof(to_test[0]); ++fn_i) { + double unused; + printf("\t%s\t", to_test[fn_i].n); + int64_t start = FIO_NAME_TEST(stl, atol_time)(); + for (size_t i = 0; i < (FIO_ATOL_TEST_MAX / 10); ++i) { + for (size_t n_i = 0; n_i < sizeof(floats) / sizeof(floats[0]); ++n_i) { + char *tmp = (char *)floats[n_i]; + unused = to_test[fn_i].fn(&tmp); + FIO_COMPILER_GUARD; + } + } + (void)unused; + int64_t end = FIO_NAME_TEST(stl, atol_time)(); + printf("%lld us\n", (long long int)(end - start)); + } + if (rounding_errors_detected) + FIO_LOG_WARNING("Single bit rounding errors detected when comparing " + "`fio_aton` to `strtod`.\n"); +} + +FIO_SFUNC size_t sprintf_wrapper(char *dest, int64_t num, uint8_t base) { + switch (base) { + case 2: /* overflow - unsupported */ + case 8: /* overflow - unsupported */ + case 10: return snprintf(dest, 256, "%" PRId64, num); + case 16: + if (num >= 0) + return snprintf(dest, 256, "0x%.16" PRIx64, num); + return snprintf(dest, 256, "-0x%.8" PRIx64, (0 - num)); + } + return snprintf(dest, 256, "%" PRId64, num); +} + +FIO_SFUNC int64_t strtoll_wrapper(char **pstr) { + return strtoll(*pstr, pstr, 0); +} +FIO_SFUNC int64_t fio_aton_wrapper(char **pstr) { + fio_aton_s r = fio_aton(pstr); + if (r.is_float) + return (int64_t)r.f; + return r.i; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, atol_speed)(const char *name, + int64_t (*a2l)(char **), + size_t (*l2a)(char *, + int64_t, + uint8_t)) { + int64_t start; + int64_t tw = 0; + int64_t trt = 0; + char buf[1024]; + struct { + const char *str; + const char *prefix; + uint8_t prefix_len; + uint8_t base; + } * pb, b[] = { + {.str = "Base 10", .base = 10}, + {.str = "Hex ", .prefix = "0x", .prefix_len = 2, .base = 16}, + {.str = "Binary ", .prefix = "0b", .prefix_len = 2, .base = 2}, + // {.str = "Oct ", .prefix = "0", .prefix_len = 1, .base = 8}, + /* end marker */ + {.str = NULL}, + }; + fprintf(stderr, " * %s test performance:\n", name); + if (l2a == sprintf_wrapper) + b[2].str = NULL; + for (pb = b; pb->str; ++pb) { + start = FIO_NAME_TEST(stl, atol_time)(); + for (int64_t i = -FIO_ATOL_TEST_MAX; i < FIO_ATOL_TEST_MAX; ++i) { + char *bf = buf + pb->prefix_len; + size_t len = l2a(bf, i, pb->base); + bf[len] = 0; + if (bf[0] == '-') { + for (int pre_test = 0; pre_test < pb->prefix_len; ++pre_test) { + if (bf[pre_test + 1] == pb->prefix[pre_test]) + continue; + FIO_MEMCPY(buf, pb->prefix, pb->prefix_len); + bf = buf; + break; + } + } else { + for (int pre_test = 0; pre_test < pb->prefix_len; ++pre_test) { + if (bf[pre_test] == pb->prefix[pre_test]) + continue; + FIO_MEMCPY(buf, pb->prefix, pb->prefix_len); + bf = buf; + break; + } + } + FIO_COMPILER_GUARD; /* don't optimize this loop */ + int64_t n = a2l(&bf); + bf = buf; + FIO_ASSERT(n == i, + "roundtrip error for %s: %s != %lld (got %lld stopped: %s)", + name, + buf, + i, + a2l(&bf), + bf); + } + trt = FIO_NAME_TEST(stl, atol_time)() - start; + start = FIO_NAME_TEST(stl, atol_time)(); + for (int64_t i = -FIO_ATOL_TEST_MAX; i < FIO_ATOL_TEST_MAX; ++i) { + char *bf = buf + pb->prefix_len; + size_t len = l2a(bf, i, pb->base); + bf[len] = 0; + if (bf[0] == '-') { + for (int pre_test = 0; pre_test < pb->prefix_len; ++pre_test) { + if (bf[pre_test + 1] == pb->prefix[pre_test]) + continue; + FIO_MEMCPY(buf, pb->prefix, pb->prefix_len); + bf = buf; + break; + } + } else { + for (int pre_test = 0; pre_test < pb->prefix_len; ++pre_test) { + if (bf[pre_test] == pb->prefix[pre_test]) + continue; + FIO_MEMCPY(buf, pb->prefix, pb->prefix_len); + bf = buf; + break; + } + } + FIO_COMPILER_GUARD; /* don't optimize this loop */ + } + tw = FIO_NAME_TEST(stl, atol_time)() - start; + // clang-format off + fprintf(stderr, " - %s roundtrip %zd us\n", pb->str, (size_t)trt); + fprintf(stderr, " - %s write %zd us\n", pb->str, (size_t)tw); + fprintf(stderr, " - %s read (calc) %zd us\n", pb->str, (size_t)(trt - tw)); + // clang-format on + } +} + +FIO_SFUNC void FIO_NAME_TEST(stl, atol)(void) { + fprintf(stderr, "* Testing fio_atol and fio_ltoa.\n"); + char buffer[1024]; + for (int i = 0 - FIO_ATOL_TEST_MAX; i < FIO_ATOL_TEST_MAX; ++i) { + size_t tmp = fio_ltoa(buffer, i, 0); + FIO_ASSERT(tmp > 0, "fio_ltoa returned length error"); + char *tmp2 = buffer; + int i2 = (int)fio_atol(&tmp2); + FIO_ASSERT(tmp2 > buffer, "fio_atol pointer motion error (1:%i)", i); + FIO_ASSERT(i == i2, + "fio_ltoa-fio_atol roundtrip error %lld != %lld", + i, + i2); + } + for (size_t bit = 0; bit < sizeof(int64_t) * 8; ++bit) { + uint64_t i = (uint64_t)1 << bit; + size_t tmp = fio_ltoa(buffer, (int64_t)i, 0); + FIO_ASSERT(tmp > 0, "fio_ltoa return length error"); + buffer[tmp] = 0; + char *tmp2 = buffer; + int64_t i2 = fio_atol(&tmp2); + FIO_ASSERT(tmp2 > buffer, "fio_atol pointer motion error (2:%zu)", bit); + FIO_ASSERT((int64_t)i == i2, + "fio_ltoa-fio_atol roundtrip error %lld != %lld", + i, + i2); + } + for (unsigned char i = 0; i < 36; ++i) { + FIO_ASSERT(i == fio_c2i(fio_i2c(i)), "fio_c2i / fio_i2c roundtrip error."); + } + for (size_t i = 1; i < (1ULL << 10); ++i) { + union { + double d; + void *p; + } e[2], r[2]; + e[0].d = (1.0 + i); + e[1].d = (1.0 - i); + r[0].d = fio_i2d(1LL + i, 0); + r[1].d = fio_i2d(1LL - i, 0); + FIO_ASSERT(e[0].d == r[0].d, + "fio_i2d failed at (1+%zu) %g != %g\n\t%p != %p", + i, + e[0].d, + r[0].d, + e[0].p, + r[0].p); + FIO_ASSERT(e[1].d == r[1].d, + "fio_i2d failed at (1-%zu) %g != %g\n\t%p != %p", + i, + e[1].d, + r[1].d, + e[1].p, + r[1].p); + } + for (size_t i = 1; i < (~0ULL); i = ((i << 1U) | 1U)) { + union { + double d; + void *p; + } tst[2]; + tst[0].d = fio_u2d(i, 0); + tst[1].d = (double)i; + char buf[128]; + buf[0] = 'x'; + fio_ltoa16u(buf + 1, i, 16); + buf[17] = 0; + FIO_ASSERT(tst[0].d == tst[0].d, + "fio_u2d failed (%s) %g != %g\n\t%p != %p", + buf, + tst[0].d, + tst[1].d, + tst[0].p, + tst[1].p); + } +#if 1 || !(DEBUG - 1 + 1) + { + uint64_t start, end, rep = (1ULL << 22); + int64_t u64[128] = {0}; + double dbl[128] = {0.0}; + double rtest; + fprintf(stderr, "* Testing fio_i2d conversion overhead.\n"); + start = fio_time_micro(); + for (size_t i = 0; i < rep; ++i) { + u64[i & 127] -= i; + FIO_COMPILER_GUARD; + dbl[i & 127] += 2.0 * u64[i & 127]; + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, "\t- C cast: %zuus\n", (size_t)(end - start)); + rtest = dbl[127]; + FIO_MEMSET(u64, 0, sizeof(u64)); + FIO_MEMSET(dbl, 0, sizeof(dbl)); + start = fio_time_micro(); + for (size_t i = 0; i < rep; ++i) { + u64[i & 127] -= i; + FIO_COMPILER_GUARD; + dbl[i & 127] += fio_i2d((int64_t)u64[i & 127], 1); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, "\t- fio_i2d: %zuus\n", (size_t)(end - start)); + FIO_ASSERT(rtest == dbl[127], "fio_i2d results not the same as C cast?"); + start = fio_time_micro(); + for (size_t i = 0; i < rep; ++i) { + u64[i & 127] -= i; + FIO_COMPILER_GUARD; + dbl[i & 127] += fio_u2d((int64_t)u64[i & 127], 1); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, "\t- fio_u2d: %zuus\n", (size_t)(end - start)); + } +#endif + fprintf(stderr, "* Testing fio_atol samples.\n"); +#define TEST_ATOL(s_, n) \ + do { \ + char *s = (char *)s_; \ + char *p = (char *)(s); \ + int64_t r = fio_atol(&p); \ + FIO_ASSERT(r == (n), \ + "fio_atol test error! %s => %zd (not %zd)", \ + ((char *)(s)), \ + (size_t)r, \ + (size_t)n); \ + FIO_ASSERT((s) + FIO_STRLEN((s)) == p, \ + "fio_atol test error! %s reading position not at end " \ + "(!%zu == %zu)\n\t0x%p - 0x%p", \ + (s), \ + (size_t)FIO_STRLEN((s)), \ + (size_t)(p - (s)), \ + (void *)p, \ + (void *)s); \ + char buf[96]; \ + buf[0] = '0'; \ + buf[1] = 'b'; \ + buf[fio_ltoa(buf + 2, n, 2) + 2] = 0; \ + p = buf; \ + FIO_ASSERT(fio_atol(&p) == (n), \ + "fio_ltoa base 2 test error! " \ + "%s != %s (%zd)", \ + buf, \ + ((char *)(s)), \ + (size_t)((p = buf), fio_atol(&p))); \ + fio_ltoa(buf, n, 8); \ + p = buf; \ + p += buf[0] == '-'; \ + FIO_ASSERT((r = (int64_t)fio_atol8u(&p)) == \ + ((buf[0] == '-') ? (0 - (n)) : (n)), \ + "fio_ltoa base 8 test error! " \ + "%s != %s (%zd)", \ + buf, \ + ((char *)(s)), \ + (size_t)r); \ + buf[fio_ltoa(buf, n, 10)] = 0; \ + p = buf; \ + FIO_ASSERT(fio_atol(&p) == (n), \ + "fio_ltoa base 10 test error! " \ + "%s != %s (%zd)", \ + buf, \ + ((char *)(s)), \ + (size_t)((p = buf), fio_atol(&p))); \ + buf[0] = '0'; \ + buf[1] = 'x'; \ + buf[fio_ltoa(buf + 2, n, 16) + 2] = 0; \ + p = buf; \ + FIO_ASSERT(fio_atol(&p) == (n), \ + "fio_ltoa base 16 test error! " \ + "%s != %s (%zd)", \ + buf, \ + ((char *)(s)), \ + (size_t)((p = buf), fio_atol(&p))); \ + } while (0) + + TEST_ATOL("0x1", 1); + TEST_ATOL("-0x1", -1); + TEST_ATOL("-0xa", -10); /* sign before hex */ + TEST_ATOL("0xe5d4c3b2a1908770", -1885667171979196560LL); /* sign within hex */ + TEST_ATOL("0b00000000000011", 3); + TEST_ATOL("-0b00000000000011", -3); + TEST_ATOL("0b0000000000000000000000000000000000000000000000000", 0); + TEST_ATOL("0", 0); + TEST_ATOL("1", 1); + TEST_ATOL("2", 2); + TEST_ATOL("-2", -2); + TEST_ATOL("0000000000000000000000000000000000000000000000042", 34); /* oct */ + TEST_ATOL("9223372036854775807", 9223372036854775807LL); /* INT64_MAX */ + TEST_ATOL("9223372036854775808", + 9223372036854775807LL); /* INT64_MAX overflow protection */ + TEST_ATOL("9223372036854775999", + 9223372036854775807LL); /* INT64_MAX overflow protection */ + TEST_ATOL("9223372036854775806", + 9223372036854775806LL); /* almost INT64_MAX */ +#undef TEST_ATOL + +#define TEST_LTOA_DIGITS10(num, digits) \ + FIO_ASSERT(fio_digits10(num) == digits, \ + "fio_digits10 failed for " #num " != (%zu)", \ + (size_t)fio_digits10(num)); \ + { \ + char *number_str__ = (char *)#num; \ + char *pstr__ = number_str__; \ + FIO_ASSERT(fio_atol10(&pstr__) == num, "fio_atol10 failed for " #num); \ + } + TEST_LTOA_DIGITS10(1LL, 1); + TEST_LTOA_DIGITS10(22LL, 2); + TEST_LTOA_DIGITS10(333LL, 3); + TEST_LTOA_DIGITS10(4444LL, 4); + TEST_LTOA_DIGITS10(55555LL, 5); + TEST_LTOA_DIGITS10(666666LL, 6); + TEST_LTOA_DIGITS10(7777777LL, 7); + TEST_LTOA_DIGITS10(88888888LL, 8); + TEST_LTOA_DIGITS10(999999999LL, 9); + TEST_LTOA_DIGITS10(-1LL, (1 + 1)); + TEST_LTOA_DIGITS10(-22LL, (2 + 1)); + TEST_LTOA_DIGITS10(-333LL, (3 + 1)); + TEST_LTOA_DIGITS10(-4444LL, (4 + 1)); + TEST_LTOA_DIGITS10(-55555LL, (5 + 1)); + TEST_LTOA_DIGITS10(-666666LL, (6 + 1)); + TEST_LTOA_DIGITS10(-7777777LL, (7 + 1)); + TEST_LTOA_DIGITS10(-88888888LL, (8 + 1)); + TEST_LTOA_DIGITS10(-999999999LL, (9 + 1)); + TEST_LTOA_DIGITS10(-9223372036854775807LL, (19 + 1)); +#undef TEST_LTOA_DIGITS10 + +#define TEST_LTOA_DIGITS16(num, digits) \ + FIO_ASSERT(fio_digits16u(num) == digits, \ + "fio_digits16u failed for " #num " != (%zu)", \ + (size_t)fio_digits16u(num)); \ + { \ + char *number_str__ = (char *)#num; \ + char *pstr__ = number_str__; \ + FIO_ASSERT(fio_atol16u(&pstr__) == (uint64_t)(num), \ + "fio_atol16u failed for " #num " != %zu", \ + ((pstr__ = number_str__), (size_t)fio_atol16u(&pstr__))); \ + } + TEST_LTOA_DIGITS16(0x00ULL, 2); + TEST_LTOA_DIGITS16(0x10ULL, 2); + TEST_LTOA_DIGITS16(0x100ULL, 4); + TEST_LTOA_DIGITS16(0x10000ULL, 6); + TEST_LTOA_DIGITS16(0xFFFFFFULL, 6); + TEST_LTOA_DIGITS16(0x1000000ULL, 8); + TEST_LTOA_DIGITS16(0x10000000ULL, 8); + TEST_LTOA_DIGITS16(0x100000000ULL, 10); + TEST_LTOA_DIGITS16(0x10000000000ULL, 12); + TEST_LTOA_DIGITS16(0x1000000000000ULL, 14); + TEST_LTOA_DIGITS16(0x100000000000000ULL, 16); + TEST_LTOA_DIGITS16(0xFF00000000000000ULL, 16); +#undef TEST_LTOA_DIGITS16 + +#define TEST_LTOA_DIGITS_BIN(num, digits) \ + FIO_ASSERT(fio_digits_bin(num) == digits, \ + "fio_digits_bin failed for " #num " != (%zu)", \ + (size_t)fio_digits_bin(num)); + + TEST_LTOA_DIGITS_BIN(0x00ULL, 1); + TEST_LTOA_DIGITS_BIN(-0x01ULL, 64); + TEST_LTOA_DIGITS_BIN(0x10ULL, 6); + TEST_LTOA_DIGITS_BIN(0x100ULL, 10); + TEST_LTOA_DIGITS_BIN(0x10000ULL, 18); + TEST_LTOA_DIGITS_BIN(0x20000ULL, 18); + TEST_LTOA_DIGITS_BIN(0xFFFFFFULL, 24); + TEST_LTOA_DIGITS_BIN(0x1000000ULL, 26); + TEST_LTOA_DIGITS_BIN(0x10000000ULL, 30); + TEST_LTOA_DIGITS_BIN(0x100000000ULL, 34); + TEST_LTOA_DIGITS_BIN(0x10000000000ULL, 42); + TEST_LTOA_DIGITS_BIN(0x1000000000000ULL, 50); + TEST_LTOA_DIGITS_BIN(0x100000000000000ULL, 58); + TEST_LTOA_DIGITS_BIN(0xFF00000000000000ULL, 64); +#undef TEST_LTOA_DIGITS_BIN + + FIO_NAME_TEST(stl, atol_speed)("fio_atol/fio_ltoa", fio_atol, fio_ltoa); + FIO_NAME_TEST(stl, atol_speed) + ("fio_aton/fio_ltoa", fio_aton_wrapper, fio_ltoa); + + FIO_NAME_TEST(stl, atol_speed) + ("system strtoll/sprintf", strtoll_wrapper, sprintf_wrapper); + FIO_NAME_TEST(stl, aton_speed)(); + +#define TEST_DOUBLE(s, d, stop) \ + do { \ + union { \ + double d_; \ + uint64_t as_i; \ + } pn, pn1, pn2; \ + pn2.d_ = (double)d; \ + char *p = (char *)(s); \ + char *p1 = (char *)(s); \ + char *p2 = (char *)(s); \ + double r = fio_atof(&p); \ + fio_aton_s num_result = fio_aton(&p1); \ + double r2 = num_result.is_float ? num_result.f : (double)num_result.i; \ + double std = strtod(p2, &p2); \ + (void)std; \ + pn.d_ = r; \ + pn1.d_ = r2; \ + FIO_ASSERT( \ + *p == stop || p == p2, \ + "atof float parsing didn't stop at correct position! %x != %x\n%s", \ + *p, \ + stop, \ + (s)); \ + FIO_ASSERT(*p1 == stop || p1 == p2, \ + "aton float parsing didn't stop at correct position!\n\t%s" \ + "\n\t%x != %x", \ + s, \ + *p1, \ + stop); \ + if (((double)d == r && (double)d == r2) || (r == std && r2 == std)) { \ + /** fprintf(stderr, "Okay for %s\n", s); */ \ + } else if ((pn2.as_i + 1) == (pn.as_i) || (pn.as_i + 1) == pn2.as_i) { \ + if (FIO_LOG_LEVEL == FIO_LOG_LEVEL_DEBUG) \ + FIO_LOG_WARNING("Single bit rounding error detected (%s1): %s\n", \ + ((pn2.as_i + 1) == (pn.as_i) ? "-" : "+"), \ + s); \ + } else if ((pn1.as_i + 1) == (pn.as_i) || (pn.as_i + 1) == pn1.as_i) { \ + if (FIO_LOG_LEVEL == FIO_LOG_LEVEL_DEBUG) \ + FIO_LOG_WARNING("aton Single bit rounding error detected (%s1): %s\n" \ + "\t%g != %g", \ + ((pn1.as_i + 1) == (pn.as_i) ? "-" : "+"), \ + s, \ + r2, \ + std); \ + } else if (r == 0.0 && (double)d != 0.0 && !isnan((double)d)) { \ + if (FIO_LOG_LEVEL == FIO_LOG_LEVEL_DEBUG) \ + FIO_LOG_WARNING("float range limit marked before: %s\n", s); \ + } else if (r2 == 0.0 && (double)d != 0.0 && !isnan((double)d)) { \ + if (FIO_LOG_LEVEL == FIO_LOG_LEVEL_DEBUG) \ + FIO_LOG_WARNING("aton float range limit marked before: %s\n", s); \ + } else { \ + char f_buf[256]; \ + pn.d_ = std; \ + pn2.d_ = r; \ + size_t tmp_pos = fio_ltoa(f_buf, pn2.as_i, 2); \ + f_buf[tmp_pos++] = '\n'; \ + tmp_pos += fio_ltoa(f_buf + tmp_pos, pn.as_i, 2); \ + f_buf[tmp_pos++] = '\n'; \ + fio_ltoa(f_buf + tmp_pos, pn1.as_i, 2); \ + FIO_ASSERT(0, \ + "Float error bigger than a single bit rounding error." \ + "\n\tString: %s" \ + "\n\texp. " \ + "vs. act.:\nstd %.19g\natof %.19g\naton %.19g\nBinary:\n%s", \ + s, \ + std, \ + r, \ + r2, \ + f_buf); \ + } \ + } while (0) + + fprintf(stderr, "* Testing fio_atof & fio_aton samples.\n"); + + /* A few hex-float examples */ + TEST_DOUBLE("0x10.1p0", 0x10.1p0, 0); + TEST_DOUBLE("0x1.8p1", 0x1.8p1, 0); + TEST_DOUBLE("0x1.8p5", 0x1.8p5, 0); + TEST_DOUBLE("0x4.0p5", 0x4.0p5, 0); + TEST_DOUBLE("0x1.0p50a", 0x1.0p50, 'a'); + TEST_DOUBLE("0x1.0p500", 0x1.0p500, 0); + TEST_DOUBLE("0x1.0P-1074", 0x1.0P-1074, 0); + TEST_DOUBLE("0x3a.0P-1074", 0x3a.0P-1074, 0); + + /* These numbers were copied from https://gist.github.com/mattn/1890186 */ + TEST_DOUBLE(".1", 0.1, 0); + TEST_DOUBLE(" .", 0, 0); + TEST_DOUBLE(" 1.2e3", 1.2e3, 0); + TEST_DOUBLE(" +1.2e3", 1.2e3, 0); + TEST_DOUBLE("1.2e3", 1.2e3, 0); + TEST_DOUBLE("+1.2e3", 1.2e3, 0); + TEST_DOUBLE("+1.e3", 1000, 0); + TEST_DOUBLE("-1.2e3", -1200, 0); + TEST_DOUBLE("-1.2e3.5", -1200, '.'); + TEST_DOUBLE("-1.2e", -1.2, 0); + TEST_DOUBLE("--1.2e3.5", 0, '-'); + TEST_DOUBLE("--1-.2e3.5", 0, '-'); + TEST_DOUBLE("-a", 0, 'a'); + TEST_DOUBLE("a", 0, 'a'); + TEST_DOUBLE(".1e", 0.1, 0); + TEST_DOUBLE(".1e3", 100, 0); + TEST_DOUBLE(".1e-3", 0.1e-3, 0); + TEST_DOUBLE(".1e-", 0.1, 0); + TEST_DOUBLE(" .e-", 0, 0); + TEST_DOUBLE(" .e", 0, 0); + TEST_DOUBLE(" e", 0, 0); + TEST_DOUBLE(" e0", 0, 0); + TEST_DOUBLE(" ee", 0, 'e'); + TEST_DOUBLE(" -e", 0, 0); + TEST_DOUBLE(" .9", 0.9, 0); + TEST_DOUBLE(" ..9", 0, '.'); + TEST_DOUBLE("007", 7, 0); + TEST_DOUBLE("0.09e02", 9, 0); + /* http://thread.gmane.org/gmane.editors.vim.devel/19268/ */ + TEST_DOUBLE("0.9999999999999999999999999999999999", 1, 0); + TEST_DOUBLE("2.2250738585072010e-308", 2.225073858507200889e-308, 0); + TEST_DOUBLE("2.2250738585072013e-308", 2.225073858507201383e-308, 0); + TEST_DOUBLE("9214843084008499", 9214843084008499, 0); + TEST_DOUBLE("30078505129381147446200", 3.007850512938114954e+22, 0); + + /* These numbers were copied from https://github.com/miloyip/rapidjson */ + TEST_DOUBLE("0.0", 0.0, 0); + TEST_DOUBLE("-0.0", -0.0, 0); + TEST_DOUBLE("1.0", 1.0, 0); + TEST_DOUBLE("-1.0", -1.0, 0); + TEST_DOUBLE("1.5", 1.5, 0); + TEST_DOUBLE("-1.5", -1.5, 0); + TEST_DOUBLE("3.1416", 3.1416, 0); + TEST_DOUBLE("1E10", 1E10, 0); + TEST_DOUBLE("1e10", 1e10, 0); + TEST_DOUBLE("100000000000000000000000000000000000000000000000000000000000" + "000000000000000000000", + 1E80, + 0); + TEST_DOUBLE("1E+10", 1E+10, 0); + TEST_DOUBLE("1E-10", 1E-10, 0); + TEST_DOUBLE("-1E10", -1E10, 0); + TEST_DOUBLE("-1e10", -1e10, 0); + TEST_DOUBLE("-1E+10", -1E+10, 0); + TEST_DOUBLE("-1E-10", -1E-10, 0); + TEST_DOUBLE("1.234E+10", 1.234E+10, 0); + TEST_DOUBLE("1.234E-10", 1.234E-10, 0); + TEST_DOUBLE("1.79769e+308", 1.79769e+308, 0); + TEST_DOUBLE("2.22507e-308", 2.22507e-308, 0); + TEST_DOUBLE("-1.79769e+308", -1.79769e+308, 0); + TEST_DOUBLE("-2.22507e-308", -2.22507e-308, 0); + TEST_DOUBLE("4.9406564584124654e-324", 4.9406564584124654e-324, 0); + TEST_DOUBLE("2.2250738585072009e-308", 2.2250738585072009e-308, 0); + TEST_DOUBLE("2.2250738585072014e-308", 2.2250738585072014e-308, 0); + TEST_DOUBLE("1.7976931348623157e+308", 1.7976931348623157e+308, 0); + TEST_DOUBLE("1e-10000", 0.0, 0); + TEST_DOUBLE("18446744073709551616", 18446744073709551616.0, 0); + + TEST_DOUBLE("-9223372036854775809", -9223372036854775809.0, 0); + + TEST_DOUBLE("0.9868011474609375", 0.9868011474609375, 0); + TEST_DOUBLE("123e34", 123e34, 0); + TEST_DOUBLE("45913141877270640000.0", 45913141877270640000.0, 0); + TEST_DOUBLE("2.2250738585072011e-308", 2.2250738585072011e-308, 0); + TEST_DOUBLE("1e-214748363", 0.0, 0); + TEST_DOUBLE("1e-214748364", 0.0, 0); + TEST_DOUBLE("0.017976931348623157e+310, 1", 1.7976931348623157e+308, ','); + + TEST_DOUBLE("2.2250738585072012e-308", 2.2250738585072014e-308, 0); + TEST_DOUBLE("2.22507385850720113605740979670913197593481954635164565e-308", + 2.2250738585072014e-308, + 0); + + TEST_DOUBLE("0.999999999999999944488848768742172978818416595458984375", + 1.0, + 0); + TEST_DOUBLE("0.999999999999999944488848768742172978818416595458984376", + 1.0, + 0); + TEST_DOUBLE("1.00000000000000011102230246251565404236316680908203125", + 1.0, + 0); + TEST_DOUBLE("1.00000000000000011102230246251565404236316680908203124", + 1.0, + 0); + + TEST_DOUBLE("72057594037927928.0", 72057594037927928.0, 0); + TEST_DOUBLE("72057594037927936.0", 72057594037927936.0, 0); + TEST_DOUBLE("72057594037927932.0", 72057594037927936.0, 0); + TEST_DOUBLE("7205759403792793200001e-5", 72057594037927936.0, 0); + + TEST_DOUBLE("9223372036854774784.0", 9223372036854774784.0, 0); + TEST_DOUBLE("9223372036854775808.0", 9223372036854775808.0, 0); + TEST_DOUBLE("9223372036854775296.0", 9223372036854775808.0, 0); + TEST_DOUBLE("922337203685477529600001e-5", 9223372036854775808.0, 0); + + TEST_DOUBLE("10141204801825834086073718800384", + 10141204801825834086073718800384.0, + 0); + TEST_DOUBLE("10141204801825835211973625643008", + 10141204801825835211973625643008.0, + 0); + TEST_DOUBLE("10141204801825834649023672221696", + 10141204801825835211973625643008.0, + 0); + TEST_DOUBLE("1014120480182583464902367222169600001e-5", + 10141204801825835211973625643008.0, + 0); + + TEST_DOUBLE("5708990770823838890407843763683279797179383808", + 5708990770823838890407843763683279797179383808.0, + 0); + TEST_DOUBLE("5708990770823839524233143877797980545530986496", + 5708990770823839524233143877797980545530986496.0, + 0); + TEST_DOUBLE("5708990770823839207320493820740630171355185152", + 5708990770823839524233143877797980545530986496.0, + 0); + TEST_DOUBLE("5708990770823839207320493820740630171355185152001e-3", + 5708990770823839524233143877797980545530986496.0, + 0); +#undef TEST_DOUBLE +#if !DEBUG + { + clock_t start, stop; + fio_memcpy15x(buffer, "1234567890.123", 14); + buffer[14] = 0; + volatile size_t r = 0; + start = clock(); + for (int i = 0; i < (FIO_ATOL_TEST_MAX << 3); ++i) { + char *pos = buffer; + r += fio_atol(&pos); + FIO_COMPILER_GUARD; + // FIO_ASSERT(r == exp, "fio_atol failed during speed test"); + } + stop = clock(); + fprintf(stderr, + "* fio_atol speed test completed in %zu cycles\n", + (size_t)(stop - start)); + r = 0; + + start = clock(); + for (int i = 0; i < (FIO_ATOL_TEST_MAX << 3); ++i) { + char *pos = buffer; + r += strtol(pos, NULL, 10); + FIO_COMPILER_GUARD; + // FIO_ASSERT(r == exp, "system strtol failed during speed test"); + } + stop = clock(); + fprintf(stderr, + "* system atol speed test completed in %zu cycles\n", + (size_t)(stop - start)); + } +#endif /* !DEBUG */ +} +#undef FIO_ATOL_TEST_MAX + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Atomics Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_ATOMIC_TEST___H) +#define H___FIO_ATOMIC_TEST___H +#ifndef H___FIO_ATOMIC___H +#define FIO_ATOMIC +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, atomics)(void) { + fprintf(stderr, "* Testing atomic operation macros.\n"); + struct fio___atomic_test_s { + size_t w; + unsigned long l; + unsigned short s; + unsigned char c; + } s = {0}, r1 = {0}, r2 = {0}; + fio_lock_i lock = FIO_LOCK_INIT; + + r1.c = fio_atomic_add(&s.c, 1); + r1.s = fio_atomic_add(&s.s, 1); + r1.l = fio_atomic_add(&s.l, 1); + r1.w = fio_atomic_add(&s.w, 1); + FIO_ASSERT(r1.c == 0 && s.c == 1, "fio_atomic_add failed for c"); + FIO_ASSERT(r1.s == 0 && s.s == 1, "fio_atomic_add failed for s"); + FIO_ASSERT(r1.l == 0 && s.l == 1, "fio_atomic_add failed for l"); + FIO_ASSERT(r1.w == 0 && s.w == 1, "fio_atomic_add failed for w"); + r2.c = fio_atomic_add_fetch(&s.c, 1); + r2.s = fio_atomic_add_fetch(&s.s, 1); + r2.l = fio_atomic_add_fetch(&s.l, 1); + r2.w = fio_atomic_add_fetch(&s.w, 1); + FIO_ASSERT(r2.c == 2 && s.c == 2, "fio_atomic_add_fetch failed for c"); + FIO_ASSERT(r2.s == 2 && s.s == 2, "fio_atomic_add_fetch failed for s"); + FIO_ASSERT(r2.l == 2 && s.l == 2, "fio_atomic_add_fetch failed for l"); + FIO_ASSERT(r2.w == 2 && s.w == 2, "fio_atomic_add_fetch failed for w"); + r1.c = fio_atomic_sub(&s.c, 1); + r1.s = fio_atomic_sub(&s.s, 1); + r1.l = fio_atomic_sub(&s.l, 1); + r1.w = fio_atomic_sub(&s.w, 1); + FIO_ASSERT(r1.c == 2 && s.c == 1, "fio_atomic_sub failed for c"); + FIO_ASSERT(r1.s == 2 && s.s == 1, "fio_atomic_sub failed for s"); + FIO_ASSERT(r1.l == 2 && s.l == 1, "fio_atomic_sub failed for l"); + FIO_ASSERT(r1.w == 2 && s.w == 1, "fio_atomic_sub failed for w"); + r2.c = fio_atomic_sub_fetch(&s.c, 1); + r2.s = fio_atomic_sub_fetch(&s.s, 1); + r2.l = fio_atomic_sub_fetch(&s.l, 1); + r2.w = fio_atomic_sub_fetch(&s.w, 1); + FIO_ASSERT(r2.c == 0 && s.c == 0, "fio_atomic_sub_fetch failed for c"); + FIO_ASSERT(r2.s == 0 && s.s == 0, "fio_atomic_sub_fetch failed for s"); + FIO_ASSERT(r2.l == 0 && s.l == 0, "fio_atomic_sub_fetch failed for l"); + FIO_ASSERT(r2.w == 0 && s.w == 0, "fio_atomic_sub_fetch failed for w"); + fio_atomic_add(&s.c, 1); + fio_atomic_add(&s.s, 1); + fio_atomic_add(&s.l, 1); + fio_atomic_add(&s.w, 1); + r1.c = fio_atomic_exchange(&s.c, 99); + r1.s = fio_atomic_exchange(&s.s, 99); + r1.l = fio_atomic_exchange(&s.l, 99); + r1.w = fio_atomic_exchange(&s.w, 99); + FIO_ASSERT(r1.c == 1 && s.c == 99, "fio_atomic_exchange failed for c"); + FIO_ASSERT(r1.s == 1 && s.s == 99, "fio_atomic_exchange failed for s"); + FIO_ASSERT(r1.l == 1 && s.l == 99, "fio_atomic_exchange failed for l"); + FIO_ASSERT(r1.w == 1 && s.w == 99, "fio_atomic_exchange failed for w"); + // clang-format off + FIO_ASSERT(!fio_atomic_compare_exchange_p(&s.c, &r1.c, &r1.c), "fio_atomic_compare_exchange_p didn't fail for c"); + FIO_ASSERT(!fio_atomic_compare_exchange_p(&s.s, &r1.s, &r1.s), "fio_atomic_compare_exchange_p didn't fail for s"); + FIO_ASSERT(!fio_atomic_compare_exchange_p(&s.l, &r1.l, &r1.l), "fio_atomic_compare_exchange_p didn't fail for l"); + FIO_ASSERT(!fio_atomic_compare_exchange_p(&s.w, &r1.w, &r1.w), "fio_atomic_compare_exchange_p didn't fail for w"); + r1.c = 1;s.c = 99; r1.s = 1;s.s = 99; r1.l = 1;s.l = 99; r1.w = 1;s.w = 99; /* ignore system spefcific behavior. */ + r1.c = fio_atomic_compare_exchange_p(&s.c,&s.c, &r1.c); + r1.s = fio_atomic_compare_exchange_p(&s.s,&s.s, &r1.s); + r1.l = fio_atomic_compare_exchange_p(&s.l,&s.l, &r1.l); + r1.w = fio_atomic_compare_exchange_p(&s.w,&s.w, &r1.w); + FIO_ASSERT(r1.c == 1 && s.c == 1, "fio_atomic_compare_exchange_p failed for c (%zu got %zu)", (size_t)s.c, (size_t)r1.c); + FIO_ASSERT(r1.s == 1 && s.s == 1, "fio_atomic_compare_exchange_p failed for s (%zu got %zu)", (size_t)s.s, (size_t)r1.s); + FIO_ASSERT(r1.l == 1 && s.l == 1, "fio_atomic_compare_exchange_p failed for l (%zu got %zu)", (size_t)s.l, (size_t)r1.l); + FIO_ASSERT(r1.w == 1 && s.w == 1, "fio_atomic_compare_exchange_p failed for w (%zu got %zu)", (size_t)s.w, (size_t)r1.w); + // clang-format on + + uint64_t val = 1; + FIO_ASSERT(fio_atomic_and(&val, 2) == 1, + "fio_atomic_and should return old value"); + FIO_ASSERT(val == 0, "fio_atomic_and should update value"); + FIO_ASSERT(fio_atomic_xor(&val, 1) == 0, + "fio_atomic_xor should return old value"); + FIO_ASSERT(val == 1, "fio_atomic_xor_fetch should update value"); + FIO_ASSERT(fio_atomic_xor_fetch(&val, 1) == 0, + "fio_atomic_xor_fetch should return new value"); + FIO_ASSERT(val == 0, "fio_atomic_xor should update value"); + FIO_ASSERT(fio_atomic_or(&val, 2) == 0, + "fio_atomic_or should return old value"); + FIO_ASSERT(val == 2, "fio_atomic_or should update value"); + FIO_ASSERT(fio_atomic_or_fetch(&val, 1) == 3, + "fio_atomic_or_fetch should return new value"); + FIO_ASSERT(val == 3, "fio_atomic_or_fetch should update value"); +#if !_MSC_VER /* don't test missing MSVC features */ + FIO_ASSERT(fio_atomic_nand_fetch(&val, 4) == ~0ULL, + "fio_atomic_nand_fetch should return new value"); + FIO_ASSERT(val == ~0ULL, "fio_atomic_nand_fetch should update value"); + val = 3ULL; + FIO_ASSERT(fio_atomic_nand(&val, 4) == 3ULL, + "fio_atomic_nand should return old value"); + FIO_ASSERT(val == ~0ULL, "fio_atomic_nand_fetch should update value"); +#endif /* !_MSC_VER */ + FIO_ASSERT(!fio_is_locked(&lock), + "lock should be initialized in unlocked state"); + FIO_ASSERT(!fio_trylock(&lock), "fio_trylock should succeed"); + FIO_ASSERT(fio_trylock(&lock), "fio_trylock should fail"); + FIO_ASSERT(fio_is_locked(&lock), "lock should be engaged"); + fio_unlock(&lock); + FIO_ASSERT(!fio_is_locked(&lock), "lock should be released"); + fio_lock(&lock); + FIO_ASSERT(fio_is_locked(&lock), "lock should be engaged (fio_lock)"); + for (uint8_t i = 1; i < 8; ++i) { + FIO_ASSERT(!fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(i)), + "group lock flagged, but wasn't engaged (%u - %p)", + (unsigned int)i, + (void *)(uintptr_t)lock); + } + fio_unlock(&lock); + FIO_ASSERT(!fio_is_locked(&lock), "lock should be released"); + lock = FIO_LOCK_INIT; + for (size_t i = 0; i < 8; ++i) { + FIO_ASSERT(!fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(i)), + "group lock should be initialized in unlocked state"); + FIO_ASSERT(!fio_trylock_group(&lock, FIO_LOCK_SUBLOCK(i)), + "fio_trylock_group should succeed"); + FIO_ASSERT(fio_trylock_group(&lock, FIO_LOCK_SUBLOCK(i)), + "fio_trylock should fail"); + FIO_ASSERT(fio_trylock_full(&lock), "fio_trylock_full should fail"); + FIO_ASSERT(fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(i)), + "sub-lock %d should be engaged", + i); + { + uint8_t g = + fio_trylock_group(&lock, FIO_LOCK_SUBLOCK(1) | FIO_LOCK_SUBLOCK(3)); + FIO_ASSERT((i != 1 && i != 3 && !g) || ((i == 1 || i == 3) && g), + "fio_trylock_group should succeed / fail"); + if (!g) + fio_unlock_group(&lock, FIO_LOCK_SUBLOCK(1) | FIO_LOCK_SUBLOCK(3)); + } + for (uint8_t j = 1; j < 8; ++j) { + FIO_ASSERT(i == j || !fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(j)), + "another group lock was flagged, though it wasn't engaged"); + } + FIO_ASSERT(fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(i)), + "lock should remain engaged"); + fio_unlock_group(&lock, FIO_LOCK_SUBLOCK(i)); + FIO_ASSERT(!fio_is_group_locked(&lock, FIO_LOCK_SUBLOCK(i)), + "group lock should be released"); + FIO_ASSERT(!fio_trylock_full(&lock), "fio_trylock_full should succeed"); + fio_unlock_full(&lock); + FIO_ASSERT(!lock, "fio_unlock_full should unlock all"); + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_CLI Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_CLI_TEST___H) +#define H___FIO_CLI_TEST___H +#ifndef H___FIO_CLI___H +#define FIO_CLI +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, cli)(void) { + const char *argv[] = { + "appname", + "-i11", + "-i2=2", + "-i3", + "3", + "-t,u", + "-s", + "test", + "unnamed", + }; + const int argc = sizeof(argv) / sizeof(argv[0]); + fprintf(stderr, "* Testing CLI helpers.\n"); + { /* avoid macro for C++ */ + fio___cli_line_s arguments[] = { + FIO_CLI_INT("-integer1 -i1 first integer"), + FIO_CLI_INT("-integer2 -i2 second integer"), + FIO_CLI_INT("-integer3 -i3 third integer"), + FIO_CLI_INT("-integer4 -i4 (4) fourth integer"), + FIO_CLI_INT("-integer5 -i5 (\"5\") fifth integer"), + FIO_CLI_BOOL("-boolean -t boolean"), + FIO_CLI_BOOL("-boolean2 -u boolean"), + FIO_CLI_BOOL("-boolean_false -f boolean"), + FIO_CLI_STRING("-str -s a string"), + FIO_CLI_PRINT_HEADER("Printing stuff"), + FIO_CLI_PRINT_LINE("does nothing, but shouldn't crash either"), + FIO_CLI_PRINT("does nothing, but shouldn't crash either"), + {(fio_cli_arg_e)0}, + }; + fio_cli_start FIO_NOOP(argc, argv, 0, -1, NULL, arguments); + } + FIO_ASSERT(fio_cli_get_i("-i2") == 2, "CLI second integer error."); + FIO_ASSERT(fio_cli_get_i("-i3") == 3, "CLI third integer error."); + FIO_ASSERT(fio_cli_get_i("-i4") == 4, + "CLI fourth integer error (%s).", + fio_cli_get("-i4")); + FIO_ASSERT(fio_cli_get_i("-i5") == 5, + "CLI fifth integer error (%s).", + fio_cli_get("-i5")); + FIO_ASSERT(fio_cli_get_i("-i1") == 1, "CLI first integer error."); + FIO_ASSERT(fio_cli_get_i("-i2") == fio_cli_get_i("-integer2"), + "CLI second integer error."); + FIO_ASSERT(fio_cli_get_i("-i3") == fio_cli_get_i("-integer3"), + "CLI third integer error."); + FIO_ASSERT(fio_cli_get_i("-i1") == fio_cli_get_i("-integer1"), + "CLI first integer error."); + FIO_ASSERT(fio_cli_get_i("-t") == 1, "CLI boolean true error."); + FIO_ASSERT(fio_cli_get_i("-u") == 1, "CLI boolean 2 true error."); + FIO_ASSERT(fio_cli_get_i("-f") == 0, "CLI boolean false error."); + FIO_ASSERT(!strcmp(fio_cli_get("-s"), "test"), "CLI string error."); + FIO_ASSERT(fio_cli_unnamed_count() == 1, "CLI unnamed count error."); + FIO_ASSERT(!strcmp(fio_cli_unnamed(0), "unnamed"), "CLI unnamed error."); + fio_cli_set("-manual", "okay"); + FIO_ASSERT(!strcmp(fio_cli_get("-manual"), "okay"), "CLI set/get error."); + fio_cli_end(); + FIO_ASSERT(fio_cli_get_i("-i1") == 0, "CLI cleanup error."); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Core Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_CORE_TEST___H) +#define H___FIO_CORE_TEST___H + +FIO_SFUNC void FIO_NAME_TEST(stl, core)(void) { + fprintf(stderr, "* Testing fio_memcpy primitives.\n"); + { + struct { + void *(*fn)(void *, const void *, size_t); + const char *name; + size_t len; + } tests[] = { + {fio_memcpy7x, "fio_memcpy7x", 7}, + {fio_memcpy15x, "fio_memcpy15x", 15}, + {fio_memcpy31x, "fio_memcpy31x", 31}, + {fio_memcpy63x, "fio_memcpy63x", 63}, + {fio_memcpy127x, "fio_memcpy127x", 127}, + {fio_memcpy255x, "fio_memcpy255x", 255}, + {fio_memcpy511x, "fio_memcpy511x", 511}, + {fio_memcpy1023x, "fio_memcpy1023x", 1023}, + {fio_memcpy2047x, "fio_memcpy2047x", 2047}, + {fio_memcpy4095x, "fio_memcpy4095x", 4095}, + {NULL}, + }; + char buf[(4096 << 1) + 64]; + fio_rand_bytes(buf + (4096 + 32), (4096 + 32)); + for (size_t ifn = 0; tests[ifn].fn; ++ifn) { + /* test all x primitives */ + size_t len = tests[ifn].len; + for (size_t i = 0; i < 31; ++i) { + memset(buf, 0, 4096 + 32); + buf[i + len] = '\xFF'; + tests[ifn].fn(buf + i, buf + (4096 + 32), len); + FIO_ASSERT(!memcmp(buf + i, buf + (4096 + 32), len), + "%s failed @ %zu\n", + tests[ifn].name, + i); + FIO_ASSERT(fio_ct_is_eq(buf + i, buf + (4096 + 32), len), + "fio_ct_is_eq claims that %s failed @ %zu\n", + tests[ifn].name, + i); + FIO_ASSERT(buf[i + len] == '\xFF', "%s overflow?", tests[ifn].name); + } + } + } + fprintf(stderr, "* Testing fio_bswapX macros.\n"); + FIO_ASSERT(fio_bswap16(0x0102) == (uint16_t)0x0201, "fio_bswap16 failed"); + FIO_ASSERT(fio_bswap32(0x01020304) == (uint32_t)0x04030201, + "fio_bswap32 failed"); + FIO_ASSERT(fio_bswap64(0x0102030405060708ULL) == 0x0807060504030201ULL, + "fio_bswap64 failed"); + + fprintf(stderr, "* Testing fio_lrotX and fio_rrotX macros.\n"); + { + uint64_t tmp = 1; + tmp = FIO_RROT(tmp, 1); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << ((sizeof(uint64_t) << 3) - 1)), + "fio_rrot failed"); + tmp = FIO_LROT(tmp, 3); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << 2), "fio_lrot failed"); + tmp = 1; + tmp = fio_rrot32((uint32_t)tmp, 1); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << 31), "fio_rrot32 failed"); + tmp = fio_lrot32((uint32_t)tmp, 3); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << 2), "fio_lrot32 failed"); + tmp = 1; + tmp = fio_rrot64(tmp, 1); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << 63), "fio_rrot64 failed"); + tmp = fio_lrot64(tmp, 3); + FIO_COMPILER_GUARD; + FIO_ASSERT(tmp == ((uint64_t)1 << 2), "fio_lrot64 failed"); + } + for (size_t i = 0; i < 63; ++i) { +#if !defined(__has_builtin) || !__has_builtin(__builtin_ctzll) || \ + !__has_builtin(__builtin_clzll) + FIO_ASSERT(fio___single_bit_index_unsafe((1ULL << i)) == i, + "bit index map[%zu] error != %zu", + (size_t)(1ULL << i), + i); +#endif + FIO_ASSERT(fio_bits_msb_index(((1ULL << i) | 1)) == i, + "fio_bits_msb_index(%zu) != %zu", + ((1ULL << i)), + (size_t)fio_bits_msb_index(((1ULL << i) | 1))); + FIO_ASSERT(fio_bits_lsb_index(((~0ULL) << i)) == i, + "fio_bits_lsb_index(%zu) != %zu", + 1, + (size_t)fio_bits_lsb_index(((~0ULL) << i))); + } + + fprintf(stderr, "* Testing fio_buf2uX and fio_u2bufX helpers.\n"); +#define FIO___BITMAP_TEST_BITS(itype, utype, bits) \ + for (size_t i = 0; i < (bits); ++i) { \ + char tmp_buf[32]; \ + itype n = ((utype)1 << i); \ + FIO_NAME2(fio_u, buf##bits##u)(tmp_buf, n); \ + itype r = FIO_NAME2(fio_buf, u##bits##u)(tmp_buf); \ + FIO_ASSERT(r == n, \ + "roundtrip failed for U" #bits " at bit %zu\n\t%zu != %zu", \ + i, \ + (size_t)n, \ + (size_t)r); \ + FIO_ASSERT(!memcmp(tmp_buf, &n, (bits) >> 3), \ + "memory ordering implementation error for U" #bits "!"); \ + } + FIO___BITMAP_TEST_BITS(int8_t, uint8_t, 8); + FIO___BITMAP_TEST_BITS(int16_t, uint16_t, 16); + FIO___BITMAP_TEST_BITS(int32_t, uint32_t, 32); + FIO___BITMAP_TEST_BITS(int64_t, uint64_t, 64); +#undef FIO___BITMAP_TEST_BITS + + fprintf(stderr, "* Testing constant-time helpers.\n"); + FIO_ASSERT(fio_ct_true(0) == 0, "fio_ct_true(0) should be zero!"); + for (uintptr_t i = 1; i; i <<= 1) { + FIO_ASSERT(fio_ct_true(i) == 1, + "fio_ct_true(%p) should be true!", + (void *)i); + } + for (uintptr_t i = 1; i + 1 != 0; i = (i << 1) | 1) { + FIO_ASSERT(fio_ct_true(i) == 1, + "fio_ct_true(%p) should be true!", + (void *)i); + } + FIO_ASSERT(fio_ct_true(((uintptr_t)~0ULL)) == 1, + "fio_ct_true(%p) should be true!", + (void *)(uintptr_t)(~0ULL)); + + FIO_ASSERT(fio_ct_false(0) == 1, "fio_ct_false(0) should be true!"); + for (uintptr_t i = 1; i; i <<= 1) { + FIO_ASSERT(fio_ct_false(i) == 0, + "fio_ct_false(%p) should be zero!", + (void *)i); + } + for (uintptr_t i = 1; i + 1 != 0; i = (i << 1) | 1) { + FIO_ASSERT(fio_ct_false(i) == 0, + "fio_ct_false(%p) should be zero!", + (void *)i); + } + FIO_ASSERT(fio_ct_false(((uintptr_t)~0ULL)) == 0, + "fio_ct_false(%p) should be zero!", + (void *)(uintptr_t)(~0ULL)); + FIO_ASSERT(fio_ct_true(8), "fio_ct_true should be true."); + FIO_ASSERT(!fio_ct_true(0), "fio_ct_true should be false."); + FIO_ASSERT(!fio_ct_false(8), "fio_ct_false should be false."); + FIO_ASSERT(fio_ct_false(0), "fio_ct_false should be true."); + FIO_ASSERT(fio_ct_if_bool(0, 1, 2) == 2, + "fio_ct_if_bool selection error (false)."); + FIO_ASSERT(fio_ct_if_bool(1, 1, 2) == 1, + "fio_ct_if_bool selection error (true)."); + FIO_ASSERT(fio_ct_if(0, 1, 2) == 2, "fio_ct_if selection error (false)."); + FIO_ASSERT(fio_ct_if(8, 1, 2) == 1, "fio_ct_if selection error (true)."); + FIO_ASSERT(fio_ct_max(1, 2) == 2, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(2, 1) == 2, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(-1, 2) == 2, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(2, -1) == 2, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(1, -2) == 1, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(-2, 1) == 1, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(-1, -2) == -1, "fio_ct_max error."); + FIO_ASSERT(fio_ct_max(-2, -1) == -1, "fio_ct_max error."); + { + uint8_t bitmap[1024]; + FIO_MEMSET(bitmap, 0, 1024); + fprintf(stderr, "* Testing bitmap helpers.\n"); + FIO_ASSERT(!fio_bit_get(bitmap, 97), "fio_bit_get should be 0."); + fio_bit_set(bitmap, 97); + FIO_ASSERT(fio_bit_get(bitmap, 97) == 1, + "fio_bit_get should be 1 after being set"); + FIO_ASSERT(!fio_bit_get(bitmap, 96), + "other bits shouldn't be effected by set."); + FIO_ASSERT(!fio_bit_get(bitmap, 98), + "other bits shouldn't be effected by set."); + fio_bit_flip(bitmap, 96); + fio_bit_flip(bitmap, 97); + FIO_ASSERT(!fio_bit_get(bitmap, 97), "fio_bit_get should be 0 after flip."); + FIO_ASSERT(fio_bit_get(bitmap, 96) == 1, + "other bits shouldn't be effected by flip"); + fio_bit_unset(bitmap, 96); + fio_bit_flip(bitmap, 97); + FIO_ASSERT(!fio_bit_get(bitmap, 96), + "fio_bit_get should be 0 after unset."); + FIO_ASSERT(fio_bit_get(bitmap, 97) == 1, + "other bits shouldn't be effected by unset"); + fio_bit_unset(bitmap, 96); + } + { + uint8_t bitmap[1024]; + FIO_MEMSET(bitmap, 0, 1024); + fprintf(stderr, "* Testing atomic bitmap helpers.\n"); + FIO_ASSERT(!fio_atomic_bit_get(bitmap, 97), + "fio_atomic_bit_get should be 0."); + fio_atomic_bit_set(bitmap, 97); + FIO_ASSERT(fio_atomic_bit_get(bitmap, 97) == 1, + "fio_atomic_bit_get should be 1 after being set"); + FIO_ASSERT(!fio_atomic_bit_get(bitmap, 96), + "other bits shouldn't be effected by set."); + FIO_ASSERT(!fio_atomic_bit_get(bitmap, 98), + "other bits shouldn't be effected by set."); + fio_atomic_bit_flip(bitmap, 96); + fio_atomic_bit_flip(bitmap, 97); + FIO_ASSERT(!fio_atomic_bit_get(bitmap, 97), + "fio_atomic_bit_get should be 0 after flip."); + FIO_ASSERT(fio_atomic_bit_get(bitmap, 96) == 1, + "other bits shouldn't be effected by flip"); + fio_atomic_bit_unset(bitmap, 96); + fio_atomic_bit_flip(bitmap, 97); + FIO_ASSERT(!fio_atomic_bit_get(bitmap, 96), + "fio_atomic_bit_get should be 0 after unset."); + FIO_ASSERT(fio_atomic_bit_get(bitmap, 97) == 1, + "other bits shouldn't be effected by unset"); + fio_atomic_bit_unset(bitmap, 96); + } + { + fprintf(stderr, "* Testing popcount and hemming distance calculation.\n"); + for (int i = 0; i < 64; ++i) { + FIO_ASSERT(fio_popcount((uint64_t)1 << i) == 1, + "fio_popcount error for 1 bit"); + } + for (int i = 0; i < 63; ++i) { + FIO_ASSERT(fio_popcount((uint64_t)3 << i) == 2, + "fio_popcount error for 2 bits"); + } + for (int i = 0; i < 62; ++i) { + FIO_ASSERT(fio_popcount((uint64_t)7 << i) == 3, + "fio_popcount error for 3 bits"); + } + for (int i = 0; i < 59; ++i) { + FIO_ASSERT(fio_popcount((uint64_t)21 << i) == 3, + "fio_popcount error for 3 alternating bits"); + } + for (int i = 0; i < 64; ++i) { + FIO_ASSERT(fio_hemming_dist(((uint64_t)1 << i) - 1, 0) == i, + "fio_hemming_dist error at %d", + i); + } + } + { + struct test_s { + int a; + char force_padding; + int b; + } stst = {.a = 1}; + + struct test_s *stst_p = FIO_PTR_FROM_FIELD(struct test_s, b, &stst.b); + FIO_ASSERT(stst_p == &stst, "FIO_PTR_FROM_FIELD failed to retrace pointer"); + } + { + fprintf(stderr, "* Testing fio_xmask.\n"); + char data[128], buf[256]; + uint64_t mask; + uint64_t counter; + do { + mask = fio_rand64(); + counter = fio_rand64(); + } while (fio_has_zero_byte64(mask) || !counter); + fio_rand_bytes(data, 128); + const size_t len = 127; + for (uint8_t i = 0; i < 16; ++i) { + FIO_MEMCPY(buf + i, data, len); + buf[len + i] = '\xFF'; + fio_xmask(buf + i, len, mask); + FIO_ASSERT(buf[len + i] == '\xFF', "fio_xmask overflow?"); + FIO_ASSERT(memcmp(buf + i, data, len), "fio_xmask masking error"); + FIO_ASSERT(memcmp(buf + i, data, 8), "fio_xmask didn't mask data head?"); + FIO_ASSERT( + !(len & 7) || + memcmp(buf + i + (len & (~7U)), data + (len & (~7U)), (len & 7)), + "fio_xmask mask didn't mask data's tail?"); + fio_xmask(buf + i, len, mask); + FIO_ASSERT(!memcmp(buf + i, data, len), "fio_xmask rountrip error"); + fio_xmask(buf + i, len, mask); + FIO_MEMMOVE(buf + i + 1, buf + i, len); + fio_xmask(buf + i + 1, len, mask); + FIO_ASSERT(!memcmp(buf + i + 1, data, len), + "fio_xmask rountrip (with move) error"); + } + } + { + fprintf(stderr, "* Testing Core UTF-8 Support (Macros).\n"); + struct { + const char *buf; + size_t clen; + bool expect_fail; + } utf8_core_tests[] = { + {"\xf0\x9f\x92\x85", 4}, + {"\xf0\x9f\x92\x95", 4}, + {"\xe2\x9d\xa4", 3}, + {"\xE1\x9A\x80", 3}, + {"\xE2\x80\x80", 3}, + {"\xE2\x80\x81", 3}, + {"\xE2\x80\x82", 3}, + {"\xE2\x80\x83", 3}, + {"\xE2\x80\x84", 3}, + {"\xE2\x80\x85", 3}, + {"\xE2\x80\x86", 3}, + {"\xE2\x80\x87", 3}, + {"\xE2\x80\x88", 3}, + {"\xE2\x80\x89", 3}, + {"\xE2\x80\x8A", 3}, + {"\xE2\x80\xA8", 3}, + {"\xE2\x80\xA9", 3}, + {"\xE2\x80\xAF", 3}, + {"\xE2\x81\x9F", 3}, + {"\xE3\x80\x80", 3}, + {"\xEF\xBB\xBF", 3}, + {"\xc6\x92", 2}, + {"\xC2\xA0", 2}, + {"\x09", 1}, + {"\x0A", 1}, + {"\x0B", 1}, + {"\x0C", 1}, + {"\x0D", 1}, + {"\x20", 1}, + {"Z", 1}, + {"\0", 1}, + {"\xf0\x9f\x92\x35", 4, 1}, + {"\xf0\x9f\x32\x95", 4, 1}, + {"\xf0\x3f\x92\x95", 4, 1}, + {"\xFE\x9f\x92\x95", 4, 1}, + {"\xE1\x9A\x30", 3, 1}, + {"\xE1\x3A\x80", 3, 1}, + {"\xf0\x9A\x80", 3, 1}, + {"\xc6\x32", 2, 1}, + {"\xf0\x92", 2, 1}, + {0}, + }; + for (size_t i = 0; utf8_core_tests[i].buf; ++i) { + char *pos = (char *)utf8_core_tests[i].buf; + FIO_ASSERT(utf8_core_tests[i].expect_fail || + (size_t)fio_utf8_char_len(pos) == utf8_core_tests[i].clen, + "fio_utf8_char_len failed on %s ([%zu] == %X), %d != %u", + utf8_core_tests[i].buf, + i, + (unsigned)(uint8_t)utf8_core_tests[i].buf[0], + (int)fio_utf8_char_len(pos), + (unsigned)utf8_core_tests[i].clen); + uint32_t value = 0, validate = 0; + void *tst_str = NULL; + fio_memcpy7x(&tst_str, utf8_core_tests[i].buf, utf8_core_tests[i].clen); +#if __LITTLE_ENDIAN__ + tst_str = (void *)(uintptr_t)fio_lton32((uint32_t)(uintptr_t)tst_str); +#endif + value = fio_utf8_read(&pos); + uint32_t val_len = fio_utf8_code_len(value); /* val_len 0 (fail) == 1 */ + FIO_ASSERT(!utf8_core_tests[i].expect_fail || + (!value && pos == utf8_core_tests[i].buf && + !fio_utf8_char_len(utf8_core_tests[i].buf)), + "Failed to detect invalid UTF-8"); + if (utf8_core_tests[i].expect_fail) + continue; + char output[32]; + pos = output; + pos += fio_utf8_write(pos, value); + FIO_ASSERT(val_len == utf8_core_tests[i].clen, + "fio_utf8_read + fio_utf8_code_len failed on %s / %p (%zu " + "len => %zu != %zu)", + utf8_core_tests[i].buf, + tst_str, + (size_t)value, + val_len, + utf8_core_tests[i].clen); + pos = output; + validate = fio_utf8_read(&pos); + FIO_ASSERT(validate == value && (value > 0 || !utf8_core_tests[i].buf[0]), + "fio_utf8_read + fio_utf8_write roundtrip failed on [%zu] %s\n" + "\t %zu != %zu", + i, + utf8_core_tests[i].buf, + validate, + value); + } + } + { + fprintf(stderr, + "* Testing Basic Multi-Precision add / sub / mul for fio_uXXX " + "(fio_u256).\n"); + + char *buf[1024]; + + fio_u256 a = fio_u256_init64(2); + fio_u256 b = fio_u256_init64(3); + fio_u512 expected = fio_u512_init64(6); + + fio_u512 result = {0}; + fio_u256_mul(&result, &a, &b); + + FIO_ASSERT(!FIO_MEMCMP(&result, &expected, sizeof(result)), + "2 * 3 should be 6"); + FIO_ASSERT(!fio_u512_cmp(&result, &expected), + "fio_u512_cmp failed for result 6."); + + a = fio_u256_init64(2, 2); + expected = fio_u512_init64(6, 6); + fio_u256_mul(&result, &a, &b); + FIO_ASSERT(!FIO_MEMCMP(&result, &expected, sizeof(result)), + "2,2 * 3 should be 6,6"); + FIO_ASSERT(!fio_u512_cmp(&result, &expected), + "fio_u512_cmp failed for result 6,6."); + + a = fio_u256_init64(2, 0x8000000000000000); + expected = fio_u512_init64(6, 0x8000000000000000, 1); + fio_u256_mul(&result, &a, &b); + FIO_ASSERT(!FIO_MEMCMP(&result, &expected, sizeof(result)), + "2,0x8... * 3 should be 6,0x8..., 1"); + FIO_ASSERT(!fio_u512_cmp(&result, &expected), + "fio_u512_cmp failed for result 6,0x8..., 1"); + + a = fio_u256_init64(0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF); // Max value + b = fio_u256_init64(0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF); // Max value + expected = fio_u512_init64(0x1, + 0, + 0, + 0, + 0xFFFFFFFFFFFFFFFE, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF); + fio_u256_mul(&result, &a, &b); + buf[fio_u512_hex_write((char *)buf, &result)] = 0; + + FIO_ASSERT(!FIO_MEMCMP(&result, &expected, sizeof(result)), + "Max * Max should be (Max << 256) + 1\n\t0x%s", + buf); + FIO_ASSERT(!fio_u512_cmp(&result, &expected), + "fio_u512_cmp failed for Max * Max result."); + } + { + fprintf(stderr, + "* Testing Basic vector operations for fio_uXXX (fio_u256).\n"); + for (uint64_t a = 1; a; a = ((a << 2) | (((a >> 62) & 1) ^ 1))) { + for (uint64_t b = 2; b; b = ((b << 2) | (((b >> 62) & 2) ^ 2))) { + uint64_t expected[8] = {1, ~0, 4, ~0}; + uint64_t na[4] = {a, a, a, a}; + uint64_t nb[4] = {b, b, b, b}; + fio_u512 result = fio_u512_init64(~0, 1, ~0, 4); + fio_u256 ua = fio_u256_init64(a, a, a, a); + fio_u256 ub = fio_u256_init64(b, b, b, b); + + fio_u64x4_add(expected, na, nb); + fio_u256_add64(&result.u256[0], &ua, &ub); + FIO_ASSERT( + !memcmp(result.u256[0].u64, expected, sizeof(result.u256[0].u64)), + "Basic vector ADD error"); + + fio_u64x4_sub(expected, na, nb); + fio_u256_sub64(&result.u256[0], &ua, &ub); + FIO_ASSERT( + !memcmp(result.u256[0].u64, expected, sizeof(result.u256[0].u64)), + "Basic vector SUB error"); + + fio_u64x4_mul(expected, na, nb); + fio_u256_mul64(&result.u256[0], &ua, &ub); + FIO_ASSERT( + !memcmp(result.u256[0].u64, expected, sizeof(result.u256[0].u64)), + "Basic vector MUL error"); + + /* the following will probably never detect an error */ + + (void)fio_math_add(expected, na, nb, 4); + (void)fio_u256_add(&result.u256[0], &ua, &ub); + FIO_ASSERT( + !memcmp(result.u256[0].u64, expected, sizeof(result.u256[0].u64)), + "Multi-Precision ADD error"); + + (void)fio_math_sub(expected, na, nb, 4); + (void)fio_u256_sub(&result.u256[0], &ua, &ub); + FIO_ASSERT( + !memcmp(result.u256[0].u64, expected, sizeof(result.u256[0].u64)), + "Multi-Precision SUB error"); + + fio___math_mul_long(expected, na, nb, 4); /* test possible difference */ + fio_u256_mul(&result, &ua, &ub); + FIO_ASSERT(!memcmp(result.u64, expected, sizeof(result.u64)), + "Multi-Precision MUL error"); + } + } + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_MODULE_NAME Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_MODULE_NAME_TEST___H) +#define H___FIO_MODULE_NAME_TEST___H +// #ifndef H___FIO_MODULE_NAME___H +// #define FIO_MODULE_NAME +// #define FIO___TEST_REINCLUDE +// #include FIO_INCLUDE_FILE +// #undef FIO___TEST_REINCLUDE +// #endif + +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MODULE_NAME)(void) {} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_FILES Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_FILES_TEST___H) +#define H___FIO_FILES_TEST___H +#ifndef H___FIO_FILES___H +#define FIO_FILES +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, files)(void) { + fprintf(stderr, "* Testing file utilities (partial).\n"); + struct { + const char *str; + fio_filename_s result; + } filename_test[] = { + // clang-format off + {.str = "/", .result = {.folder = FIO_BUF_INFO2((char*)0, 1), .basename = FIO_BUF_INFO2(NULL, 0), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "/.", .result = {.folder = FIO_BUF_INFO2((char*)0, 1), .basename = FIO_BUF_INFO2((char*)1, 1), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "/..", .result = {.folder = FIO_BUF_INFO2((char*)0, 1), .basename = FIO_BUF_INFO2((char*)1, 2), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "name", .result = {.folder = FIO_BUF_INFO2(NULL, 0), .basename = FIO_BUF_INFO2(0, 4), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "name.ext", .result = {.folder = FIO_BUF_INFO2(NULL, 0), .basename = FIO_BUF_INFO2((char*)0, 4), .ext = FIO_BUF_INFO2((char*)5, 3)}}, + {.str = ".name", .result = {.folder = FIO_BUF_INFO2(NULL, 0), .basename = FIO_BUF_INFO2((char*)0, 5), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "/.name", .result = {.folder = FIO_BUF_INFO2((char*)0, 1), .basename = FIO_BUF_INFO2((char*)1, 5), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "/my_folder/.name", .result = {.folder = FIO_BUF_INFO2((char*)0, 11), .basename = FIO_BUF_INFO2((char*)11, 5), .ext = FIO_BUF_INFO2(NULL, 0)}}, + {.str = "/my_folder/name.ext", .result = {.folder = FIO_BUF_INFO2((char*)0, 11), .basename = FIO_BUF_INFO2((char*)11, 4), .ext = FIO_BUF_INFO2((char*)16, 3)}}, + {.str = NULL}, // clang-format on + }; + for (size_t i = 0; filename_test[i].str; ++i) { + fio_filename_s r = fio_filename_parse(filename_test[i].str); + FIO_ASSERT( + r.folder.len == filename_test[i].result.folder.len && + r.basename.len == filename_test[i].result.basename.len && + r.ext.len == filename_test[i].result.ext.len && + ((!r.folder.buf && !filename_test[i].result.folder.len) || + r.folder.buf == (filename_test[i].str + + (size_t)filename_test[i].result.folder.buf)) && + ((!r.basename.buf && !filename_test[i].result.basename.len) || + r.basename.buf == + (filename_test[i].str + + (size_t)filename_test[i].result.basename.buf)) && + ((!r.ext.buf && !filename_test[i].result.ext.len) || + r.ext.buf == (filename_test[i].str + + (size_t)filename_test[i].result.ext.buf)), + "fio_filename_parse error for %s" + "\n\t folder: (%zu) %.*s (%p)" + "\n\t basename: (%zu) %.*s (%p)" + "\n\t extension: (%zu) %.*s (%p)", + filename_test[i].str, + r.folder.len, + (int)r.folder.len, + (r.folder.buf ? r.folder.buf : "null"), + r.folder.buf, + r.basename.len, + (int)r.basename.len, + (r.basename.buf ? r.basename.buf : "null"), + r.basename.buf, + r.ext.len, + (int)r.ext.len, + (r.ext.buf ? r.ext.buf : "null"), + r.ext.buf); + } +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_FIOBJ Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_FIOBJ_TEST___H) +#define H___FIO_FIOBJ_TEST___H +#ifndef H___FIO_FIOBJ___H +#define FIO_FIOBJ +#define FIOBJ_MALLOC +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#define FIOBJ_TEST_REPETITIONS 4096 + +FIO_SFUNC int FIO_NAME_TEST(stl, fiobj_task)(fiobj_each_s *e) { + static size_t index = 0; + if (!e) { + index = 0; + return -1; + } + int *expect = (int *)e->udata; + FIO_ASSERT(e->key == FIOBJ_INVALID, "key is set in an Array loop?"); + if (expect[index] == -1) { + FIO_ASSERT(FIOBJ_TYPE(e->value) == FIOBJ_T_ARRAY, + "each2 ordering issue [%zu] (array).", + index); + } else { + FIO_ASSERT(FIO_NAME2(fiobj, i)(e->value) == expect[index], + "each2 ordering issue [%zu] (number) %ld != %d", + index, + FIO_NAME2(fiobj, i)(e->value), + expect[index]); + } + ++index; + return 0; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, fiobj)(void) { + FIOBJ o = FIOBJ_INVALID; + if (!FIOBJ_MARK_MEMORY_ENABLED) { + FIO_LOG_WARNING("FIOBJ defined without allocation counter. " + "Tests might not be complete."); + } + /* primitives - (in)sanity */ + { + fprintf(stderr, "* Testing FIOBJ primitives.\n"); + FIO_ASSERT(FIOBJ_TYPE(o) == FIOBJ_T_NULL, + "invalid FIOBJ type should be FIOBJ_T_NULL."); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(o, FIO_NAME(fiobj, FIOBJ___NAME_NULL)()), + "invalid FIOBJ is NOT a fiobj_null()."); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(fiobj_true(), + FIO_NAME(fiobj, FIOBJ___NAME_NULL)()), + "fiobj_true() is NOT fiobj_null()."); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(fiobj_false(), + FIO_NAME(fiobj, FIOBJ___NAME_NULL)()), + "fiobj_false() is NOT fiobj_null()."); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(fiobj_false(), fiobj_true()), + "fiobj_false() is NOT fiobj_true()."); + FIO_ASSERT(FIOBJ_TYPE(FIO_NAME(fiobj, FIOBJ___NAME_NULL)()) == FIOBJ_T_NULL, + "fiobj_null() type should be FIOBJ_T_NULL."); + FIO_ASSERT(FIOBJ_TYPE(fiobj_true()) == FIOBJ_T_TRUE, + "fiobj_true() type should be FIOBJ_T_TRUE."); + FIO_ASSERT(FIOBJ_TYPE(fiobj_false()) == FIOBJ_T_FALSE, + "fiobj_false() type should be FIOBJ_T_FALSE."); + FIO_ASSERT(FIO_NAME_BL(fiobj, eq)(FIO_NAME(fiobj, FIOBJ___NAME_NULL)(), + FIO_NAME(fiobj, FIOBJ___NAME_NULL)()), + "fiobj_null() should be equal to self."); + FIO_ASSERT(FIO_NAME_BL(fiobj, eq)(fiobj_true(), fiobj_true()), + "fiobj_true() should be equal to self."); + FIO_ASSERT(FIO_NAME_BL(fiobj, eq)(fiobj_false(), fiobj_false()), + "fiobj_false() should be equal to self."); + } + { + fprintf(stderr, "* Testing FIOBJ integers.\n"); + uint8_t allocation_flags = 0; + for (uint8_t bit = 0; bit < (sizeof(intptr_t) * 8) - 4; ++bit) { + uintptr_t i = ((uintptr_t)1 << bit) + 1; + uintptr_t m = (uintptr_t)0 - i; + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)((intptr_t)i); + FIO_ASSERT(FIOBJ_TYPE_CLASS(o) == FIOBJ_T_NUMBER, + "FIOBJ integer allocation wasn't supposed to happen for %zd", + (size_t)i); + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)((intptr_t)m); + FIO_ASSERT(FIOBJ_TYPE_CLASS(o) == FIOBJ_T_NUMBER, + "FIOBJ integer allocation wasn't supposed to happen for %zd", + (size_t)m); + } + for (uint8_t bit = 0; bit < (sizeof(intptr_t) * 8); ++bit) { + uintptr_t i = (uintptr_t)1 << bit; + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)((intptr_t)i); + FIO_ASSERT(FIO_NAME2(fiobj, i)(o) == (intptr_t)i, + "Number not reversible at bit %d (%zd != %zd)!", + (int)bit, + (ssize_t)FIO_NAME2(fiobj, i)(o), + (ssize_t)i); + fio_str_info_s str = FIO_NAME2(fiobj, cstr)(o); + char *str_buf = str.buf; + FIO_ASSERT(fio_atol(&str_buf) == (intptr_t)i, + "Number atol not reversible at bit %d (%s != %zd)!", + (int)bit, + str.buf, + (ssize_t)i); + allocation_flags |= (FIOBJ_TYPE_CLASS(o) == FIOBJ_T_NUMBER) ? 1 : 2; + fiobj_free(o); + } + FIO_ASSERT(allocation_flags == 3, + "no bits are allocated / no allocations optimized away (%d)", + (int)allocation_flags); + } + { + fprintf(stderr, "* Testing FIOBJ floats.\n"); + uint8_t allocation_flags = 0; + for (uint8_t bit = 0; bit < (sizeof(double) * 8); ++bit) { + union { + double d; + uint64_t i; + } punned; + punned.i = (uint64_t)1 << bit; + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(punned.d); + FIO_ASSERT(FIO_NAME2(fiobj, f)(o) == punned.d, + "Float not reversible at bit %d (%lf != %lf)!", + (int)bit, + FIO_NAME2(fiobj, f)(o), + punned.d); + + fio_str_info_s str = FIO_NAME2(fiobj, cstr)(o); + char buf_tmp[32]; + FIO_ASSERT(fio_ftoa(buf_tmp, FIO_NAME2(fiobj, f)(o), 10) == str.len, + "fio_atof length didn't match Float's fiobj2cstr length."); + FIO_ASSERT(!memcmp(str.buf, buf_tmp, str.len), + "fio_atof string didn't match Float's fiobj2cstr."); + allocation_flags |= (FIOBJ_TYPE_CLASS(o) == FIOBJ_T_FLOAT) ? 1 : 2; + fiobj_free(o); + } + FIO_ASSERT(allocation_flags == 3, + "no bits are allocated / no allocations optimized away (%d)", + (int)allocation_flags); + } + { + fprintf(stderr, "* Testing FIOBJ each2.\n"); + FIOBJ a = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(o, a); + for (size_t i = 1; i < 10; ++i) // 1, 2, 3 ... 10 + { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(i)); + if (i % 3 == 0) { + a = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(o, a); + } + } + int expectation[] = + {-1 /* array */, -1, 1, 2, 3, -1, 4, 5, 6, -1, 7, 8, 9, -1}; + size_t c = + fiobj_each2(o, FIO_NAME_TEST(stl, fiobj_task), (void *)expectation); + FIO_ASSERT(c == FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o) + + 9 + 1, + "each2 repetition count error"); + fiobj_free(o); + FIO_NAME_TEST(stl, fiobj_task)(NULL); + } + { + fprintf(stderr, "* Testing FIOBJ JSON handling.\n"); + char json[] = + " " + "\n# comment 1" + "\n// comment 2" + "\n/* comment 3 */" + "{\"true\":true,\"false\":false,\"null\":null,\"array\":[1,2,3,4.2," + "\"five\"]," + "\"string\":\"hello\\tjson\\bworld!\\r\\n\",\"hash\":{\"true\":true," + "\"false\":false},\"array2\":[1,2,3,4.2,\"five\",{\"hash\":true},[{" + "\"hash\":{\"true\":true}}]]}"; + o = fiobj_json_parse2(json, FIO_STRLEN(json), NULL); + FIO_ASSERT(o, "JSON parsing failed - no data returned."); + FIO_ASSERT(fiobj_json_find2(o, (char *)"array2[6][0].hash.true", 22) == + fiobj_true(), + "fiobj_json_find2 failed"); + FIOBJ j = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, o, 0); +#ifdef DEBUG + fprintf(stderr, "JSON: %s\n", FIO_NAME2(fiobj, cstr)(j).buf); +#endif + FIO_ASSERT(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(j) == + FIO_STRLEN(json + 61), + "JSON roundtrip failed (length error %zu != %zu).\n%s\n%s", + (size_t)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(j), + (size_t)FIO_STRLEN(json + 61), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(j), + json + 61); + FIO_ASSERT(!memcmp(json + 61, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(j), + FIO_STRLEN(json + 61)), + "JSON roundtrip failed (data error)."); + fiobj_free(o); + fiobj_free(j); + o = FIOBJ_INVALID; + } + { + fprintf(stderr, "* Testing FIOBJ array equality test (fiobj_is_eq).\n"); + FIOBJ a1 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIOBJ a2 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIOBJ n1 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIOBJ n2 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a1, fiobj_null()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a2, fiobj_null()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(n1, fiobj_true()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(n2, fiobj_true()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a1, n1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a2, n2); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a1, FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new_cstr)("test", 4)); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a2, FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new_cstr)("test", 4)); + FIO_ASSERT(FIO_NAME_BL(fiobj, eq)(a1, a2), "equal arrays aren't equal?"); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(n1, fiobj_null()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(n2, fiobj_false()); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(a1, a2), "unequal arrays are equal?"); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), remove)(n1, -1, NULL); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), remove)(n2, -1, NULL); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), remove)(a1, 0, NULL); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), remove)(a2, -1, NULL); + FIO_ASSERT(!FIO_NAME_BL(fiobj, eq)(a1, a2), "unequal arrays are equal?"); + fiobj_free(a1); + fiobj_free(a2); + } + { + fprintf(stderr, "* Testing FIOBJ array ownership.\n"); + FIOBJ a = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + for (size_t i = 1; i <= FIOBJ_TEST_REPETITIONS; ++i) { + FIOBJ tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_cstr)("number: ", 8); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i)(tmp, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a, tmp); + } + FIOBJ shifted = FIOBJ_INVALID; + FIOBJ popped = FIOBJ_INVALID; + FIOBJ removed = FIOBJ_INVALID; + FIOBJ set = FIOBJ_INVALID; + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), shift)(a, &shifted); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), pop)(a, &popped); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), set) + (a, 1, fiobj_true(), &set); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), remove)(a, 2, &removed); + fiobj_free(a); + if (1) { + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(popped) == + FIO_STRLEN( + "number: " FIO_MACRO2STR(FIOBJ_TEST_REPETITIONS)) && + !memcmp( + "number: " FIO_MACRO2STR(FIOBJ_TEST_REPETITIONS), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(popped), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(popped)), + "Object popped from Array lost it's value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(popped)); + FIO_ASSERT(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(shifted) == + 9 && + !memcmp("number: 1", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + ptr)(shifted), + 9), + "Object shifted from Array lost it's value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(shifted)); + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(set) == 9 && + !memcmp("number: 3", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(set), + 9), + "Object retrieved from Array using fiobj_array_set() lost it's " + "value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(set)); + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(removed) == 9 && + !memcmp( + "number: 4", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(removed), + 9), + "Object retrieved from Array using fiobj_array_set() lost it's " + "value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(removed)); + } + fiobj_free(shifted); + fiobj_free(popped); + fiobj_free(set); + fiobj_free(removed); + } + { + fprintf(stderr, "* Testing FIOBJ array ownership after concat.\n"); + FIOBJ a1, a2; + a1 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + a2 = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + for (size_t i = 0; i < FIOBJ_TEST_REPETITIONS; ++i) { + FIOBJ str = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i)(str, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a1, str); + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), concat)(a2, a1); + fiobj_free(a1); + for (size_t i = 0; i < FIOBJ_TEST_REPETITIONS; ++i) { + FIOBJ_STR_TEMP_VAR(tmp); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i)(tmp, i); + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(a2, i)) == + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(tmp), + "string length zeroed out - string freed?"); + FIO_ASSERT( + !memcmp( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(tmp), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(a2, i)), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(tmp)), + "string data error - string freed?"); + FIOBJ_STR_TEMP_DESTROY(tmp); + } + fiobj_free(a2); + } + { + fprintf(stderr, "* Testing FIOBJ hash ownership.\n"); + o = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + for (size_t i = 1; i <= FIOBJ_TEST_REPETITIONS; ++i) { + FIOBJ tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_cstr)("number: ", 8); + FIOBJ k = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i)(tmp, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) + (o, k, fiobj_dup(tmp), NULL); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set_if_missing)(o, k, tmp); + fiobj_free(k); + } + + FIOBJ set = FIOBJ_INVALID; + FIOBJ removed = FIOBJ_INVALID; + FIOBJ k = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(o, k, &removed); + fiobj_free(k); + k = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(2); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set)(o, k, fiobj_true(), &set); + fiobj_free(k); + FIO_ASSERT(set, "fiobj_hash_set didn't copy information to old pointer?"); + FIO_ASSERT(removed, + "fiobj_hash_remove didn't copy information to old pointer?"); + // fiobj_hash_set(o, uintptr_t hash, FIOBJ key, FIOBJ value, FIOBJ *old) + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(removed) == + FIO_STRLEN("number: 1") && + !memcmp( + "number: 1", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(removed), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(removed)), + "Object removed from Hash lost it's value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(removed)); + FIO_ASSERT( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(set) == + FIO_STRLEN("number: 2") && + !memcmp("number: 2", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(set), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(set)), + "Object removed from Hash lost it's value %s", + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(set)); + + fiobj_free(removed); + fiobj_free(set); + fiobj_free(o); + } + +#if FIOBJ_MARK_MEMORY + { + fprintf(stderr, "* Testing FIOBJ for memory leaks.\n"); + FIOBJ a = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), reserve)(a, 64); + for (uint8_t bit = 0; bit < (sizeof(intptr_t) * 8); ++bit) { + uintptr_t i = (uintptr_t)1 << bit; + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)((intptr_t)i)); + } + FIOBJ h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + FIOBJ key = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(key, "array", 5); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set)(h, key, a); + FIO_ASSERT(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(h, key) == a, + "FIOBJ Hash retrieval failed"); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(a, key); + if (0) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, FIO_NAME(fiobj, FIOBJ___NAME_NULL)()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, fiobj_true()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, fiobj_false()); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push) + (a, FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(0.42)); + + FIOBJ json = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, h, 0); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(json, "\n", 1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), reserve) + (json, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(json) + << 1); /* prevent memory realloc */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) + (json, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), ptr)(json), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(json) - 1); + fprintf(stderr, "%s\n", FIO_NAME2(fiobj, cstr)(json).buf); + fiobj_free(json); + } + fiobj_free(h); + FIOBJ_MARK_MEMORY_PRINT(); + FIO_ASSERT(FIOBJ_MARK_MEMORY_ALLOC_COUNTER == + FIOBJ_MARK_MEMORY_FREE_COUNTER, + "FIOBJ leak detected (freed %zu/%zu)", + FIOBJ_MARK_MEMORY_FREE_COUNTER, + FIOBJ_MARK_MEMORY_ALLOC_COUNTER); + } +#endif + fprintf(stderr, "* Passed.\n"); +} +#undef FIOBJ_TEST_REPETITIONS +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_GLOB_MATCH Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_GLOB_MATCH_TEST___H) +#define H___FIO_GLOB_MATCH_TEST___H + +#ifndef H___FIO_GLOB_MATCH___H +#define FIO_GLOB_MATCH +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, glob_matching)(void) { + struct { + char *pat; + char *str; + uint8_t expect; + } t[] = { + // clang-format off + /* test empty string */ + {.pat = (char *)"", .str = (char *)"", .expect = 1}, + /* test exact match */ + {.pat = (char *)"a", .str = (char *)"a", .expect = 1}, + /* test empty pattern */ + {.pat = (char *)"", .str = (char *)"a", .expect = 0}, + /* test longer pattern */ + {.pat = (char *)"a", .str = (char *)"", .expect = 0}, + /* test empty string with glob pattern */ + {.pat = (char *)"*", .str = (char *)"", .expect = 1}, + /* test glob pattern */ + {.pat = (char *)"*", .str = (char *)"Whatever", .expect = 1}, + /* test glob pattern at end */ + {.pat = (char *)"W*", .str = (char *)"Whatever", .expect = 1}, + /* test glob pattern as bookends */ + {.pat = (char *)"*Whatever*", .str = (char *)"Whatever", .expect = 1}, + /* test glob pattern in the middle */ + {.pat = (char *)"W*er", .str = (char *)"Whatever", .expect = 1}, + /* test glob pattern in the middle - empty match*/ + {.pat = (char *)"W*hatever", .str = (char *)"Whatever", .expect = 1}, + /* test glob pattern in the middle - no match */ + {.pat = (char *)"W*htever", .str = (char *)"Whatever", .expect = 0}, + /* test partial match with glob at end */ + {.pat = (char *)"h*", .str = (char *)"Whatever", .expect = 0}, + /* test partial match with glob in the middle */ + {.pat = (char *)"h*er", .str = (char *)"Whatever", .expect = 0}, + /* test glob match with "?" */ + {.pat = (char *)"?h*er", .str = (char *)"Whatever", .expect = 1}, + /* test "?" for length restrictions */ + {.pat = (char *)"?", .str = (char *)"Whatever", .expect = 0}, + /* test ? in the middle */ + {.pat = (char *)"What?ver", .str = (char *)"Whatever", .expect = 1}, + /* test letter list */ + {.pat = (char *)"[ASW]hat?ver", .str = (char *)"Whatever", .expect = 1}, + /* test letter range */ + {.pat = (char *)"[A-Z]hat?ver", .str = (char *)"Whatever", .expect = 1}, + /* test letter range (fail) */ + {.pat = (char *)"[a-z]hat?ver", .str = (char *)"Whatever", .expect = 0}, + /* test inverted letter range */ + {.pat = (char *)"[!a-z]hat?ver", .str = (char *)"Whatever", .expect = 1}, + /* test inverted list */ + {.pat = (char *)"[!F]hat?ver", .str = (char *)"Whatever", .expect = 1}, + /* test escaped range */ + {.pat = (char *)"[!a-z\\]]hat?ver", .str = (char *)"Whatever", .expect = 1}, + /* test "?" after range (no skip) */ + {.pat = (char *)"[A-Z]?at?ver", .str = (char *)"Whatever", .expect = 1}, + /* test error after range (no skip) */ + {.pat = (char *)"[A-Z]Fat?ver", .str = (char *)"Whatever", .expect = 0}, + /* end of test marker */ + {.pat = (char *)NULL, .str = (char *)NULL, .expect = 0}, + // clang-format on + }; + fprintf(stderr, "* Testing glob matching.\n"); + for (size_t i = 0; t[i].pat; ++i) { + fio_str_info_s p = FIO_STR_INFO1(t[i].pat); + fio_str_info_s s = FIO_STR_INFO1(t[i].str); + FIO_ASSERT(t[i].expect == fio_glob_match(p, s), + "glob matching error for:\n\t String: %s\n\t Pattern: %s", + s.buf, + p.buf); + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + fio_http_s Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_HTTP_HANDLE_TEST___H) +#define H___FIO_HTTP_HANDLE_TEST___H +// #ifndef H___FIO_MODULE_NAME___H +// #define FIO_MODULE_NAME +// #define FIO___TEST_REINCLUDE +// #include FIO_INCLUDE_FILE +// #undef FIO___TEST_REINCLUDE +// #endif + +FIO_SFUNC void FIO_NAME_TEST(stl, http_s)(void) { + fprintf(stderr, "* Testing HTTP handle (fio_http_s).\n"); + fio_http_s *h = fio_http_new(); + FIO_ASSERT(!fio_http_cdata(h), "fio_http_cdata should start as NULL"); + fio_http_cdata_set(h, (void *)(uintptr_t)42); + FIO_ASSERT((uintptr_t)fio_http_cdata(h) == 42, + "fio_http_cdata roundtrip error"); + FIO_ASSERT(!fio_http_udata(h), "fio_http_udata should start as NULL"); + fio_http_udata_set(h, (void *)(uintptr_t)43); + FIO_ASSERT((uintptr_t)fio_http_udata(h) == 43, + "fio_http_udata roundtrip error"); + FIO_ASSERT(!fio_http_udata2(h), "fio_http_udata2 should start as NULL"); + fio_http_udata2_set(h, (void *)(uintptr_t)44); + FIO_ASSERT((uintptr_t)fio_http_udata2(h) == 44, + "fio_http_udata2 roundtrip error"); + + FIO_ASSERT(!fio_http_status(h), "fio_http_status should start as NULL"); + fio_http_status_set(h, 101); + FIO_ASSERT((uintptr_t)fio_http_status(h) == 101, + "fio_http_status roundtrip error"); + + FIO_ASSERT(!fio_http_method(h).buf, "fio_http_method should start as empty"); + fio_http_method_set(h, FIO_STR_INFO1((char *)"POST")); + FIO_ASSERT( + FIO_STR_INFO_IS_EQ(fio_http_method(h), FIO_STR_INFO1((char *)"POST")), + "fio_http_method roundtrip error"); + + FIO_ASSERT(!fio_http_path(h).buf, "fio_http_path should start as empty"); + fio_http_path_set(h, FIO_STR_INFO1((char *)"/path")); + FIO_ASSERT( + FIO_STR_INFO_IS_EQ(fio_http_path(h), FIO_STR_INFO1((char *)"/path")), + "fio_http_path roundtrip error"); + + FIO_ASSERT(!fio_http_query(h).buf, "fio_http_query should start as empty"); + fio_http_query_set(h, FIO_STR_INFO1((char *)"query=null")); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(fio_http_query(h), + FIO_STR_INFO1((char *)"query=null")), + "fio_http_query roundtrip error"); + + FIO_ASSERT(!fio_http_version(h).buf, + "fio_http_version should start as empty"); + fio_http_version_set(h, FIO_STR_INFO1((char *)"HTTP/1.1")); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(fio_http_version(h), + FIO_STR_INFO1((char *)"HTTP/1.1")), + "fio_http_version roundtrip error"); + + { /* test multiple header support */ + fio_str_info_s test_data[] = { + FIO_STR_INFO1((char *)"header-name"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 001"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 002"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 003"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 004"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 005"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 006"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 007"), + FIO_STR_INFO1( + (char *)"a long enough value to require memory allocation 008"), + }; + size_t count = sizeof(test_data) / sizeof(test_data[0]); + FIO_ASSERT(!fio_http_request_header(h, test_data[0], 0).buf, + "fio_http_request_header should start as empty"); + FIO_ASSERT(!fio_http_response_header(h, test_data[0], 0).buf, + "fio_http_response_header should start as empty"); + for (size_t i = 1; i < count; ++i) { + FIO_ASSERT(!fio_http_request_header(h, test_data[0], i - 1ULL).buf, + "fio_http_request_header index (%zu) should start as empty", + (size_t)(i - 1ULL)); + FIO_ASSERT(!fio_http_response_header(h, test_data[0], i - 1ULL).buf, + "fio_http_response_header index (%zu) should start as empty", + (size_t)(i - 1ULL)); + fio_str_info_s req_h = + fio_http_request_header_add(h, test_data[0], test_data[i]); + fio_str_info_s res_h = + fio_http_response_header_add(h, test_data[0], test_data[i]); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(req_h, test_data[i]), + "fio_http_request_header_set error"); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(res_h, test_data[i]), + "fio_http_response_header_set error"); + req_h = fio_http_request_header(h, test_data[0], i - 1ULL); + res_h = fio_http_response_header(h, test_data[0], i - 1ULL); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(req_h, test_data[i]), + "fio_http_request_header_set error"); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(res_h, test_data[i]), + "fio_http_response_header_set error"); + } + for (size_t i = 0; i < count - 1; ++i) { + fio_str_info_s req_h = fio_http_request_header(h, test_data[0], i); + fio_str_info_s res_h = fio_http_response_header(h, test_data[0], i); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(req_h, test_data[i + 1]), + "fio_http_request_header_set error"); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(res_h, test_data[i + 1]), + "fio_http_response_header_set error"); + } + fio_http_request_header_set(h, test_data[0], test_data[1]); + fio_http_response_header_set(h, test_data[0], test_data[1]); + FIO_ASSERT(!fio_http_request_header(h, test_data[0], 1ULL).buf, + "fio_http_request_header_set index should reset header values"); + FIO_ASSERT(!fio_http_response_header(h, test_data[0], 1ULL).buf, + "fio_http_response_header_set index should reset header values"); + } + { /* test body writer */ + size_t written = 0; + do { + union { + char buf[32]; + uint64_t u64[4]; + void *p[4]; + } w, r; + fio_rand_bytes(r.buf, sizeof(r.buf)); + fio_http_body_write(h, r.buf, sizeof(r.buf)); + fio_http_body_seek(h, written); + fio_str_info_s got = fio_http_body_read(h, sizeof(r.buf)); + FIO_MEMSET(w.buf, 0, sizeof(w.buf)); + FIO_MEMCPY(w.buf, got.buf, got.len); + written += sizeof(r.buf); + FIO_ASSERT(written == fio_http_body_length(h), + "fio_http_body_length error (%zu != %zu)", + fio_http_body_length(h), + written); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(FIO_STR_INFO2(r.buf, sizeof(r.buf)), got), + "fio_http_body_write-fio_http_body_read roundtrip error @ %zu" + "\n\t expected (32):\t%p%p%p%p)" + "\n\t got (%zu): \t%p%p%p%p)", + written - sizeof(r.buf), + r.p[0], + r.p[1], + r.p[2], + r.p[3], + got.len, + w.p[0], + w.p[1], + w.p[2], + w.p[3]); + } while (written < (FIO_HTTP_BODY_RAM_LIMIT << 1)); + fio_http_body_seek(h, 0); + fio_http_body_write(h, "\n1234", 5); + fio_str_info_s ln = fio_http_body_read_until(h, '\n', 0); + FIO_ASSERT(ln.buf && ln.len && ln.buf[ln.len - 1] == '\n', + "fio_http_body_read_until token error"); + } + + /* almost done, just make sure reference counting doesn't destroy object */ + fio_http_free(fio_http_dup(h)); + FIO_ASSERT( + (uintptr_t)fio_http_udata2(h) == 44 && + FIO_STR_INFO_IS_EQ(fio_http_method(h), FIO_STR_INFO1((char *)"POST")), + "fio_http_s reference counting shouldn't object"); + + fio_http_free(h); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_IMAP_CORE Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_IMAP_CORE_TEST___H) +#define H___FIO_IMAP_CORE_TEST___H + +#ifndef H___FIO_IMAP_CORE___H +#define FIO_IMAP_CORE +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#define FIO_IMAP_TESTER_IMAP_HASH(n) ((n)[0] | ((n)[0] << 9)) +#define FIO_IMAP_TESTER_IMAP_CMP(a, b) (*(a) == *(b)) +#define FIO_IMAP_TESTER_IMAP_VALID(n) ((n)[0]) +FIO_TYPEDEF_IMAP_ARRAY(fio_imap_tester, + size_t, + uint32_t, /* good for up to 65K objects */ + FIO_IMAP_TESTER_IMAP_HASH, + FIO_IMAP_TESTER_IMAP_CMP, + FIO_IMAP_TESTER_IMAP_VALID) + +#undef FIO_IMAP_TESTER_IMAP_HASH +#undef FIO_IMAP_TESTER_IMAP_CMP +#undef FIO_IMAP_TESTER_IMAP_VALID + +FIO_SFUNC void FIO_NAME_TEST(stl, imap_core)(void) { + fprintf(stderr, "* Testing core indexed array type (imap).\n"); + fio_imap_tester_s a = {0}; + fio_imap_tester_reserve(&a, 1024); + FIO_ASSERT(fio_imap_tester_capa(&a) >= 1024 && + fio_imap_tester_capa(&a) < 4096, + "fio_imap_tester_reserve failed"); + for (size_t val = 1; val < 4096; ++val) { + size_t *pobj = fio_imap_tester_set(&a, val, 1); + FIO_ASSERT(a.count == val, "imap array count failed at set %zu!", val); + size_t *ptmp = fio_imap_tester_set(&a, val, 0); + FIO_ASSERT(ptmp == pobj, + "fio_imap_tester_set should return pointer to existing item"); + ptmp = fio_imap_tester_set(&a, val, 0); + FIO_ASSERT(ptmp == pobj, + "fio_imap_tester_set should return pointer to existing item"); + ptmp = fio_imap_tester_set(&a, val, 0); + FIO_ASSERT(ptmp == pobj, + "fio_imap_tester_set should return pointer to existing item"); + FIO_ASSERT(a.count == val, "imap array double-set error %zu!", val); + FIO_ASSERT(fio_imap_tester_get(&a, val) == pobj && + fio_imap_tester_get(&a, val)[0] == val, + "imap array get failed for %zu!", + val); + } + for (size_t val = 1; val < 4096; ++val) { + FIO_ASSERT(fio_imap_tester_get(&a, val) && + fio_imap_tester_get(&a, val)[0] == val, + "imap array get failed for %zu (2)!", + val); + } + for (size_t val = 4096; --val;) { + FIO_ASSERT(fio_imap_tester_get(&a, val) && + fio_imap_tester_get(&a, val)[0] == val, + "imap array get failed for %zu (2)!", + val); + fio_imap_tester_remove(&a, val); + FIO_ASSERT((size_t)(a.count + 1) == val, + "imap array count failed at remove %zu!", + val); + FIO_ASSERT(!fio_imap_tester_get(&a, val), + "imap array get should fail after remove for %zu!", + val); + } + fio_imap_tester_destroy(&a); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Server Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_SERVER_TEST___H) +#define H___FIO_SERVER_TEST___H +#ifndef H___FIO_SERVER___H +#define FIO_SERVER +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif +/* ***************************************************************************** +Test TLS support +***************************************************************************** */ + +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_cert)(fio_io_tls_each_s *e, + const char *nm, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + size_t *result = (size_t *)e->udata2; + *result += 0x01U; + const size_t step = result[0] & 0xFF; + struct { + const char *s[4]; + } d = + { + {nm, public_cert_file, private_key_file, pk_password}, + }, + ex = {{NULL, "cert.pem", "key.pem", "1234"}}; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_cert"); + for (size_t i = 1; i < 4; ++i) { + FIO_ASSERT(d.s[i] && ex.s[i] && FIO_STRLEN(ex.s[i]) == FIO_STRLEN(d.s[i]) && + !memcmp(ex.s[i], d.s[i], FIO_STRLEN(d.s[i])), + "tls_each_cert string error for argument %zu", + i); + } + return 0; +} +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_alpn)(fio_io_tls_each_s *e, + const char *nm, + void (*fn)(fio_io_s *)) { + size_t *result = (size_t *)e->udata2; + *result += 0x0100U; + const size_t step = (result[0] >> 8) & 0xFF; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_alpn"); + FIO_ASSERT((uintptr_t)fn == step, "fn value error for tls_each_alpn"); + return 0; +} +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_trust)(fio_io_tls_each_s *e, + const char *nm) { + + size_t *result = (size_t *)e->udata2; + *result += 0x010000U; + const size_t step = (result[0] >> 16) & 0xFF; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_trust"); + return 0; +} + +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_alpn_cb)(fio_io_s *io) { + ((size_t *)io)[0]++; +} + +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_helpers)(void) { + fprintf(stderr, " * Testing fio_io_tls_s helpers.\n"); + struct { + const char *nm; + const char *public_cert_file; + const char *private_key_file; + const char *pk_password; + } tls_test_cert_data[] = { + { + .nm = "1", + .public_cert_file = "c.pem", + .private_key_file = "k.pem", + .pk_password = NULL, + }, + { + .nm = "2", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + { + .nm = "1", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + { + .nm = "3", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + {NULL}, + }; + struct { + const char *nm; + void (*fn)(fio_io_s *); + } tls_test_alpn_data[] = { + { + .nm = "1", + .fn = (void (*)(fio_io_s *))(uintptr_t)3, + }, + { + .nm = "2", + .fn = (void (*)(fio_io_s *))(uintptr_t)2, + }, + { + .nm = "1", + .fn = (void (*)(fio_io_s *))(uintptr_t)1, + }, + {NULL}, + }; + struct { + const char *nm; + } tls_test_trust_data[] = { + { + .nm = "1", + }, + { + .nm = "2", + }, + {NULL}, + }; + size_t counter = 0; + void *data_containers[] = { + (void *)&tls_test_cert_data, + (void *)&tls_test_alpn_data, + (void *)&tls_test_trust_data, + NULL, + }; + fio_io_tls_s *t = fio_io_tls_new(); + FIO_ASSERT(t, "fio_io_tls_new should return a valid fio_io_tls_s object"); + for (size_t i = 0; tls_test_cert_data[i].nm; ++i) { + fio_io_tls_s *r = + fio_io_tls_cert_add(t, + tls_test_cert_data[i].nm, + tls_test_cert_data[i].public_cert_file, + tls_test_cert_data[i].private_key_file, + tls_test_cert_data[i].pk_password); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + for (size_t i = 0; tls_test_alpn_data[i].nm; ++i) { + fio_io_tls_s *r = fio_io_tls_alpn_add(t, + tls_test_alpn_data[i].nm, + tls_test_alpn_data[i].fn); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + for (size_t i = 0; tls_test_trust_data[i].nm; ++i) { + fio_io_tls_s *r = fio_io_tls_trust_add(t, tls_test_trust_data[i].nm); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + + fio_io_tls_each( + t, + .udata = data_containers, + .udata2 = &counter, + .each_cert = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_cert), + .each_alpn = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_alpn), + .each_trust = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_trust)); + FIO_ASSERT(counter == 0x020203, "fio_io_tls_each iteration count error."); + fio_io_tls_alpn_add(t, + "tst", + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_alpn_cb)); + counter = 0; + fio_io_tls_alpn_select(t, "tst", 3, (fio_io_s *)&counter); + FIO_ASSERT(counter == 1, "fio_io_tls_alpn_select failed."); + fio_io_tls_free(t); + + const struct { + fio_buf_info_s url; + size_t is_tls; + } url_tests[] = { + {FIO_BUF_INFO1((char *)"ws://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"wss://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"sse://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"sses://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"http://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"https://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"tcp://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"tcps://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"udp://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"udps://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"tls://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"ws://ex.com/?TLSN"), 0}, + {FIO_BUF_INFO1((char *)"ws://ex.com/?TLS"), 1}, + {FIO_BUF_INFO0, 0}, + }; + for (size_t i = 0; url_tests[i].url.buf; ++i) { + t = NULL; + fio_url_s u = fio_url_parse(url_tests[i].url.buf, url_tests[i].url.len); + t = fio_io_tls_from_url(t, u); + FIO_ASSERT((!url_tests[i].is_tls && !t) || (url_tests[i].is_tls && t), + "fio_io_tls_from_url result error @ %s", + url_tests[i].url.buf); + fio_io_tls_free(t); + } +} + +/* ***************************************************************************** +Test IO ENV support +***************************************************************************** */ + +/* State callback test task */ +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + env_on_close)(void *udata) { + size_t *p = (size_t *)udata; + ++p[0]; +} + +/* State callback tests */ +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env)(void) { + fprintf(stderr, " * Testing fio_env.\n"); + size_t a = 0, b = 0, c = 0; + fio___io_env_safe_s env = FIO___IO_ENV_SAFE_INIT; + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 1); + FIO_ASSERT(fio___io_env_safe_get(&env, (char *)"a_key", 5, 1) == &a, + "fio___io_env_safe_set/get round-trip error!"); + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 2, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 2); + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 3, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 1); + fio___io_env_safe_set( + &env, + (char *)"b_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &b}, + 1); + fio___io_env_safe_set( + &env, + (char *)"c_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &c}, + 1); + fio___io_env_safe_unset(&env, (char *)"a_key", 5, 3); + FIO_ASSERT(!a, + "unset should have removed an object without calling callback."); + fio___io_env_safe_remove(&env, (char *)"a_key", 5, 3); + FIO_ASSERT(!a, "remove after unset should have no side-effects."); + fio___io_env_safe_remove(&env, (char *)"a_key", 5, 2); + FIO_ASSERT(a == 1, "remove should call callbacks."); + fio___io_env_safe_destroy(&env); + FIO_ASSERT(a == 2 && b == 1 && c == 1, "destroy should call callbacks."); +} + +/* ***************************************************************************** +Test Server Modules +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, io)(void) { + fprintf(stderr, "* Testing fio_io units (TODO).\n"); + FIO_PRINT_SIZE_OF(fio_io_protocol_s); + FIO_PRINT_SIZE_OF(fio_io_s); + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env)(); + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_helpers)(); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Math Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_MATH_TEST___H) +#define H___FIO_MATH_TEST___H +#ifndef H___FIO_MATH___H +#define FIO_MATH +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif +FIO_SFUNC void FIO_NAME_TEST(stl, math)(void) { + fprintf(stderr, + "* Testing multi-precision math operations " + "(partial).\n"); + + { /* Test add/sub carry */ + uint64_t a, c; + a = fio_math_addc64(1ULL, 1ULL, 1ULL, &c); + FIO_ASSERT(a == 3 && c == 0, + "fio_math_addc64(1ULL, 1ULL, 1ULL, &c) failed"); + a = fio_math_addc64(~(uint64_t)0ULL, 1ULL, 0ULL, &c); + FIO_ASSERT(!a && c == 1, + "fio_math_addc64(~(uint64_t)0ULL, 1ULL, " + "0ULL, &c) failed"); + c = 0; + a = fio_math_addc64(~(uint64_t)0ULL, 1ULL, 1ULL, &c); + FIO_ASSERT(a == 1 && c == 1, + "fio_math_addc64(~(uint64_t)0ULL, 1ULL, " + "1ULL, &c) failed"); + c = 0; + a = fio_math_addc64(~(uint64_t)0ULL, 0ULL, 1ULL, &c); + FIO_ASSERT(!a && c == 1, + "fio_math_addc64(~(uint64_t)0ULL, 0ULL, " + "1ULL, &c) failed"); + a = fio_math_subc64(3ULL, 1ULL, 1ULL, &c); + FIO_ASSERT(a == 1 && c == 0, "fio_math_subc64 failed"); + a = fio_math_subc64(~(uint64_t)0ULL, 1ULL, 0ULL, &c); + FIO_ASSERT(c == 0, + "fio_math_subc64(~(uint64_t)0ULL, 1ULL, " + "0ULL, &c) failed"); + a = fio_math_subc64(0ULL, ~(uint64_t)0ULL, 1ULL, &c); + FIO_ASSERT(!a && c == 1, + "fio_math_subc64(0ULL, ~(uint64_t)0ULL, " + "1ULL, &c) failed " + "(%llu, %llu)", + a, + c); + a = fio_math_subc64(0ULL, 1ULL, 0ULL, &c); + FIO_ASSERT(a == ~(uint64_t)0ULL && c == 1, + "fio_math_subc64(0ULL, 1ULL, 0ULL, &c) failed"); + } + + { /* Test division */ + uint64_t n = 0, d = 1; + for (size_t i = 0; i < 64; ++i) { + n = (n << 7) ^ 0xAA; + for (size_t j = 0; j < 64; ++j) { + d = (d << 3) ^ 0xAA; + uint64_t q, r; + FIO_COMPILER_GUARD; + fio_math_div(&q, &r, &n, &d, 1); + FIO_COMPILER_GUARD; + FIO_ASSERT(q == (n / d), + "fio_math_div failed quotient for " + "0x%llX / 0x%llX (Q=0x%llX " + "R=0x%llX)", + (long long)n, + (long long)d, + (long long)q, + (long long)r); + FIO_ASSERT((q * d) + r == n, + "fio_math_div failed remainder for " + "0x%llX / 0x%llX (Q=0x%llX " + "R=0x%llX)", + (long long)n, + (long long)d, + (long long)q, + (long long)r); + } + } + } + { /* Test bit shifting */ + uint64_t a[] = {0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0}; + uint64_t b[] = {0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 1}; + uint64_t c[3]; + fio_math_shl(c, a, 1, 3); + FIO_ASSERT(!memcmp(b, c, sizeof(c)), + "left shift failed, %llX:%llX:%llX", + c[0], + c[1], + c[2]); + fio_math_shr(c, c, 1, 3); + FIO_ASSERT(!memcmp(a, c, sizeof(c)), + "right shift failed, %llX:%llX:%llX", + c[0], + c[1], + c[2]); + fio_math_shl(c, a, 128, 3); + FIO_ASSERT(!c[0] && !c[1] && !(~c[2]), + "left shift failed, %llX:%llX:%llX", + c[0], + c[1], + c[2]); + FIO_ASSERT(fio_math_msb_index(a, 3) == 127, + "fio_math_msb_index(a) failed %zu", + fio_math_msb_index(a, 3)); + FIO_ASSERT(fio_math_lsb_index(a, 3) == 0, + "fio_math_lsb_index(a) failed %zu", + fio_math_lsb_index(a, 3)); + FIO_ASSERT(fio_math_msb_index(b, 3) == 128, + "fio_math_msb_index(b) failed %zu", + fio_math_msb_index(b, 3)); + FIO_ASSERT(fio_math_lsb_index(b, 3) == 1, + "fio_math_lsb_index(b) failed %zu", + fio_math_lsb_index(b, 3)); + } + { /* Test vectors (partial) */ + fio_u128 v128 = {{0}}; + fio_u256 v256 = {{0}}; + fio_u512 v512 = {{0}}; +#define FIO_VTEST_ACT_CONST(opt, val) \ + fio_u128_c##opt##64(&v128, &v128, val); \ + fio_u256_c##opt##64(&v256, &v256, val); \ + fio_u512_c##opt##64(&v512, &v512, val); +#define FIO_VTEST_ACT(opt, val) \ + fio_u128_##opt##64(&v128, &v128, &((fio_u128){.u64 = {val, val}})); \ + fio_u256_##opt##64(&v256, \ + &v256, \ + &((fio_u256){.u64 = {val, val, val, val}})); \ + fio_u512_##opt##64( \ + &v512, \ + &v512, \ + &((fio_u512){.u64 = {val, val, val, val, val, val, val, val}})); +#define FIO_VTEST_ACT_BIG(opt, val) \ + fio_u128_c##opt##64(&v128, &v128, val); \ + fio_u256_##opt(&v256, &v256, &((fio_u256){.u64 = {val, val, val, val}})); \ + fio_u512_c##opt##64(&v512, &v512, val); + +#define FIO_VTEST_IS_EQ(val) \ + (v128.u64[0] == val && v128.u64[1] == val && v256.u64[0] == val && \ + v256.u64[1] == val && v256.u64[2] == val && v256.u64[3] == val && \ + v512.u64[0] == val && v512.u64[1] == val && v512.u64[2] == val && \ + v512.u64[3] == val && v512.u64[4] == val && v512.u64[5] == val && \ + v512.u64[6] == val && v512.u64[7] == val) + + FIO_VTEST_ACT_CONST(add, 1); + FIO_VTEST_ACT_CONST(mul, 31); + FIO_VTEST_ACT_BIG(and, 15); + FIO_ASSERT(FIO_VTEST_IS_EQ(15), + "fio_u128 / fio_u256 / fio_u512 failed " + "with constant vec. operations"); + FIO_VTEST_ACT(sub, 15); + FIO_VTEST_ACT(add, 1); + FIO_VTEST_ACT(mul, 31); + FIO_VTEST_ACT_BIG(and, 15); + FIO_ASSERT(FIO_VTEST_IS_EQ(15), + "fio_u128 / fio_u256 / fio_u512 failed " + "with vector operations"); + FIO_ASSERT(fio_u128_reduce_add64(&v128) == 30 && + fio_u256_reduce_add64(&v256) == 60 && + fio_u512_reduce_add64(&v512) == 120, + "fio_u128 / fio_u256 / fio_u512 reduce " + "(add) failed"); + FIO_ASSERT(FIO_VTEST_IS_EQ(15), " reduce had side-effects!"); + + fio_u256_add64(&v256, &v256, &(fio_u256){.u64 = {1, 2, 3, 0}}); + FIO_ASSERT(v256.u64[0] == 16 && v256.u64[1] == 17 && v256.u64[2] == 18 && + v256.u64[3] == 15, + "fio_u256_add64 failed"); + // v256 = fio_u256_shuffle64(v256, 3, 0, 1, 2); + // FIO_ASSERT(v256.u64[0] == 15 && v256.u64[1] == 16 && v256.u64[2] == 17 && + // v256.u64[3] == 18, + // "fio_u256_shuffle64 failed"); + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_MEMALT Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_MEMALT_TEST___H) +#define H___FIO_MEMALT_TEST___H + +#ifndef H___FIO_MEMALT___H +#define FIO_MEMALT +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, memalt)(void) { + uint64_t start, end; + fprintf(stderr, "* Testing memcpy, memchr and memset alternatives.\n"); + + { /* test fio_memcpy possible overflow. */ + uint64_t buf1[64]; + uint8_t *buf = (uint8_t *)buf1; + fio_memset(buf1, ~(uint64_t)0, sizeof(*buf1) * 64); + char *data = + (char *)"This should be an uneven amount of characters, say 53"; + fio_memcpy(buf, data, FIO_STRLEN(data)); + FIO_ASSERT(!memcmp(buf, data, FIO_STRLEN(data)) && + buf[FIO_STRLEN(data)] == 0xFF, + "fio_memcpy should not overflow or underflow on uneven " + "amounts of bytes."); + } + { /* test fio_memcpy as memmove */ + fprintf(stderr, "* testing fio_memcpy with overlapping memory (memmove)\n"); + char *msg = (char *)"fio_memcpy should work also as memmove, " + "so undefined behavior should not occur. " + "Should be true for larger offsets too. At least over " + "128 Bytes."; + size_t len = FIO_STRLEN(msg); + char buf[512]; + for (size_t offset = 1; offset < len; ++offset) { + memset(buf, 0, sizeof(buf)); + memmove(buf, msg, len); + fio_memcpy(buf + offset, buf, len); + FIO_ASSERT(!memcmp(buf + offset, msg, len), + "fio_memcpy failed on overlapping data (offset +%d, len %zu)", + offset, + len); + memset(buf, 0, sizeof(buf)); + memmove(buf + offset, msg, len); + fio_memcpy(buf, buf + offset, len); + FIO_ASSERT(!memcmp(buf, msg, len), + "fio_memcpy failed on overlapping data (offset -%d, len %zu)", + offset, + len); + } + } + { /* test fio_memcmp */ + for (size_t i = 0; i < 4096; ++i) { + uint64_t a = fio_rand64(), b = fio_rand64(); + int s = memcmp(&a, &b, sizeof(a)); + int f = fio_memcmp(&a, &b, sizeof(a)); + FIO_ASSERT((s < 0 && f < 0) || (s > 0 && f > 0) || (!s && !f), + "fio_memcmp != memcmp (result meaning, not value)."); + FIO_ASSERT(fio_ct_is_eq(&a, &b, sizeof(a)) == (!s), + "fio_ct_is_eq differs from memcmp result"); + } + } + { /* test fio_memchr and fio_strlen */ + char membuf[4096]; + memset(membuf, 0xff, 4096); + membuf[4095] = 0; + for (size_t i = 0; i < 4095; ++i) { + membuf[i] = 0; + char *result = (char *)fio_memchr(membuf, 0, 4096); + size_t len = fio_strlen(membuf); + membuf[i] = (char)((i & 0xFFU) | 1U); + FIO_ASSERT(result == membuf + i, "fio_memchr failed."); + FIO_ASSERT(len == i, "fio_strlen failed (%zu != %zu).", len, i); + } + } +#ifndef DEBUG + const size_t base_repetitions = 8192; + fprintf(stderr, "* Speed testing core memcpy primitives:\n"); + { + struct { + void *(*fn)(void *, const void *); + size_t bytes; + } tests[] = { + {fio_memcpy8, 8}, + {fio_memcpy16, 16}, + {fio_memcpy32, 32}, + {fio_memcpy64, 64}, + {fio_memcpy128, 128}, + {fio_memcpy256, 256}, + {fio_memcpy512, 512}, + {fio_memcpy1024, 1024}, + {fio_memcpy2048, 2048}, + {fio_memcpy4096, 4096}, + {NULL}, + }; + char buf[4096 * 2]; + memset(buf, 0x80, 4096 * 2); + for (size_t i = 0; tests[i].bytes; ++i) { + start = fio_time_micro(); + for (size_t r = 0; r < (base_repetitions << 4); ++r) { + tests[i].fn(buf, buf + 4096); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, + "\tfio_memcpy%zu\tmemcpy(a,b,%zu) \t%zuus\t", + tests[i].bytes, + tests[i].bytes, + (size_t)(end - start)); + start = fio_time_micro(); + for (size_t r = 0; r < (base_repetitions << 4); ++r) { + memcpy(buf, buf + 4096, tests[i].bytes); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, "%zuus\n", (size_t)(end - start)); + } + } + { + fprintf(stderr, "\n"); + struct { + void *(*fn)(void *, const void *, size_t); + size_t bytes; + } tests[] = { + {fio_memcpy7x, 7}, + {fio_memcpy15x, 15}, + {fio_memcpy31x, 31}, + {fio_memcpy63x, 63}, + {fio_memcpy127x, 127}, + {fio_memcpy255x, 255}, + {fio_memcpy511x, 511}, + {fio_memcpy1023x, 1023}, + {fio_memcpy2047x, 2047}, + {fio_memcpy4095x, 4095}, + {NULL}, + }; + char buf[4096 * 2]; + memset(buf, 0x80, 4096 * 2); + for (size_t i = 0; tests[i].bytes; ++i) { + start = fio_time_micro(); + for (size_t r = 0; r < (base_repetitions << 4); ++r) { + tests[i].fn(buf, buf + 4096, ((tests[i].bytes + r) & tests[i].bytes)); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, + "\tfio_memcpy%zux\tmemcpy(a,b,%zu) \t%zuus\t", + tests[i].bytes, + tests[i].bytes, + (size_t)(end - start)); + start = fio_time_micro(); + for (size_t r = 0; r < (base_repetitions << 4); ++r) { + memcpy(buf, buf + 4096, ((tests[i].bytes + r) & tests[i].bytes)); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, "%zuus\n", (size_t)(end - start)); + } + } + fprintf(stderr, "* Speed testing memset:\n"); + + for (size_t len_i = 5; len_i < 20; ++len_i) { + const size_t repetitions = base_repetitions + << (len_i < 15 ? (15 - (len_i & 15)) : 0); + const size_t mem_len = (1ULL << len_i); + void *mem = malloc(mem_len + 32); + FIO_ASSERT_ALLOC(mem); + uint64_t sig = (uintptr_t)mem; + sig ^= sig >> 13; + sig ^= sig << 17; + sig ^= sig << 29; + sig ^= sig << 31; + for (size_t rlen = mem_len - 1; rlen < mem_len + 2; ++rlen) { + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + memset(mem, (int)sig, rlen); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, + "\tsystem memset\t(%zu bytes):\t%zuus\t/ %zu\n", + rlen, + (size_t)(end - start), + repetitions); + + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + fio_memset(mem, sig, rlen); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fio___memset_test_aligned(mem, + sig, + rlen, + "fio_memset sanity test FAILED"); + fprintf(stderr, + "\tfio_memset\t(%zu bytes):\t%zuus\t/ %zu\n", + rlen, + (size_t)(end - start), + repetitions); + } + free(mem); + } + + fprintf(stderr, "* Speed testing memcpy:\n"); + + for (int len_i = 5; len_i < 21; ++len_i) { + const size_t repetitions = base_repetitions + << (len_i < 15 ? (15 - (len_i & 15)) : 0); + for (size_t mem_len = (1ULL << len_i) - 1; mem_len <= (1ULL << len_i) + 1; + ++mem_len) { + void *mem = malloc(mem_len << 1); + FIO_ASSERT_ALLOC(mem); + uint64_t sig = (uintptr_t)mem; + sig ^= sig >> 13; + sig ^= sig << 17; + sig ^= sig << 29; + sig ^= sig << 31; + fio_memset(mem, sig, mem_len); + + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + memcpy((char *)mem + mem_len, mem, mem_len); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, + "\tsystem memcpy\t(%zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + fio_memcpy((char *)mem + mem_len, mem, mem_len); + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fio___memset_test_aligned((char *)mem + mem_len, + sig, + mem_len, + "fio_memcpy sanity test FAILED"); + fprintf(stderr, + "\tfio_memcpy\t(%zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + // size_t threads_used = 0; + // start = fio_time_micro(); + // for (size_t i = 0; i < repetitions; ++i) { + // threads_used = fio_thread_memcpy((char *)mem + mem_len, mem, + // mem_len); if (threads_used == 1) + // break; + // FIO_COMPILER_GUARD; + // } + // end = fio_time_micro(); + // fio___memset_test_aligned((char *)mem + mem_len, + // sig, + // mem_len, + // "fio_thread_memcpy sanity test FAILED"); + // fprintf(stderr, + // " fio_thread_memcpy (%zut)\t(%zu bytes):\t%zu" + // "us\t/ %zu\n", threads_used, mem_len, (size_t)(end + // - start), repetitions); + + free(mem); + } + } + + fprintf(stderr, "* Speed testing memchr:\n"); + + for (int len_i = 2; len_i < 20; ++len_i) { + const size_t repetitions = base_repetitions + << (len_i < 15 ? (15 - (len_i & 15)) : 0); + const size_t mem_len = (1ULL << len_i) - 1; + size_t token_index = ((mem_len >> 1) + (mem_len >> 2)) + 1; + void *mem = malloc(mem_len + 2); + FIO_ASSERT_ALLOC(mem); + fio_memset(mem, ((uint64_t)0x0101010101010101ULL * 0x80), mem_len + 1); + ((uint8_t *)mem)[mem_len + 1] = 0; + ((uint8_t *)mem)[token_index >> 1] = 0xFFU; /* edge case? */ + ((uint8_t *)mem)[(token_index >> 1) + 1] = 0x01U; /* edge case? */ + ((uint8_t *)mem)[(token_index >> 1) + 2] = 0x7FU; /* edge case? */ + ((uint8_t *)mem)[token_index] = 0; + ((uint8_t *)mem)[token_index + 1] = 0; + FIO_ASSERT(memchr((char *)mem + 1, 0, mem_len) == + fio_memchr((char *)mem + 1, 0, mem_len), + "fio_memchr != memchr"); + ((uint8_t *)mem)[token_index] = (char)0x80; + ((uint8_t *)mem)[token_index + 1] = (char)0x80; + + token_index = mem_len; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + char *result = (char *)memchr((char *)mem, 0, mem_len); + FIO_ASSERT(result == ((char *)mem + token_index) || + (!result && token_index == mem_len), + "memchr failed? @ %zu", + token_index); + FIO_COMPILER_GUARD; + ((uint8_t *)mem)[token_index] = 0x80; + token_index = (token_index - 1) & ((1ULL << len_i) - 1); + ((uint8_t *)mem)[token_index] = 0; + } + end = fio_time_micro(); + ((uint8_t *)mem)[token_index] = 0x80; + fprintf(stderr, + "\tsystem memchr\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + token_index = mem_len; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + char *result = (char *)fio_memchr((char *)mem, 0, mem_len); + FIO_ASSERT(result == ((char *)mem + token_index) || + (!result && token_index == mem_len), + "fio_memchr failed? @ %zu", + token_index); + FIO_COMPILER_GUARD; + ((uint8_t *)mem)[token_index] = 0x80; + token_index = (token_index - 1) & ((1ULL << len_i) - 1); + ((uint8_t *)mem)[token_index] = 0; + } + end = fio_time_micro(); + ((uint8_t *)mem)[token_index] = 0x80; + fprintf(stderr, + "\tfio_memchr\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + free(mem); + } + + fprintf(stderr, "* Speed testing memcmp:\n"); + + for (int len_i = 2; len_i < 21; ++len_i) { + const size_t repetitions = base_repetitions + << (len_i < 13 ? (15 - (len_i & 15)) : 2); + const size_t mem_len = (1ULL << len_i); + char *mem = (char *)malloc((mem_len << 1) + 128); + FIO_ASSERT_ALLOC(mem); + uint64_t sig = (uintptr_t)mem; + sig ^= sig >> 13; + sig ^= sig << 17; + sig ^= sig << 29; + sig ^= sig << 31; + char *a = mem; + char *b = mem + mem_len + 32; + fio_memset(a, sig, mem_len); + a[mem_len] = 'A'; + fio_memset(b, sig, mem_len); + b[mem_len] = 'B'; + size_t twister = 0; + + if (mem_len > 64) { + for (size_t i = 0; i < 64; ++i) { + FIO_ASSERT(!fio_memcmp(a + i, b + i, mem_len - i), + "fio_memcmp sanity test FAILED (%zu eq)", + mem_len); + FIO_ASSERT(fio_ct_is_eq(a + i, b + i, mem_len - i), + "fio_ct_is_eq sanity test FAILED (%zu eq)", + mem_len); + } + } else { + FIO_ASSERT(!fio_memcmp(a, b, mem_len), + "fio_memcmp sanity test FAILED (%zu eq)", + mem_len); + FIO_ASSERT(fio_ct_is_eq(a, b, mem_len), + "fio_ct_is_eq sanity test FAILED (%zu eq)", + mem_len); + } + { + mem[mem_len - 2]--; + if (mem_len > 64) { + for (size_t i = 0; i < 64; ++i) { + int r1 = fio_memcmp(a + i, b + i, mem_len - i); + int r2 = memcmp(a + i, b + i, mem_len - i); + FIO_ASSERT((r1 > 0 && r2 > 0) | (r1 < 0 && r2 < 0), + "fio_memcmp sanity test FAILED (%zu !eq)", + mem_len); + FIO_ASSERT(!fio_ct_is_eq(a, b, mem_len), + "fio_ct_is_eq sanity test FAILED (%zu !eq)", + mem_len); + } + } else { + int r1 = fio_memcmp(a, b, mem_len); + int r2 = memcmp(a, b, mem_len); + FIO_ASSERT((r1 > 0 && r2 > 0) | (r1 < 0 && r2 < 0), + "fio_memcmp sanity test FAILED (%zu !eq)", + mem_len); + FIO_ASSERT(!fio_ct_is_eq(a, b, mem_len), + "fio_ct_is_eq sanity test FAILED (%zu !eq)", + mem_len); + } + mem[mem_len - 2]++; + } + + FIO_MEMCPY(b, a, mem_len); /* shouldn't be needed, but anyway */ + twister = mem_len - 3; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + int cmp = memcmp(a, b, mem_len); + FIO_COMPILER_GUARD; + if (cmp) { + ++mem[twister--]; + twister &= ((1ULL << (len_i - 1)) - 1); + } else { + --mem[twister]; + } + } + end = fio_time_micro(); + fprintf(stderr, + "\tsystem memcmp\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + FIO_MEMCPY(b, a, mem_len); /* shouldn't be needed, but anyway */ + twister = mem_len - 3; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + int cmp = fio_memcmp(a, b, mem_len); + FIO_COMPILER_GUARD; + if (cmp) { + ++mem[twister--]; + twister &= ((1ULL << (len_i - 1)) - 1); + } else { + --mem[twister]; + } + } + end = fio_time_micro(); + fprintf(stderr, + "\tfio_memcmp\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + FIO_MEMCPY(b, a, mem_len); /* shouldn't be needed, but anyway */ + twister = mem_len - 3; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + int cmp = fio_ct_is_eq(a, b, mem_len); + FIO_COMPILER_GUARD; + if (!cmp) { + ++mem[twister--]; + twister &= ((1ULL << (len_i - 1)) - 1); + } else { + --mem[twister]; + } + } + end = fio_time_micro(); + fprintf(stderr, + "\tfio_ct_is_eq\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + free(mem); + } + + fprintf(stderr, "* Speed testing strlen:\n"); + + for (int len_i = 2; len_i < 20; ++len_i) { + const size_t repetitions = base_repetitions + << (len_i < 15 ? (15 - (len_i & 15)) : 0); + const size_t mem_len = (1ULL << len_i) - 1; + size_t token_index = ((mem_len >> 1) + (mem_len >> 2)) + 1; + void *mem = malloc(mem_len + 1); + FIO_ASSERT_ALLOC(mem); + fio_memset(mem, ((uint64_t)0x0101010101010101ULL * 0x80), mem_len + 1); + ((uint8_t *)mem)[token_index >> 1] = 0xFFU; /* edge case? */ + ((uint8_t *)mem)[(token_index >> 1) + 1] = 0x01U; /* edge case? */ + ((uint8_t *)mem)[(token_index >> 1) + 2] = 0x7FU; /* edge case? */ + ((uint8_t *)mem)[token_index] = 0; + ((uint8_t *)mem)[token_index + 1] = 0; + FIO_ASSERT(fio_strlen((char *)mem + 1) == strlen((char *)mem + 1), + "fio_strlen != strlen"); + FIO_ASSERT(fio_strlen((char *)mem) == strlen((char *)mem), + "fio_strlen != strlen"); + ((uint8_t *)mem)[token_index] = 0x80U; + ((uint8_t *)mem)[token_index + 1] = 0x80U; + ((uint8_t *)mem)[mem_len] = 0; + FIO_ASSERT(fio_strlen((char *)mem) == strlen((char *)mem) && + fio_strlen((char *)mem) == mem_len, + "fio_strlen != strlen"); + + token_index = mem_len - 1; + ((uint8_t *)mem)[token_index] = 0; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + size_t result = strlen((char *)mem); + FIO_ASSERT(result == token_index, "strlen failed? @ %zu", token_index); + FIO_COMPILER_GUARD; + ((uint8_t *)mem)[token_index] = 0x80; + token_index = (token_index - 1) & ((1ULL << len_i) - 1); + token_index -= (token_index == mem_len); + ((uint8_t *)mem)[token_index] = 0; + } + end = fio_time_micro(); + ((uint8_t *)mem)[token_index] = 0x80; + fprintf(stderr, + "\tsystem strlen\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + token_index = mem_len - 1; + ((uint8_t *)mem)[token_index] = 0; + start = fio_time_micro(); + for (size_t i = 0; i < repetitions; ++i) { + size_t result = fio_strlen((char *)mem); + FIO_ASSERT(result == token_index, + "fio_strlen failed? @ %zu", + token_index); + FIO_COMPILER_GUARD; + ((uint8_t *)mem)[token_index] = 0x80; + token_index = (token_index - 1) & ((1ULL << len_i) - 1); + token_index -= token_index == mem_len; + ((uint8_t *)mem)[token_index] = 0; + } + end = fio_time_micro(); + ((uint8_t *)mem)[token_index] = 0x80; + fprintf(stderr, + "\tfio_strlen\t(up to %zu bytes):\t%zuus\t/ %zu\n", + mem_len, + (size_t)(end - start), + repetitions); + + free(mem); + } +#endif /* DEBUG */ + ((void)start), ((void)end); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_MUSTACHE Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_MUSTACHE_TEST___H) +// #define H___FIO_MUSTACHE_TEST___H +// #ifndef H___FIO_MUSTACHE___H +// #define FIO_MUSTACHE +// #define FIO___TEST_REINCLUDE +// #include FIO_INCLUDE_FILE +// #undef FIO___TEST_REINCLUDE +// #endif + +FIO_SFUNC void FIO_NAME_TEST(stl, mustache)(void) { + fprintf(stderr, "* Testing mustache template parser.\n"); + char *example1 = (char *)"This is a {{tag}}, and so is {{ this_one }}."; + char *example2 = (char *)"{{tag}} and {{ incomplete}"; + fio_mustache_s *m = fio_mustache_load(.data = FIO_BUF_INFO1(example1)); + FIO_ASSERT(m, "valid example load failed!"); + char *result = (char *)fio_mustache_build(m, .ctx = NULL); + FIO_ASSERT(result, "a valid fio_mustache_build returned NULL"); + FIO_ASSERT( + FIO_BUF_INFO_IS_EQ(fio_bstr_buf(result), + FIO_BUF_INFO1((char *)"This is a , and so is .")), + "valid example result failed: %s", + result); + fio_bstr_free(result); + fio_mustache_free(m); + fprintf(stderr, "\terror should print on next line.\n"); + m = fio_mustache_load(.data = FIO_BUF_INFO1(example2)); + FIO_ASSERT(!m, "invalid example load returned an object."); + fio_mustache_free(m); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_POLL Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_POLL_TEST___H) +#define H___FIO_POLL_TEST___H +#ifndef H___FIO_POLL___H +#define FIO_POLL +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#if FIO_POLL_ENGINE == FIO_POLL_ENGINE_EPOLL +FIO_SFUNC void FIO_NAME_TEST(stl, poll)(void) { + fprintf(stderr, + "* SKIPPED testing file descriptor polling (engine: epoll).\n"); +} + +#elif FIO_POLL_ENGINE == FIO_POLL_ENGINE_KQUEUE +FIO_SFUNC void FIO_NAME_TEST(stl, poll)(void) { + fprintf(stderr, + "* SKIPPED testing file descriptor polling (engine: kqueue).\n"); +} + +#elif FIO_POLL_ENGINE == FIO_POLL_ENGINE_POLL +FIO_SFUNC void FIO_NAME_TEST(stl, poll)(void) { + fprintf( + stderr, + "* Testing file descriptor monitoring (poll setup / cleanup only).\n"); + fio_poll_s p; + fio_poll_init(&p, NULL); + short events[4] = {POLLOUT, POLLIN, POLLOUT | POLLIN, POLLOUT | POLLIN}; + for (int i = 128; i--;) { + FIO_ASSERT(!fio_poll_monitor(&p, i, (void *)(uintptr_t)i, events[(i & 3)]), + "fio_poll_monitor failed for fd %d", + i); + } + for (int i = 128; i--;) { + if ((i & 3) == 3) { + FIO_ASSERT(!fio_poll_forget(&p, i), "fio_poll_forget failed at %d", i); + FIO_ASSERT(fio_poll_forget(&p, i), + "fio_poll_forget didn't forget previous %d", + i); + } + } + fio_poll_destroy(&p); +} + +#endif +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_PUBSUB Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_PUBSUB_TEST___H) +#define H___FIO_PUBSUB_TEST___H +#ifndef H___FIO_PUBSUB___H +#define FIO_PUBSUB +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +/* ***************************************************************************** +Encryption Testing +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_encryption)(void) { + fprintf(stderr, "* Testing pub/sub encryption / decryption.\n"); + fio_publish_args_s origin = {.channel = FIO_BUF_INFO1((char *)"my channel"), + .message = FIO_BUF_INFO1((char *)"my message"), + .filter = 0xAA, + .is_json = + FIO___PUBSUB_JSON | FIO___PUBSUB_CLUSTER}; + fio___pubsub_message_s *enc = fio___pubsub_message_author(origin); + fio___pubsub_message_encrypt(enc); + FIO_ASSERT(FIO_BUF_INFO_IS_EQ(enc->data.channel, origin.channel), + "channel info error"); + FIO_ASSERT(FIO_BUF_INFO_IS_EQ(enc->data.message, origin.message), + "message info error"); + FIO_ASSERT(enc->data.filter == origin.filter, "filter info error"); + FIO_ASSERT(enc->data.is_json == origin.is_json, "flags info error"); + FIO_MEM_STACK_WIPE(2); + + fio___pubsub_message_s *dec = fio___pubsub_message_alloc(enc->data.udata); + FIO_MEMCPY(dec->data.udata, + enc->data.udata, + origin.channel.len + origin.message.len + + FIO___PUBSUB_MESSAGE_OVERHEAD); + dec->data.udata = enc->data.udata; + FIO_ASSERT(!fio___pubsub_message_decrypt(dec), "decryption failed"); + FIO_ASSERT(enc->data.filter == dec->data.filter, + "(pubsub) filter enc/dec error"); + FIO_ASSERT(enc->data.is_json == dec->data.is_json, + "(pubsub) is_json enc/dec error"); + FIO_ASSERT(enc->data.id == dec->data.id, "(pubsub) id enc/dec error"); + FIO_ASSERT(enc->data.published == dec->data.published, + "(pubsub) published enc/dec error"); + FIO_ASSERT(FIO_BUF_INFO_IS_EQ(enc->data.channel, dec->data.channel), + "(pubsub) channel enc/dec error"); + FIO_ASSERT(FIO_BUF_INFO_IS_EQ(enc->data.message, dec->data.message), + "(pubsub) message enc/dec error"); + fio___pubsub_message_free(enc); + fio___pubsub_message_free(dec); +} + +/* ***************************************************************************** +Round Trip Testing +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_on_message)(fio_msg_s *msg) { + ((int *)(msg->udata))[0] += 1; +} +FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_on_unsubscribe)(void *udata) { + ((int *)(udata))[0] -= 1; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_roundtrip)(void) { + fprintf(stderr, "* Testing pub/sub round-trip.\n"); + uintptr_t sub_handle = 0; + int state = 0, expected = 0, delta = 0; + fio_buf_info_s test_channel = FIO_BUF_INFO1((char *)"pubsub_test_channel"); + fio_subscribe_args_s sub[] = { + { + .channel = test_channel, + .on_message = FIO_NAME_TEST(stl, pubsub_on_message), + .on_unsubscribe = FIO_NAME_TEST(stl, pubsub_on_unsubscribe), + .udata = &state, + .filter = -127, + }, + { + .channel = test_channel, + .on_message = FIO_NAME_TEST(stl, pubsub_on_message), + .on_unsubscribe = FIO_NAME_TEST(stl, pubsub_on_unsubscribe), + .udata = &state, + .subscription_handle_ptr = &sub_handle, + .filter = -127, + }, + { + .channel = FIO_BUF_INFO1((char *)"pubsub_*"), + .on_message = FIO_NAME_TEST(stl, pubsub_on_message), + .on_unsubscribe = FIO_NAME_TEST(stl, pubsub_on_unsubscribe), + .udata = &state, + .filter = -127, + .is_pattern = 1, + }, + }; + const int sub_count = (sizeof(sub) / sizeof(sub[0])); + +#define FIO___PUBLISH2TEST() \ + fio_publish(.engine = FIO_PUBSUB_CLUSTER, \ + .channel = test_channel, \ + .filter = -127); \ + expected += delta; \ + fio_queue_perform_all(fio_io_queue()); + + for (int i = 0; i < sub_count; ++i) { + fio_subscribe FIO_NOOP(sub[i]); + ++delta; + FIO_ASSERT(state == expected, "subscribe shouldn't affect state (%d)", i); + FIO___PUBLISH2TEST(); + FIO_ASSERT(state == expected, "pub/sub test state incorrect (1-%d)", i); + FIO___PUBLISH2TEST(); + FIO_ASSERT(state == expected, "pub/sub test state incorrect (2-%d)", i); + } + for (int i = 0; i < sub_count; ++i) { + if (fio_unsubscribe FIO_NOOP(sub[i])) + FIO_LOG_WARNING("fio_unsubscribe returned an error value"); + --delta; + --expected; + fio_queue_perform_all(fio_io_queue()); + FIO_ASSERT(state == expected, "unsubscribe should call callback (%i)", i); + FIO___PUBLISH2TEST(); + FIO_ASSERT(state == expected, "pub/sub test state incorrect (3-%d)", i); + FIO___PUBLISH2TEST(); + FIO_ASSERT(state == expected, "pub/sub test state incorrect (4-%d)", i); + } +#undef FIO___PUBLISH2TEST +} + +/* ***************************************************************************** + +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, pubsub)(void) { + FIO_NAME_TEST(stl, pubsub_encryption)(); + FIO_NAME_TEST(stl, pubsub_roundtrip)(); + fio___io_cleanup_at_exit(NULL); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_QUEUE Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_QUEUE_TEST___H) +#define H___FIO_QUEUE_TEST___H +#ifndef H___FIO_QUEUE___H +#define FIO_QUEUE +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#ifndef FIO___QUEUE_TEST_PRINT +#define FIO___QUEUE_TEST_PRINT 1 +#endif + +#define FIO___QUEUE_TOTAL_COUNT (512 * 1024) + +typedef struct { + fio_queue_s *q; + uintptr_t count; + uintptr_t *counter; +} fio___queue_test_s; + +FIO_SFUNC void fio___queue_test_sample_task(void *i_count, void *unused2) { + (void)(unused2); + fio_atomic_add((uintptr_t *)i_count, 1); +} + +FIO_SFUNC void fio___queue_test_counter_task(void *i_count1, void *i_count2) { + static intptr_t counter = 0; + if (!i_count1 && !i_count2) { + counter = 0; + return; + } + FIO_ASSERT((intptr_t)i_count1 == (intptr_t)counter + 1, + "udata1 value error in task"); + FIO_ASSERT((intptr_t)i_count2 == (intptr_t)counter + 2, + "udata2 value error in task"); + ++counter; +} + +FIO_SFUNC void fio___queue_test_sched_sample_task(void *t_, void *i_count) { + fio___queue_test_s *t = (fio___queue_test_s *)t_; + size_t i = (size_t)(uintptr_t)i_count; + FIO_ASSERT(!fio_queue_push(t->q, + .fn = fio___queue_test_sample_task, + .udata1 = t->counter), + "Couldn't push task!"); + --i; + if (!i) + return; + if ((i & 1)) { + FIO_ASSERT( + !fio_queue_push(t->q, fio___queue_test_sched_sample_task, t, (void *)i), + "Couldn't push task!"); + } else { + FIO_ASSERT(!fio_queue_push_urgent(t->q, + fio___queue_test_sched_sample_task, + t, + (void *)i), + "Couldn't push task!"); + } +} + +FIO_SFUNC int fio___queue_test_timer_task(void *i_count, void *unused2) { + fio_atomic_add((uintptr_t *)i_count, 1); + return (unused2 ? -1 : 0); +} + +FIO_SFUNC void FIO_NAME_TEST(stl, queue)(void) { + fprintf(stderr, "* Testing facil.io task scheduling (fio_queue)\n"); + /* ************** testing queue ************** */ + fio_queue_s *q = fio_queue_new(); + fio_queue_s q2; + + fprintf(stderr, "\t- size of queue object (fio_queue_s): %zu\n", sizeof(*q)); + fprintf(stderr, + "\t- size of queue ring buffer (per allocation): %zu\n", + sizeof(q->mem)); + fprintf(stderr, + "\t- event slots per queue allocation: %zu\n", + (size_t)FIO_QUEUE_TASKS_PER_ALLOC); + + /* test task user data integrity. */ + fio___queue_test_counter_task(NULL, NULL); + for (size_t i = 0; i < (FIO_QUEUE_TASKS_PER_ALLOC << 2); ++i) { + fio_queue_push(q, + .fn = fio___queue_test_counter_task, + .udata1 = (void *)(i + 1), + .udata2 = (void *)(i + 2)); + } + fio_queue_perform_all(q); + fio_queue_perform_all(q); + for (size_t i = (FIO_QUEUE_TASKS_PER_ALLOC << 2); + i < (FIO_QUEUE_TASKS_PER_ALLOC << 3); + ++i) { + fio_queue_push(q, + .fn = fio___queue_test_counter_task, + .udata1 = (void *)(i + 1), + .udata2 = (void *)(i + 2)); + } + fio_queue_perform_all(q); + fio_queue_perform_all(q); + FIO_ASSERT(!fio_queue_count(q) && fio_queue_perform(q) == -1, + "fio_queue_perform_all didn't perform all"); + + const size_t max_threads = 12; // assumption / pure conjuncture... + uintptr_t i_count; + uint64_t start, end; + i_count = 0; + start = fio_time_milli(); + for (size_t i = 0; i < FIO___QUEUE_TOTAL_COUNT; i++) { + fio___queue_test_sample_task(&i_count, NULL); + } + end = fio_time_milli(); + if (FIO___QUEUE_TEST_PRINT) { + fprintf(stderr, + "\t- Queueless (direct call) counter: %lu ms with i_count = %lu\n", + (unsigned long)(end - start), + (unsigned long)i_count); + } + size_t i_count_should_be = i_count; + i_count = 0; + start = fio_time_milli(); + for (size_t i = 0; i < FIO___QUEUE_TOTAL_COUNT; i++) { + fio_queue_push(q, + .fn = fio___queue_test_sample_task, + .udata1 = (void *)&i_count); + } + fio_queue_perform_all(q); + fio_queue_perform_all(q); + fio_queue_perform_all(q); + end = fio_time_milli(); + if (FIO___QUEUE_TEST_PRINT) { + fprintf(stderr, + "\t- single task counter: %lu ms with i_count = %lu\n", + (unsigned long)(end - start), + (unsigned long)i_count); + } + FIO_ASSERT(i_count == i_count_should_be, "ERROR: queue count invalid\n"); + + if (FIO___QUEUE_TEST_PRINT) { + fprintf(stderr, "\n"); + } + + for (size_t i = 1; i < 32 && FIO___QUEUE_TOTAL_COUNT >> i; ++i) { + fio___queue_test_s info = { + .q = q, + .count = (uintptr_t)(FIO___QUEUE_TOTAL_COUNT >> i), + .counter = &i_count, + }; + const size_t tasks = 1 << i; + i_count = 0; + start = fio_time_milli(); + for (size_t j = 0; j < tasks; ++j) { + fio_queue_push(q, + fio___queue_test_sched_sample_task, + (void *)&info, + (void *)info.count); + } + FIO_ASSERT(fio_queue_count(q), "tasks not counted?!"); + { + const size_t t_count = (i % max_threads) + 1; + if (0) { + fio_queue_workers_add(q, t_count); + while (!(volatile uintptr_t)i_count) + FIO_THREAD_RESCHEDULE(); + fio_queue_workers_join(q); + } else { + union { + void *(*t)(void *); + void (*act)(fio_queue_s *); + } thread_tasks; + thread_tasks.act = fio_queue_perform_all; + fio_thread_t *threads = (fio_thread_t *) + FIO_MEM_REALLOC(NULL, 0, sizeof(*threads) * t_count, 0); + for (size_t j = 0; j < t_count; ++j) { + if (fio_thread_create(threads + j, thread_tasks.t, q)) { + abort(); + } + } + for (size_t j = 0; j < t_count; ++j) { + fio_thread_join(threads + j); + } + FIO_MEM_FREE(threads, sizeof(*threads) * t_count); + } + } + + end = fio_time_milli(); + if (FIO___QUEUE_TEST_PRINT) { + fprintf(stderr, + "- queue performed using %zu threads, %zu scheduling tasks (%zu " + "each):\n" + " %lu ms with i_count = %lu\n", + ((i % max_threads) + 1), + tasks, + info.count, + (unsigned long)(end - start), + (unsigned long)i_count); + } else { + fprintf(stderr, "."); + } + FIO_ASSERT(i_count == i_count_should_be, "ERROR: queue count invalid\n"); + } + if (!(FIO___QUEUE_TEST_PRINT)) + fprintf(stderr, "\n"); + FIO_ASSERT(q->w == &q->mem, + "queue library didn't release dynamic queue (should be static)"); + fio_queue_free(q); + { + fprintf(stderr, "* Testing urgent insertion\n"); + fio_queue_init(&q2); + for (size_t i = 0; i < (FIO_QUEUE_TASKS_PER_ALLOC * 3); ++i) { + FIO_ASSERT(!fio_queue_push_urgent(&q2, + .fn = (void (*)(void *, void *))(i + 1), + .udata1 = (void *)(i + 1)), + "fio_queue_push_urgent failed"); + } + FIO_ASSERT(q2.r->next && q2.r->next->next && !q2.r->next->next->next, + "should have filled only three task blocks"); + for (size_t i = 0; i < (FIO_QUEUE_TASKS_PER_ALLOC * 3); ++i) { + fio_queue_task_s t = fio_queue_pop(&q2); + FIO_ASSERT( + t.fn && (size_t)t.udata1 == (FIO_QUEUE_TASKS_PER_ALLOC * 3) - i, + "fio_queue_push_urgent pop ordering error [%zu] %zu != %zu (%p)", + i, + (size_t)t.udata1, + (FIO_QUEUE_TASKS_PER_ALLOC * 3) - i, + (void *)(uintptr_t)t.fn); + } + FIO_ASSERT(fio_queue_pop(&q2).fn == NULL, + "pop overflow after urgent tasks"); + fio_queue_destroy(&q2); + } + /* ************** testing timers ************** */ + { + fprintf(stderr, + "* Testing facil.io timer scheduling (fio_timer_queue_s)\n"); + fprintf(stderr, " Note: Errors SHOULD print out to the log.\n"); + fio_queue_init(&q2); + volatile uintptr_t tester = 0; + fio_timer_queue_s tq = FIO_TIMER_QUEUE_INIT; + + /* test failuers */ + fio_timer_schedule(&tq, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 100, + .repetitions = -1); + FIO_ASSERT(tester == 1, + "fio_timer_schedule should have called `on_finish`"); + tester = 0; + fio_timer_schedule(NULL, + .fn = fio___queue_test_timer_task, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 100, + .repetitions = -1); + FIO_ASSERT(tester == 1, + "fio_timer_schedule should have called `on_finish`"); + tester = 0; + fio_timer_schedule(&tq, + .fn = fio___queue_test_timer_task, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 0, + .repetitions = -1); + FIO_ASSERT(tester == 1, + "fio_timer_schedule should have called `on_finish`"); + fprintf(stderr, " Note: no more errors should print for this test.\n"); + + /* test endless task */ + tester = 0; + fio_timer_schedule(&tq, + .fn = fio___queue_test_timer_task, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 1, + .repetitions = -1, + .start_at = fio_time_milli() - 10); + FIO_ASSERT(tester == 0, + "fio_timer_schedule should have scheduled the task."); + for (size_t i = 0; i < 10; ++i) { + uint64_t now = fio_time_milli(); + fio_timer_push2queue(&q2, &tq, now); + fio_timer_push2queue(&q2, &tq, now); + FIO_ASSERT(fio_queue_count(&q2), "task should have been scheduled"); + FIO_ASSERT(fio_queue_count(&q2) == 1, + "task should have been scheduled only once"); + fio_queue_perform(&q2); + FIO_ASSERT(!fio_queue_count(&q2), "queue should be empty"); + FIO_ASSERT(tester == i + 1, + "task should have been performed (%zu).", + (size_t)tester); + } + + tester = 0; + fio_timer_destroy(&tq); + FIO_ASSERT(tester == 1, "fio_timer_destroy should have called `on_finish`"); + + /* test single-use task */ + tester = 0; + int64_t milli_now = fio_time_milli(); + fio_timer_schedule(&tq, + .fn = fio___queue_test_timer_task, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 100, + .repetitions = 1, + .start_at = milli_now - 10); + FIO_ASSERT(tester == 0, + "fio_timer_schedule should have scheduled the task."); + fio_timer_schedule(&tq, + .fn = fio___queue_test_timer_task, + .udata1 = (void *)&tester, + .on_finish = fio___queue_test_sample_task, + .every = 1, + // .repetitions = 1, // auto-value is 1 + .start_at = milli_now - 10); + FIO_ASSERT(tester == 0, + "fio_timer_schedule should have scheduled the task."); + FIO_ASSERT(fio_timer_next_at(&tq) == milli_now - 9, + "fio_timer_next_at value error."); + fio_timer_push2queue(&q2, &tq, milli_now); + FIO_ASSERT(fio_queue_count(&q2) == 1, + "task should have been scheduled (2)"); + FIO_ASSERT(fio_timer_next_at(&tq) == milli_now + 90, + "fio_timer_next_at value error for unscheduled task."); + fio_queue_perform(&q2); + FIO_ASSERT(!fio_queue_count(&q2), "queue should be empty"); + FIO_ASSERT(tester == 2, + "task should have been performed and on_finish called (%zu).", + (size_t)tester); + fio_timer_destroy(&tq); + FIO_ASSERT( + tester == 3, + "fio_timer_destroy should have called on_finish of future task (%zu).", + (size_t)tester); + FIO_ASSERT(!tq.next, "timer queue should be empty."); + fio_queue_destroy(&q2); + } + fprintf(stderr, "* passed.\n"); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Random Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_RAND_TEST___H) +#define H___FIO_RAND_TEST___H +#ifndef H___FIO_RAND___H +#define FIO_RAND +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +/* ***************************************************************************** +Playhouse hashing (next risky version) +***************************************************************************** */ + +typedef union { + uint64_t v[4] FIO_ALIGN(16); +#ifdef __SIZEOF_INT128__ + __uint128_t u128[2]; +#endif +} fio___r2hash_s; + +FIO_IFUNC fio___r2hash_s fio_risky2_hash___inner(const void *restrict data_, + size_t len, + uint64_t seed) { + fio___r2hash_s v = {.v = {seed, seed, seed, seed}}; + fio___r2hash_s const prime = {.v = {FIO_U64_HASH_PRIME0, + FIO_U64_HASH_PRIME1, + FIO_U64_HASH_PRIME2, + FIO_U64_HASH_PRIME3}}; + fio___r2hash_s w; + const uint8_t *data = (const uint8_t *)data_; + /* seed selection is constant time to avoid leaking seed data */ + seed += len; + seed ^= fio_lrot64(seed, 47); + seed ^= FIO_U64_HASH_PRIME4; + +#define FIO___R2_ROUND(i) /* this version passes all, but fast enough? */ \ + w.v[i] = fio_ltole64(w.v[i]); /* make sure we're using little endien? */ \ + v.v[i] ^= w.v[i]; \ + v.v[i] *= prime.v[i]; \ + w.v[i] = fio_lrot64(w.v[i], 31); \ + v.v[i] += w.v[i]; \ + v.v[i] ^= seed; + + /* consumes 32 bytes (256 bits) blocks (no padding needed) */ + for (size_t pos = 31; pos < len; pos += 32) { + for (size_t i = 0; i < 4; ++i) { + fio_memcpy8(w.v + i, data + (i << 3)); + FIO___R2_ROUND(i); + } + seed += w.v[0] + w.v[1] + w.v[2] + w.v[3]; + data += 32; + } +#if (FIO___R2_PERFORM_FULL_BLOCK + 1) && 1 + if (len & 31) { // pad with zeros + uint64_t tmp_buf[4] = {0}; + fio_memcpy31x(tmp_buf, data, len); + for (size_t i = 0; i < 4; ++i) { + w.v[0] = tmp_buf[1]; + FIO___R2_ROUND(i); + } + } +#else + switch (len & 24) { /* only performed if data exits in these positions */ + case 24: fio_memcpy8(w.v + 2, data + 16); FIO___R2_ROUND(2); /*fall through*/ + case 16: fio_memcpy8(w.v + 1, data + 8); FIO___R2_ROUND(1); /*fall through*/ + case 8: + fio_memcpy8(w.v + 0, data); + FIO___R2_ROUND(0); + data += len & 24; + } + if (len & 7) { + uint64_t i = (len & 24) >> 3; + w.v[i] = 0; + fio_memcpy7x(w.v + i, data, len); + FIO___R2_ROUND(i); + } +#endif + + /* inner vector mini-avalanche */ + for (size_t i = 0; i < 4; ++i) + v.v[i] *= prime.v[i]; + v.v[0] ^= fio_lrot64(v.v[0], 7); + v.v[1] ^= fio_lrot64(v.v[1], 11); + v.v[2] ^= fio_lrot64(v.v[2], 13); + v.v[3] ^= fio_lrot64(v.v[3], 17); + return v; +#undef FIO___R2_ROUND +} + +/* Computes a facil.io Stable Hash. */ +FIO_SFUNC uint64_t fio_risky2_hash(const void *data_, + size_t len, + uint64_t seed) { + uint64_t r; + fio___r2hash_s v = fio_risky2_hash___inner(data_, len, seed); + /* summing avalanche */ + r = v.v[0] + v.v[1] + v.v[2] + v.v[3]; + r ^= r >> 31; + r *= FIO_U64_HASH_PRIME4; + r ^= r >> 31; + return r; +} + +FIO_SFUNC void fio_risky2_hash128(void *restrict dest, + const void *restrict data_, + size_t len, + uint64_t seed) { + fio___r2hash_s v = fio_risky2_hash___inner(data_, len, seed); + uint64_t r[2]; + r[0] = v.v[0] + v.v[1] + v.v[2] + v.v[3]; + r[1] = v.v[0] ^ v.v[1] ^ v.v[2] ^ v.v[3]; + r[0] ^= r[0] >> 31; + r[1] ^= r[1] >> 31; + r[0] *= FIO_U64_HASH_PRIME4; + r[1] *= FIO_U64_HASH_PRIME0; + r[0] ^= r[0] >> 31; + r[1] ^= r[1] >> 31; + fio_memcpy16(dest, r); +} + +#undef FIO___R2_HASH_MUL_PRIME +#undef FIO___R2_HASH_ROUND_FULL + +/* ***************************************************************************** +Hashing speed test +***************************************************************************** */ +#include + +typedef uintptr_t (*fio__hashing_func_fn)(char *, size_t); + +FIO_SFUNC void fio_test_hash_function(fio__hashing_func_fn h, + char *name, + uint8_t size_log, + uint8_t mem_alignment_offset, + uint8_t fast) { + /* test based on code from BearSSL with credit to Thomas Pornin */ + if (size_log >= 21 || ((sizeof(uint64_t) - 1) >> size_log)) { + FIO_LOG_ERROR("fio_test_hash_function called with a log size too big."); + return; + } + mem_alignment_offset &= 7; + size_t const buffer_len = (1ULL << size_log) - mem_alignment_offset; + uint64_t cycles_start_at = (1ULL << (14 + (fast * 3))); + if (size_log < 13) + cycles_start_at <<= (13 - size_log); + else if (size_log > 13) + cycles_start_at >>= (size_log - 13); + +#ifdef DEBUG + fprintf(stderr, + "* Testing %s speed with %zu byte blocks" + "(DEBUG mode detected - speed may be affected).\n", + name, + buffer_len); +#else + fprintf(stderr, + "* Testing %s speed with %zu byte blocks.\n", + name, + buffer_len); +#endif + + uint8_t *buffer_mem = (uint8_t *) + FIO_MEM_REALLOC(NULL, 0, (buffer_len + mem_alignment_offset) + 64, 0); + uint8_t *buffer = buffer_mem + mem_alignment_offset; + + FIO_MEMSET(buffer, 'T', buffer_len); + /* warmup */ + uint64_t hash = 0; + for (size_t i = 0; i < 4; i++) { + hash += h((char *)buffer, buffer_len); + fio_memcpy8(buffer, &hash); + } + /* loop until test runs for more than 2 seconds */ + for (uint64_t cycles = cycles_start_at;;) { + clock_t start, end; + start = clock(); + for (size_t i = cycles; i > 0; i--) { + hash += h((char *)buffer, buffer_len); + FIO_COMPILER_GUARD; + } + end = clock(); + fio_memcpy8(buffer, &hash); + if ((end - start) > CLOCKS_PER_SEC || cycles >= ((uint64_t)1 << 62)) { + fprintf(stderr, + "\t%-40s %8.2f MB/s\n", + name, + (double)(buffer_len * cycles) / + (((end - start) * (1000000.0 / CLOCKS_PER_SEC)))); + break; + } + cycles <<= 1; + } + FIO_MEM_FREE(buffer_mem, (buffer_len + mem_alignment_offset) + 64); +} + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, risky_wrapper)(char *buf, size_t len) { + return fio_risky_hash(buf, len, 1); +} +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, stable_wrapper)(char *buf, size_t len) { + return fio_stable_hash(buf, len, 1); +} +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, risky2_wrapper)(char *buf, size_t len) { + return fio_risky2_hash(buf, len, 1); +} + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, risky_ptr_wrapper)(char *buf, + size_t len) { + uint64_t h[4] = {0}; + while (len > 31) { + h[0] += fio_risky_ptr((void *)fio_buf2u64u(buf)); + h[1] += fio_risky_ptr((void *)fio_buf2u64u(buf + 8)); + h[2] += fio_risky_ptr((void *)fio_buf2u64u(buf + 16)); + h[3] += fio_risky_ptr((void *)fio_buf2u64u(buf + 24)); + len -= 32; + buf += 32; + } + if ((len & 31)) { + uint64_t t[4] = {0}; + fio_memcpy31x(t, buf, len); + h[0] += fio_risky_ptr((void *)t[0]); + h[1] += fio_risky_ptr((void *)t[1]); + h[2] += fio_risky_ptr((void *)t[2]); + h[3] += fio_risky_ptr((void *)t[3]); + } + return h[0] + h[1] + h[2] + h[3]; +} +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, risky_num_wrapper)(char *buf, + size_t len) { + uint64_t h[4] = {0}; + while (len > 31) { + h[0] += fio_risky_num(fio_buf2u64u(buf), 0); + h[1] += fio_risky_num(fio_buf2u64u(buf + 8), 0); + h[2] += fio_risky_num(fio_buf2u64u(buf + 16), 0); + h[3] += fio_risky_num(fio_buf2u64u(buf + 24), 0); + len -= 32; + buf += 32; + } + if ((len & 31)) { + uint64_t t[4] = {0}; + fio_memcpy31x(t, buf, len); + h[0] += fio_risky_num(t[0], 0); + h[1] += fio_risky_num(t[1], 0); + h[2] += fio_risky_num(t[2], 0); + h[3] += fio_risky_num(t[3], 0); + } + return h[0] + h[1] + h[2] + h[3]; +} + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, xmask_wrapper)(char *buf, size_t len) { + fio_xmask(buf, len, fio_rand64()); + return len; +} + +/* tests Risky Hash and Stable Hash... takes a while (speed tests as well) */ +FIO_SFUNC void FIO_NAME_TEST(stl, risky)(void) { + fprintf(stderr, "* Testing Risky Hash and Risky Mask (sanity).\n"); + { + char *str = (char *)"testing that risky hash is always the same hash"; + const size_t len = FIO_STRLEN(str); + char buf[128]; + FIO_MEMCPY(buf, str, len); + uint64_t org_hash = fio_risky_hash(buf, len, 0); + FIO_ASSERT(!memcmp(buf, str, len), "hashing shouldn't touch data"); + for (int i = 0; i < 8; ++i) { + char *tmp = buf + i; + FIO_MEMCPY(tmp, str, len); + uint64_t tmp_hash = fio_risky_hash(tmp, len, 0); + FIO_ASSERT(tmp_hash == fio_risky_hash(tmp, len, 0), + "hash should be consistent!"); + FIO_ASSERT(tmp_hash == org_hash, "memory address shouldn't effect hash!"); + } + } +#if !DEBUG + fio_test_hash_function(FIO_NAME_TEST(stl, risky_wrapper), + (char *)"fio_risky_hash", + 7, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_wrapper), + (char *)"fio_risky_hash", + 13, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_wrapper), + (char *)"fio_risky_hash (unaligned)", + 6, + 3, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_wrapper), + (char *)"fio_risky_hash (unaligned)", + 5, + 3, + 2); + fprintf(stderr, "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, stable_wrapper), + (char *)"fio_stable_hash (64 bit)", + 7, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, stable_wrapper), + (char *)"fio_stable_hash (64 bit)", + 13, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, stable_wrapper), + (char *)"fio_stable_hash (64 bit unaligned)", + 6, + 3, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, stable_wrapper), + (char *)"fio_stable_hash (64 bit unaligned)", + 5, + 3, + 2); + fprintf(stderr, "\n"); +#if 0 /* speed test num and ptr hashing */ + fio_test_hash_function(FIO_NAME_TEST(stl, risky_ptr_wrapper), + (char *)"fio_risky_ptr (emulated)", + 7, + 0, + 2); + fprintf(stderr, "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_num_wrapper), + (char *)"fio_risky_num (emulated)", + 7, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_num_wrapper), + (char *)"fio_risky_num (emulated)", + 13, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_num_wrapper), + (char *)"fio_risky_num (emulated)", + 6, + 3, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky_num_wrapper), + (char *)"fio_risky_num (emulated)", + 5, + 3, + 2); +#endif /* speed test num and ptr hashing */ + + /* xmask speed testing */ + fprintf(stderr, "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, xmask_wrapper), + (char *)"fio_xmask (XOR, NO counter)", + 13, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, xmask_wrapper), + (char *)"fio_xmask (unaligned)", + 13, + 1, + 2); + +#if 0 /* speed test playground */ + /* playground speed testing */ + fprintf(stderr, "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, risky2_wrapper), + (char *)"rXtest (64 bit)", + 7, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky2_wrapper), + (char *)"rXtest (64 bit)", + 13, + 0, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky2_wrapper), + (char *)"rXtest (64 bit unaligned)", + 6, + 3, + 2); + fio_test_hash_function(FIO_NAME_TEST(stl, risky2_wrapper), + (char *)"rXtest (64 bit unaligned)", + 5, + 3, + 2); +#endif /* speed test playground */ + fprintf(stderr, "\n"); +#endif /* DEBUG */ +} + +FIO_SFUNC void FIO_NAME_TEST(stl, random_buffer)(uint64_t *stream, + size_t len, + const char *name, + size_t clk) { + size_t totals[2] = {0}; + size_t freq[256] = {0}; + const size_t total_bits = (len * sizeof(*stream) * 8); + uint64_t hemming = 0; + /* collect data */ + for (size_t i = 1; i < len; i += 2) { + hemming += fio_hemming_dist(stream[i], stream[i - 1]); + for (size_t byte = 0; byte < (sizeof(*stream) << 1); ++byte) { + uint8_t val = ((uint8_t *)(stream + (i - 1)))[byte]; + ++freq[val]; + for (int bit = 0; bit < 8; ++bit) { + ++totals[(val >> bit) & 1]; + } + } + } + hemming /= len; + fprintf(stderr, "\n"); +#if DEBUG + fprintf(stderr, + "\t- \x1B[1m%s\x1B[0m (%zu CPU cycles NOT OPTIMIZED):\n", + name, + clk); +#else + fprintf(stderr, "\t- \x1B[1m%s\x1B[0m (%zu CPU cycles):\n", name, clk); +#endif + fprintf(stderr, + "\t zeros / ones (bit frequency)\t%.05f\n", + ((float)1.0 * totals[0]) / totals[1]); + if (!(totals[0] < totals[1] + (total_bits / 20) && + totals[1] < totals[0] + (total_bits / 20))) + FIO_LOG_ERROR("randomness isn't random?"); + fprintf(stderr, + "\t avarage hemming distance\t%zu (should be: 14-18)\n", + (size_t)hemming); + /* expect avarage hemming distance of 25% == 16 bits */ + if (!(hemming >= 14 && hemming <= 18)) + FIO_LOG_ERROR("randomness isn't random (hemming distance failed)?"); + /* test chi-square ... I think */ + if (len * sizeof(*stream) > 2560) { + double n_r = (double)1.0 * ((len * sizeof(*stream)) / 256); + double chi_square = 0; + for (unsigned int i = 0; i < 256; ++i) { + double f = freq[i] - n_r; + chi_square += (f * f); + } + chi_square /= n_r; + double chi_square_r_abs = + (chi_square - 256 >= 0) ? chi_square - 256 : (256 - chi_square); + fprintf( + stderr, + "\t chi-sq. variation\t\t%.02lf - %s (expect <= %0.2lf)\n", + chi_square_r_abs, + ((chi_square_r_abs <= 2 * (sqrt(n_r))) + ? "good" + : ((chi_square_r_abs <= 3 * (sqrt(n_r))) ? "not amazing" + : "\x1B[1mBAD\x1B[0m")), + 2 * (sqrt(n_r))); + } +} + +FIO_SFUNC void FIO_NAME_TEST(stl, random)(void) { + fprintf(stderr, + "* Testing randomness " + "- bit frequency / hemming distance / chi-square.\n"); + const size_t test_len = (1UL << 21); + uint64_t *rs = + (uint64_t *)FIO_MEM_REALLOC(NULL, 0, sizeof(*rs) * test_len, 0); + clock_t start, end; + FIO_ASSERT_ALLOC(rs); + + rand(); /* warmup */ + if (sizeof(int) < sizeof(uint64_t)) { + start = clock(); + for (size_t i = 0; i < test_len; ++i) { + rs[i] = ((uint64_t)rand() << 32) | (uint64_t)rand(); + } + end = clock(); + } else { + start = clock(); + for (size_t i = 0; i < test_len; ++i) { + rs[i] = (uint64_t)rand(); + } + end = clock(); + } + FIO_NAME_TEST(stl, random_buffer) + (rs, test_len, "rand (system - naive, ignoring missing bits)", end - start); + + FIO_MEMSET(rs, 0, sizeof(*rs) * test_len); + { + if (RAND_MAX == ~(uint64_t)0ULL) { + /* RAND_MAX fills all bits */ + start = clock(); + for (size_t i = 0; i < test_len; ++i) { + rs[i] = (uint64_t)rand(); + } + end = clock(); + } else if (RAND_MAX >= (~(uint32_t)0UL)) { + /* RAND_MAX fill at least 32 bits per call */ + uint32_t *rs_adjusted = (uint32_t *)rs; + + start = clock(); + for (size_t i = 0; i < (test_len << 1); ++i) { + rs_adjusted[i] = (uint32_t)rand(); + } + end = clock(); + } else if (RAND_MAX >= (~(uint16_t)0U)) { + /* RAND_MAX fill at least 16 bits per call */ + uint16_t *rs_adjusted = (uint16_t *)rs; + + start = clock(); + for (size_t i = 0; i < (test_len << 2); ++i) { + rs_adjusted[i] = (uint16_t)rand(); + } + end = clock(); + } else { + /* assume RAND_MAX fill at least 8 bits per call */ + uint8_t *rs_adjusted = (uint8_t *)rs; + + start = clock(); + for (size_t i = 0; i < (test_len << 2); ++i) { + rs_adjusted[i] = (uint8_t)rand(); + } + end = clock(); + } + /* test RAND_MAX value */ + uint8_t rand_bits = 63; + while (rand_bits) { + if (RAND_MAX <= (~(0ULL)) >> rand_bits) + break; + --rand_bits; + } + rand_bits = 64 - rand_bits; + + char buffer[128] = {0}; + snprintf(buffer, + 128 - 14, + "rand (system - fixed, testing %d random bits)", + (int)rand_bits); + FIO_NAME_TEST(stl, random_buffer)(rs, test_len, buffer, end - start); + } + + FIO_MEMSET(rs, 0, sizeof(*rs) * test_len); + fio_rand64(); /* warmup */ + start = clock(); + for (size_t i = 0; i < test_len; ++i) { + rs[i] = fio_rand64(); + } + end = clock(); + FIO_NAME_TEST(stl, random_buffer)(rs, test_len, "fio_rand64", end - start); + FIO_MEMSET(rs, 0, sizeof(*rs) * test_len); + start = clock(); + fio_rand_bytes(rs, test_len * sizeof(*rs)); + end = clock(); + FIO_NAME_TEST(stl, random_buffer) + (rs, test_len, "fio_rand_bytes", end - start); + + fio_rand_feed2seed(rs, sizeof(*rs) * test_len); + FIO_MEM_FREE(rs, sizeof(*rs) * test_len); + fprintf(stderr, "\n"); + { + FIO_STR_INFO_TMP_VAR(data, 1124); + data.len = 1024; + for (size_t i = 0; i < data.len; ++i) + data.buf[i] = (char)(i & 255); + uint64_t h = fio_stable_hash(data.buf, 1024, 0); + FIO_LOG_DDEBUG2("Stable Hash Value: %p", (void *)h); + FIO_ASSERT(h == (uint64_t)0x5DC4DAD435547F67ULL, + "Stable Hash Value Error!"); + } +#if DEBUG + fprintf(stderr, + "\t- to compare CPU cycles, test randomness with optimization.\n\n"); +#endif /* DEBUG */ +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_SOCK Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_SOCK_TEST___H) +#define H___FIO_SOCK_TEST___H +#ifndef H___FIO_SOCK___H +#define FIO_SOCK +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, sock)(void) { + fprintf(stderr, + "* Testing socket helpers (FIO_SOCK) - partial tests only!\n"); + struct { + const char *address; + const char *port; + const char *msg; + uint16_t flag; + } server_tests[] = { + {"127.0.0.1", "9437", "TCP", FIO_SOCK_TCP}, +#ifdef AF_UNIX +#if defined(P_tmpdir) && !defined(__MINGW32__) + {P_tmpdir "/tmp_unix_testing_socket_facil_io.sock", + NULL, + "Unix", + FIO_SOCK_UNIX}, +#else + {"./tmp_unix_testing_socket_facil_io.sock", NULL, "Unix", FIO_SOCK_UNIX}, +#endif +#endif + /* accept doesn't work with UDP, not like this... UDP test is seperate */ + // {"127.0.0.1", "9437", "UDP", FIO_SOCK_UDP}, + {.address = NULL}, + }; + for (size_t i = 0; server_tests[i].address; ++i) { + short ev = (short)-1; + errno = 0; + fprintf(stderr, "* Testing %s socket API\n", server_tests[i].msg); + int srv = fio_sock_open(server_tests[i].address, + server_tests[i].port, + server_tests[i].flag | FIO_SOCK_SERVER); + FIO_ASSERT(srv != -1, "server socket failed to open: %s", strerror(errno)); + ev = fio_sock_wait_io(-1, POLLIN | POLLOUT, 0); + FIO_ASSERT(!ev, "no error should have been returned for IO -1 (%d)", ev); + ev = fio_sock_wait_io(srv, POLLIN, 0); + FIO_ASSERT(!ev, "no events should have been returned (%d)", ev); + int cl = fio_sock_open(server_tests[i].address, + server_tests[i].port, + server_tests[i].flag | FIO_SOCK_CLIENT); + FIO_ASSERT(FIO_SOCK_FD_ISVALID(cl), + "client socket failed to open (%d)", + cl); + ev = fio_sock_wait_io(cl, POLLIN /* | POLLOUT <= OS dependent */, 0); + FIO_ASSERT(!ev, + "no events should have been returned for connecting client(%d)", + ev); + ev = fio_sock_wait_io(srv, POLLIN, 200); + FIO_ASSERT((ev & POLLIN), + "incoming connection should have been detected (%d : %u)", + srv, + (unsigned)ev); + int accepted = fio_sock_accept(srv, NULL, NULL); + FIO_ASSERT(FIO_SOCK_FD_ISVALID(accepted), + "accepted socket failed to open (%zd)", + (ssize_t)accepted); + ev = fio_sock_wait_io(cl, POLLIN | POLLOUT, 0); + FIO_ASSERT(ev == POLLOUT, + "POLLOUT should have been returned for connected client(%d)", + ev); + ev = fio_sock_wait_io(accepted, POLLIN | POLLOUT, 0); + FIO_ASSERT(ev == POLLOUT, + "POLLOUT should have been returned for connected client 2(%d)", + ev); + if (fio_sock_write(accepted, "hello", 5) > 0) { + // wait for read + FIO_ASSERT( + fio_sock_wait_io(cl, POLLIN, 10) != -1 && + ((fio_sock_wait_io(cl, POLLIN | POLLOUT, 0) & POLLIN)), + "fio_sock_wait_io should have returned a POLLIN event for client."); + { + char buf[64]; + errno = 0; + FIO_ASSERT(fio_sock_read(cl, buf, 64) > 0, + "Read should have read some data...\n\t" + "error: %s", + strerror(errno)); + } + FIO_ASSERT(!fio_sock_wait_io(cl, POLLIN, 0), + "No events should have occurred here! (%zu)", + ev); + } else { + FIO_ASSERT(0, + "send(fd:%ld) failed! error: %s", + accepted, + strerror(errno)); + } + fio_sock_close(accepted); + fio_sock_close(cl); + fio_sock_close(srv); + FIO_ASSERT((fio_sock_wait_io(cl, POLLIN | POLLOUT, 0) & POLLNVAL), + "POLLNVAL should have been returned for closed socket (%d & %d) " + "(POLLERR == %d)", + fio_sock_wait_io(cl, POLLIN | POLLOUT, 0), + (int)POLLNVAL, + (int)POLLERR); +#ifdef AF_UNIX + if (FIO_SOCK_UNIX == server_tests[i].flag) + unlink(server_tests[i].address); +#endif + } + { + /* UDP semi test */ + fprintf(stderr, "* Testing UDP socket (abbreviated test)\n"); + int srv = + fio_sock_open("127.0.0.1", "9437", FIO_SOCK_UDP | FIO_SOCK_SERVER); + int n = 0; + socklen_t sn = sizeof(n); + if (-1 != getsockopt(srv, SOL_SOCKET, SO_RCVBUF, (void *)&n, &sn) && + sizeof(n) == sn) + fprintf(stderr, "\t- UDP default receive buffer is %d bytes\n", n); + n = 32 * 1024 * 1024; /* try for 32Mb */ + sn = sizeof(n); + while (setsockopt(srv, SOL_SOCKET, SO_RCVBUF, (void *)&n, sn) == -1) { + /* failed - repeat attempt at 0.5Mb interval */ + if (n >= (1024 * 1024)) // OS may have returned max value + n -= 512 * 1024; + else + break; + } + do { + n += 16 * 1024; /* at 16Kb at a time */ + if (n >= 32 * 1024 * 1024) + break; + } while (setsockopt(srv, SOL_SOCKET, SO_RCVBUF, (void *)&n, sn) != -1); + if (-1 != getsockopt(srv, SOL_SOCKET, SO_RCVBUF, (void *)&n, &sn) && + sizeof(n) == sn) + fprintf(stderr, "\t- UDP receive buffer could be set to %d bytes\n", n); + FIO_ASSERT(srv != -1, + "Couldn't open UDP server socket: %s", + strerror(errno)); + FIO_LOG_INFO("Opening client UDP socket."); + int cl = fio_sock_open("127.0.0.1", "9437", FIO_SOCK_UDP | FIO_SOCK_CLIENT); + FIO_ASSERT(cl != -1, + "Couldn't open UDP client socket: %s", + strerror(errno)); + FIO_LOG_INFO("Starting UDP roundtrip."); + FIO_ASSERT(fio_sock_write(cl, "hello", 5) != -1, + "couldn't send datagram from client"); + char buf[64]; + FIO_LOG_INFO("Receiving UDP msg."); + FIO_ASSERT(recvfrom(srv, buf, 64, 0, NULL, NULL) != -1, + "couldn't read datagram"); + FIO_ASSERT(!memcmp(buf, "hello", 5), "transmission error"); + FIO_LOG_INFO("cleaning up UDP sockets."); + fio_sock_close(srv); + fio_sock_close(cl); + } +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Core Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_SORT_TEST___H) +#define H___FIO_SORT_TEST___H +#define FIO_SORT_TYPE size_t +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE + +FIO_SFUNC int FIO_NAME_TEST(stl, qsort_cmp)(size_t *a, size_t *b) { + return (int)(a[0] - b[0]); +} + +FIO_SFUNC void FIO_NAME_TEST(stl, sort)(void) { + fprintf(stderr, "* Testing facil.io array sort helper:\n"); + { /* test insert sort of short array */ + size_t mixed[] = {19, 23, 28, 21, 3, 10, 7, 2, 13, 4, 15, + 29, 26, 16, 24, 22, 11, 5, 14, 31, 25, 8, + 12, 18, 20, 17, 1, 27, 9, 0, 6, 30}; + size_t ordered[] = {19, 23, 28, 21, 3, 10, 7, 2, 13, 4, 15, + 29, 26, 16, 24, 22, 11, 5, 14, 31, 25, 8, + 12, 18, 20, 17, 1, 27, 9, 0, 6, 30}; + const size_t len = + (sizeof(ordered) / sizeof(ordered[0])) > FIO_SORT_THRESHOLD + ? FIO_SORT_THRESHOLD + : (sizeof(ordered) / sizeof(ordered[0])); + qsort(ordered, + len, + sizeof(ordered[0]), + (int (*)(const void *, const void *))FIO_NAME_TEST(stl, qsort_cmp)); + size_t_vec_isort(mixed, len); + FIO_ASSERT(!memcmp(mixed, ordered, sizeof(*ordered) * len), + "short sort failed!"); + clock_t start, end; + start = clock(); + for (size_t i = 0; i < (1UL << 16); ++i) { + FIO_COMPILER_GUARD; + size_t_vec_sort(mixed, len); + } + end = clock(); + fprintf(stderr, + "\t* facil.io small sorted test cycles: %zu\n", + (size_t)(end - start)); + start = clock(); + for (size_t i = 0; i < (1UL << 16); ++i) { + FIO_COMPILER_GUARD; + qsort(mixed, + len, + sizeof(mixed[0]), + (int (*)(const void *, const void *))FIO_NAME_TEST(stl, qsort_cmp)); + } + end = clock(); + fprintf(stderr, + "\t* clib small sorted test cycles: %zu\n", + (size_t)(end - start)); + } + { /* test quick sort of an array with (1ULL << 18) elements */ + const size_t len = (1ULL << 18); + size_t *mem = + (size_t *)FIO_MEM_REALLOC(NULL, 0, (sizeof(*mem) * (len << 1)), 0); + for (size_t i = 0; i < len; ++i) { + mem[i] = mem[len + i] = (size_t)rand(); + } + size_t_vec_sort(mem, len); + qsort(mem + len, + len, + sizeof(mem[0]), + (int (*)(const void *, const void *))FIO_NAME_TEST(stl, qsort_cmp)); + if (memcmp(mem, mem + len, (sizeof(mem[0]) * len))) { + size_t i = 0; + while (mem[i] == mem[len + i] && i < len) + ++i; + FIO_ASSERT(0, "fio_sort != clib qsort first error at index %zu", i); + } + clock_t start, end, fio_clk = 0, lib_clk = 0; + for (int count = 0; count < 8; ++count) { + for (size_t i = 0; i < len; ++i) { + mem[i] = mem[len + i] = (size_t)rand(); + } + start = clock(); + size_t_vec_sort(mem, len); + end = clock(); + fio_clk += end - start; + start = clock(); + qsort(mem + len, + len, + sizeof(mem[0]), + (int (*)(const void *, const void *))FIO_NAME_TEST(stl, qsort_cmp)); + end = clock(); + lib_clk += end - start; + FIO_ASSERT(!memcmp(mem, mem + len, (sizeof(mem[0]) * len)), + "fio_sort != clib qsort (iteration %zu)", + count); + } + FIO_MEM_FREE(mem, (sizeof(*mem) * (len << 1))); + + fprintf(stderr, + "\t* facil.io random quick sort test cycles: %zu\n", + (size_t)fio_clk); + fprintf(stderr, + "\t* clib random quick sort test cycles: %zu\n", + (size_t)lib_clk); + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_STATE Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_STATE_TEST___H) +#define H___FIO_STATE_TEST___H +#ifndef H___FIO_STATE___H +#define FIO_STATE +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +static size_t FIO_NAME_TEST(stl, state_task_counter) = 0; +FIO_SFUNC void FIO_NAME_TEST(stl, state_task)(void *arg) { + size_t *i = (size_t *)arg; + ++i[0]; +} +FIO_SFUNC void FIO_NAME_TEST(stl, state_task_global)(void *arg) { + (void)arg; + ++FIO_NAME_TEST(stl, state_task_counter); +} +FIO_SFUNC void FIO_NAME_TEST(stl, state)(void) { + fprintf(stderr, "* Testing state callback API.\n"); + size_t count = 0; + for (size_t i = 0; i < 1024; ++i) { + fio_state_callback_add(FIO_CALL_RESERVED1, + FIO_NAME_TEST(stl, state_task), + &count); + fio_state_callback_add(FIO_CALL_RESERVED1, + FIO_NAME_TEST(stl, state_task_global), + (void *)i); + } + FIO_ASSERT(!count && !FIO_NAME_TEST(stl, state_task_counter), + "callbacks should NOT have been called yet"); + fio_state_callback_force(FIO_CALL_RESERVED1); + FIO_ASSERT(count == 1, "count error for local counter callback (%zu)", count); + FIO_ASSERT(FIO_NAME_TEST(stl, state_task_counter) == 1024, + "count error for global counter callback (%zu)", + FIO_NAME_TEST(stl, state_task_counter)); + for (size_t i = 0; i < 1024; ++i) { + fio_state_callback_remove(FIO_CALL_RESERVED1, + FIO_NAME_TEST(stl, state_task), + &count); + fio_state_callback_remove(FIO_CALL_RESERVED1, + FIO_NAME_TEST(stl, state_task_global), + (void *)i); + } + fio_state_callback_force(FIO_CALL_RESERVED1); + FIO_ASSERT(count == 1, + "count error for local counter callback (%zu) - not removed?", + count); + FIO_ASSERT(FIO_NAME_TEST(stl, state_task_counter) == 1024, + "count error for global counter callback (%zu) - not removed?", + FIO_NAME_TEST(stl, state_task_counter)); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_STREAM Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_STREAM_TEST___H) +#define H___FIO_STREAM_TEST___H +#ifndef H___FIO_STREAM___H +#define FIO_STREAM +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC size_t FIO_NAME_TEST(stl, stream___noop_dealloc_count) = 0; +FIO_SFUNC void FIO_NAME_TEST(stl, stream___noop_dealloc)(void *ignr_) { + fio_atomic_add(&FIO_NAME_TEST(stl, stream___noop_dealloc_count), 1); + (void)ignr_; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, stream)(void) { + char *const str = + (char *)"My Hello World string should be long enough so it can be used " + "for testing the stream functionality in the facil.io stream " + "module. The stream moduule takes strings and failes and places " + "them (by reference / copy) into a linked list of objects. When " + "data is requested from the stream, the stream will either copy " + "the data to a pre-allocated buffer or it may update the link to " + "it points to its own internal buffer (avoiding a copy when " + "possible)."; + fio_stream_s s = FIO_STREAM_INIT(s); + char mem[4000]; + char *buf = mem; + size_t len = 4000; + size_t expect_dealloc = FIO_NAME_TEST(stl, stream___noop_dealloc_count); + + fprintf(stderr, "* Testing fio_stream for streaming buffer storage.\n"); + fio_stream_add( + &s, + fio_stream_pack_data(str, + 11, + 3, + 1, + FIO_NAME_TEST(stl, stream___noop_dealloc))); + ++expect_dealloc; + FIO_ASSERT(fio_stream_any(&s), + "stream is empty after `fio_stream_add` (data, copy)"); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "copying a packet should deallocate the original"); + for (int i = 0; i < 3; ++i) { + /* test that read operrations are immutable */ + buf = mem; + len = 4000; + + fio_stream_read(&s, &buf, &len); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == + expect_dealloc, + "reading a packet shouldn't deallocate anything"); + FIO_ASSERT(len == 11, + "fio_stream_read didn't read all data from stream? (%zu)", + len); + FIO_ASSERT(!memcmp(str + 3, buf, len), + "fio_stream_read data error? (%.*s)", + (int)len, + buf); + FIO_ASSERT_DEBUG( + buf != mem, + "fio_stream_read should have been performed with zero-copy"); + } + fio_stream_advance(&s, len); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "advancing an embedded packet shouldn't deallocate anything"); + FIO_ASSERT( + !fio_stream_any(&s), + "after advance, at this point, the stream should have been consumed."); + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + FIO_ASSERT( + !buf && !len, + "reading from an empty stream should set buf and len to NULL and zero."); + fio_stream_destroy(&s); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "destroying an empty stream shouldn't deallocate anything"); + FIO_ASSERT(!fio_stream_any(&s), "destroyed stream should be empty."); + + fio_stream_add(&s, fio_stream_pack_data(str, 11, 0, 1, NULL)); + fio_stream_add( + &s, + fio_stream_pack_data(str, + 49, + 11, + 0, + FIO_NAME_TEST(stl, stream___noop_dealloc))); + fio_stream_add(&s, fio_stream_pack_data(str, 20, 60, 0, NULL)); + expect_dealloc += (49 < FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN); + FIO_ASSERT(fio_stream_any(&s), "stream with data shouldn't be empty."); + FIO_ASSERT(fio_stream_length(&s) == 80, "stream length error."); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "adding a stream shouldn't deallocate it."); + + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + + FIO_ASSERT(len == 80, + "fio_stream_read didn't read all data from stream(2)? (%zu)", + len); + FIO_ASSERT(!memcmp(str, buf, len), + "fio_stream_read data error? (%.*s)", + (int)len, + buf); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "reading a stream shouldn't deallocate any packets."); + + buf = mem; + len = 8; + fio_stream_read(&s, &buf, &len); + + FIO_ASSERT(len < 80, + "fio_stream_read didn't perform a partial read? (%zu)", + len); + FIO_ASSERT(!memcmp(str, buf, len), + "fio_stream_read partial read data error? (%.*s)", + (int)len, + buf); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "failing to read a stream shouldn't deallocate any packets."); + + fio_stream_advance(&s, 20); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "partial advancing shouldn't deallocate any packets."); + FIO_ASSERT(fio_stream_length(&s) == 60, "stream length error (2)."); + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + FIO_ASSERT(len == 60, + "fio_stream_read didn't read all data from stream(3)? (%zu)", + len); + FIO_ASSERT(!memcmp(str + 20, buf, len), + "fio_stream_read data error? (%.*s)", + (int)len, + buf); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "reading shouldn't deallocate packets the head packet."); + + fio_stream_add(&s, fio_stream_pack_fd(open(__FILE__, O_RDONLY), 20, 0, 0)); + FIO_ASSERT(fio_stream_length(&s) == 80, "stream length error (3)."); + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + FIO_ASSERT(len == 80, + "fio_stream_read didn't read all data from stream(4)? (%zu)", + len); + FIO_ASSERT(!memcmp("/* *****************", buf + 60, 20), + "fio_stream_read file read data error?\n%.*s", + (int)len, + buf); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "reading more than one packet shouldn't deallocate anything."); + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + FIO_ASSERT(len == 80, + "fio_stream_read didn't (re)read all data from stream(5)? (%zu)", + len); + FIO_ASSERT(!memcmp("/* *****************", buf + 60, 20), + "fio_stream_read file (re)read data error? (%.*s)", + (int)len, + buf); + + fio_stream_destroy(&s); + expect_dealloc += (49 >= FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN); + + FIO_ASSERT(!fio_stream_any(&s), "destroyed stream should be empty."); + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "destroying a stream should deallocate it's packets."); + fio_stream_add( + &s, + fio_stream_pack_data(str, + 49, + 11, + 0, + FIO_NAME_TEST(stl, stream___noop_dealloc))); + buf = mem; + len = 4000; + fio_stream_read(&s, &buf, &len); + FIO_ASSERT(len == 49, + "fio_stream_read didn't read all data from stream? (%zu)", + len); + FIO_ASSERT(!memcmp(str + 11, buf, len), + "fio_stream_read data error? (%.*s)", + (int)len, + buf); + fio_stream_advance(&s, 80); + ++expect_dealloc; + FIO_ASSERT(FIO_NAME_TEST(stl, stream___noop_dealloc_count) == expect_dealloc, + "partial advancing shouldn't deallocate any packets."); + FIO_ASSERT(!fio_stream_any(&s), "stream should be empty at this point."); + FIO_ASSERT(!fio_stream_length(&s), + "stream length should be zero at this point."); + fio_stream_destroy(&s); +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_STR Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_STR_TEST___H) +#define H___FIO_STR_TEST___H +#ifndef H___FIO_STR___H +#define FIO_STR +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +FIO_SFUNC size_t FIO_NAME_TEST(stl, string_core_ltoa)(char *buf, + int64_t i, + uint8_t base) { + fio_str_info_s s = FIO_STR_INFO3(buf, 0, 1024); + if (base == 16) { + fio_string_write_hex(&s, NULL, i); + return s.len; + } + if (base == 2) { + fio_string_write_bin(&s, NULL, i); + return s.len; + } + fio_string_write_i(&s, NULL, i); + return s.len; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, string_core_helpers)(void) { + fprintf(stderr, "* Testing Core String API.\n"); + { /* test basic fio_string_write functions. */ + fprintf(stderr, "* Testing Core String writing functions.\n"); + char mem[16]; + fio_str_info_s buf = FIO_STR_INFO3(mem, 0, 16); + FIO_ASSERT(!fio_string_write(&buf, NULL, "World", 5), + "non-truncated return should be zero for fio_string_write"); + FIO_ASSERT(mem == buf.buf && buf.len == 5 && !memcmp(buf.buf, "World", 6), + "fio_string_write failed!"); + FIO_ASSERT(!fio_string_replace(&buf, NULL, 0, 0, "Hello ", 6), + "non-truncated return should be zero for fio_string_replace"); + FIO_ASSERT(mem == buf.buf && buf.len == 11 && + !memcmp(buf.buf, "Hello World", 12), + "fio_string_replace failed to perform insert (index[0])!"); + fio_string_write(&buf, NULL, "Hello World", 11); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "Hello WorldHell", 16), + "fio_string_write failed to truncate!"); + fio_string_replace(&buf, NULL, 0, 5, "Hola", 4); + FIO_ASSERT(mem == buf.buf && buf.len == 14 && + !memcmp(buf.buf, "Hola WorldHell", 15), + "fio_string_replace at index 0 failed!"); + FIO_ASSERT(!fio_string_replace(&buf, NULL, 5, 9, "World", 5), + "non-truncated return should be zero for fio_string_replace"); + FIO_ASSERT(mem == buf.buf && buf.len == 10 && + !memcmp(buf.buf, "Hola World", 11), + "fio_string_replace end overwrite failed!"); + fio_string_replace(&buf, NULL, 5, 0, "my beautiful", 12); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "Hola my beautif", 16), + "fio_string_replace failed to truncate!"); + FIO_ASSERT(fio_string_replace(&buf, NULL, -11, 2, "big", 3), + "truncation should return non-zero on fio_string_replace."); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "Hola big beauti", 16), + "fio_string_replace failed to truncate (negative index)!"); + buf = FIO_STR_INFO3(mem, 0, 16); + fio_string_printf(&buf, NULL, "I think %d is the best answer", 42); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "I think 42 is t", 16), + "fio_string_printf failed to truncate!"); + + FIO_MEMSET(mem, 0, 16); + buf = FIO_STR_INFO3(mem, 0, 16); + FIO_ASSERT( + fio_string_write2(&buf, + NULL, + FIO_STRING_WRITE_STR2((char *)"I think ", 8), + FIO_STRING_WRITE_NUM(42), + FIO_STRING_WRITE_STR1((char *)" is the best answer")), + "truncation return value should be non-zero for fio_string_write2."); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "I think 42 is t", 16), + "fio_string_write2 failed to truncate!"); + FIO_MEMSET(mem, 0, 16); + buf = FIO_STR_INFO3(mem, 0, 16); + FIO_ASSERT( + fio_string_write2(&buf, + NULL, + FIO_STRING_WRITE_STR2((char *)"I think ", 8), + FIO_STRING_WRITE_HEX(42), + FIO_STRING_WRITE_STR1((char *)" is the best answer")), + "truncation return value should be non-zero for fio_string_write2."); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "I think 2A is t", 16), + "fio_string_write2 failed to truncate (hex)!"); + FIO_MEMSET(mem, 0, 16); + buf = FIO_STR_INFO3(mem, 0, 16); + FIO_ASSERT( + fio_string_write2(&buf, + NULL, + FIO_STRING_WRITE_STR2((char *)"I Think ", 8), + FIO_STRING_WRITE_FLOAT(42.42), + FIO_STRING_WRITE_STR1((char *)" is the best answer")), + "truncation return value should be non-zero for fio_string_write2."); + FIO_ASSERT(mem == buf.buf && buf.len == 15 && + !memcmp(buf.buf, "I Think 42.42 i", 16), + "fio_string_write2 failed to truncate (float)!"); + buf = FIO_STR_INFO3(mem, 0, 16); + fio_string_write2(&buf, + NULL, + FIO_STRING_WRITE_STR2((char *)"I think ", 8), + FIO_STRING_WRITE_BIN(-1LL), + FIO_STRING_WRITE_STR1((char *)" is the best answer")); + FIO_ASSERT(mem == buf.buf && buf.len == 8 && + !memcmp(buf.buf, "I think ", 8), + "fio_string_write2 failed to truncate (bin)!"); + } + { /* test numeral fio_string_write functions. */ + char mem[32]; + fio_str_info_s buf = FIO_STR_INFO3(mem, 0, 32); + FIO_ASSERT(!fio_string_write_i(&buf, NULL, 0), + "fio_string_write_i returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 1 && !memcmp(buf.buf, "0", 2), + "fio_string_write_i didn't print 0!"); + FIO_ASSERT(!fio_string_write_i(&buf, NULL, -42), + "fio_string_write_i returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 4 && !memcmp(buf.buf, "0-42", 5), + "fio_string_write_i didn't print -24!"); + buf = FIO_STR_INFO3(mem, 0, 32); + FIO_ASSERT(!fio_string_write_u(&buf, NULL, 0), + "fio_string_write_u returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 1 && !memcmp(buf.buf, "0", 2), + "fio_string_write_u didn't print 0!"); + FIO_ASSERT(!fio_string_write_u(&buf, NULL, -42LL), + "fio_string_write_u returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 21 && + !memcmp(buf.buf, "018446744073709551574", 21), + "fio_string_write_u didn't print -24!"); + buf = FIO_STR_INFO3(mem, 0, 32); + FIO_ASSERT(!fio_string_write_hex(&buf, NULL, 0), + "fio_string_write_hex returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 2 && !memcmp(buf.buf, "00", 3), + "fio_string_write_hex didn't print 0!"); + FIO_ASSERT(!fio_string_write_hex(&buf, NULL, 42), + "fio_string_write_hex returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 4 && !memcmp(buf.buf, "002A", 5), + "fio_string_write_hex didn't print 2A!"); + buf = FIO_STR_INFO3(mem, 0, 32); + FIO_ASSERT(!fio_string_write_bin(&buf, NULL, 0), + "fio_string_write_bin returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 1 && !memcmp(buf.buf, "0", 2), + "fio_string_write_bin didn't print 0!"); + FIO_ASSERT(!fio_string_write_bin(&buf, NULL, 16), + "fio_string_write_bin returned error!"); + FIO_ASSERT(mem == buf.buf && buf.len == 7 && !memcmp(buf.buf, "0010000", 8), + "fio_string_write_bin didn't print 16!"); + } + { /* Testing UTF-8 */ + fprintf(stderr, "* Testing UTF-8 support.\n"); + /* 4B heart, 3B heart, 3B heart resizer, 4B heart, 2B f, 1B Z */ + const char *utf8_sample = + "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95\xc6\x92Z\0"; + fio_str_info_s utf8 = FIO_STR_INFO1((char *)utf8_sample); + + FIO_ASSERT(fio_string_utf8_valid(utf8), + "fio_string_utf8_valid failed on valid code"); + FIO_ASSERT(fio_string_utf8_len(utf8) == 6, /* manual knowledge */ + "fio_string_utf8_len failed with valid UTF-8 %zu != 6", + fio_string_utf8_len(utf8)); + intptr_t pos = -4; + size_t len = 2; + FIO_ASSERT(fio_string_utf8_select(utf8, &pos, &len) == 0, + "`fio_string_utf8_select` returned error for negative pos on " + "UTF-8 data! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == (intptr_t)utf8.len - 10, + "`fio_string_utf8_select` error, negative position invalid on " + "UTF-8 data! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == 7, /* heart + math 'f' */ + "`fio_string_utf8_select` error, truncated length invalid on " + "UTF-8 data! (%zd)", + (ssize_t)len); + pos = 1; + len = 20; + FIO_ASSERT(fio_string_utf8_select(utf8, &pos, &len) == 0, + "`fio_string_utf8_select` returned error on UTF-8 data! " + "(%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == 4, + "`fio_string_utf8_select` error, position invalid on " + "UTF-8 data! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == utf8.len - 4, + "`fio_string_utf8_select` error, length invalid on " + "UTF-8 data! (%zd != %zu)", + (ssize_t)len, + utf8.len - 4); + pos = 1; + len = 3; + FIO_ASSERT(fio_string_utf8_select(utf8, &pos, &len) == 0, + "`fio_string_utf8_select` returned error on UTF-8 data " + "(2)! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(len == 10, /* 3 UTF-8 chars: 4 byte + 4 byte + 2 byte == 10 */ + "`fio_string_utf8_select` error, length invalid on UTF-8 data! " + "(%zd)", + (ssize_t)len); + /* TODO! test fio_string_utf8_valid speed. */ + } + { /* testing C / JSON style escaping */ + fprintf(stderr, "* Testing C / JSON style character (un)escaping.\n"); + char mem[2048]; + fio_str_info_s unescaped = FIO_STR_INFO3(mem, 0, 512); + fio_str_info_s decoded = FIO_STR_INFO3(mem + 512, 0, 512); + fio_str_info_s encoded = FIO_STR_INFO3(mem + 1024, 0, 1024); + const char *utf8_sample = /* three hearts, small-big-small*/ + "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95\xc6\x92Z"; + // "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95"; + FIO_ASSERT(!fio_string_write(&unescaped, + NULL, + utf8_sample, + FIO_STRLEN(utf8_sample)), + "Couldn't write UTF-8 example."); + for (int i = 1; i < 256; ++i) { + uint8_t c = i; + FIO_ASSERT(!fio_string_write(&unescaped, NULL, &c, 1), + "write returned an error"); + } + FIO_ASSERT( + !fio_string_write_escape(&encoded, NULL, unescaped.buf, unescaped.len), + "write escape returned an error"); + FIO_ASSERT( + !fio_string_write_unescape(&decoded, NULL, encoded.buf, encoded.len), + "write unescape returned an error"); + FIO_ASSERT(encoded.len, "JSON encoding failed"); + FIO_ASSERT(decoded.buf == mem + 512 && encoded.buf == mem + 1024, + "C escaping unexpected side-effects!"); + FIO_ASSERT(!memcmp(encoded.buf, utf8_sample, FIO_STRLEN(utf8_sample)), + "valid UTF-8 data shouldn't be escaped:\n%.*s\n%s", + (int)encoded.len, + encoded.buf, + decoded.buf); + FIO_ASSERT(unescaped.len == decoded.len, + "C escaping roundtrip length error, %zu != %zu (%zu - " + "%zu):\n%.127s\n\n!=>\n\n%.127s", + unescaped.len, + decoded.len, + decoded.len, + encoded.len, + encoded.buf, + decoded.buf); + FIO_ASSERT(!memcmp(unescaped.buf, decoded.buf, unescaped.len), + "C escaping round-trip failed:\n %s", + decoded.buf); + } + { /* testing Base64 Support */ + fprintf(stderr, "* Testing Base64 encoding / decoding.\n"); + char mem[2048]; + fio_str_info_s original = FIO_STR_INFO3(mem, 0, 512); + fio_str_info_s decoded = FIO_STR_INFO3(mem + 512, 0, 512); + fio_str_info_s encoded = FIO_STR_INFO3(mem + 1024, 0, 512); + fio_string_write(&original, + NULL, + "Hello World, this is the voice of peace:)", + 41); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + FIO_ASSERT(!fio_string_write(&original, NULL, &c, 1), + "write returned an error"); + } + FIO_ASSERT(!fio_string_write_base64enc(&encoded, + NULL, + original.buf, + original.len, + 1), + "base64 write escape returned an error"); + FIO_ASSERT( + !fio_string_write_base64dec(&decoded, NULL, encoded.buf, encoded.len), + "base64 write unescape returned an error"); + + FIO_ASSERT(encoded.len, "Base64 encoding failed"); + FIO_ASSERT(decoded.len < encoded.len, + "Base64 decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(original.len == decoded.len, + "Base64 roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + original.len, + decoded.len, + decoded.len, + encoded.len, + decoded.buf); + FIO_ASSERT(!memcmp(original.buf, decoded.buf, original.len), + "Base64 round-trip failed:\n %s", + decoded.buf); + } + { /* testing Base32 Support */ + fprintf(stderr, "* Testing Base32 encoding / decoding.\n"); + char mem[2048]; + fio_str_info_s original = FIO_STR_INFO3(mem, 0, 512); + fio_str_info_s decoded = FIO_STR_INFO3(mem + 512, 0, 512); + fio_str_info_s encoded = FIO_STR_INFO3(mem + 1024, 0, 512); + fio_string_write(&original, + NULL, + "Hello World, this is the voice of peace:)", + 41); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + FIO_ASSERT(!fio_string_write(&original, NULL, &c, 1), + "write returned an error"); + } + FIO_ASSERT( + !fio_string_write_base32enc(&encoded, NULL, original.buf, original.len), + "base32 write escape returned an error"); + FIO_ASSERT( + !fio_string_write_base32dec(&decoded, NULL, encoded.buf, encoded.len), + "base32 write unescape returned an error"); + + FIO_ASSERT(encoded.len, "Base32 encoding failed"); + FIO_ASSERT(decoded.len < encoded.len, + "Base32 decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(original.len == decoded.len, + "Base32 roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + original.len, + decoded.len, + decoded.len, + encoded.len, + decoded.buf); + FIO_ASSERT(!memcmp(original.buf, decoded.buf, original.len), + "Base32 round-trip failed: (%zu vs. %zu bytes, encoded using " + "%zu bytes)\n %s", + original.len, + decoded.len, + encoded.len, + decoded.buf); + } + { /* testing URL encoding Support */ + fprintf(stderr, "* Testing URL (percent) encoding / decoding.\n"); + char mem[2048]; + for (size_t i = 0; i < 256; ++i) { + mem[i] = i; + } + fio_str_info_s original = FIO_STR_INFO3(mem, 256, 256); + fio_str_info_s encoded = FIO_STR_INFO3(mem + 256, 0, 1024); + fio_str_info_s decoded = FIO_STR_INFO3(mem + 1024 + 256, 0, 257); + FIO_ASSERT( + !fio_string_write_url_enc(&encoded, NULL, mem, 256), + "fio_string_write_url_enc reported an error where none was expected!"); + FIO_ASSERT(encoded.len > 256, "fio_string_write_url_enc did nothing?"); + FIO_ASSERT( + !fio_string_write_url_dec(&decoded, NULL, encoded.buf, encoded.len), + "fio_string_write_url_dec reported an error where none was expected!"); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(original, decoded), + "fio_string_write_url_enc/dec roundtrip failed!"); + } + { /* testing HTML escaping / un-escaping Support */ + fprintf(stderr, "* Testing HTML escaping / un-escaping (basic support)\n"); + char mem[3072]; + fio_str_info_s original = FIO_STR_INFO3(mem, 127, 256); + fio_str_info_s escaped = FIO_STR_INFO3(mem + 256, 0, 2048); + fio_str_info_s unescaped = FIO_STR_INFO3(mem + 2560, 0, 512); + for (size_t i = 0; i < 127; ++i) + mem[i] = (char)i; + FIO_ASSERT(!fio_string_write_html_escape(&escaped, + NULL, + original.buf, + original.len), + "fio_string_write_html_escape returned an error"); + for (size_t i = 0; i < 2; ++i) { + FIO_ASSERT(!fio_string_write_html_unescape(&unescaped, + NULL, + escaped.buf, + escaped.len), + "fio_string_write_html_unescape returned an error"); + FIO_ASSERT(!FIO_STR_INFO_IS_EQ(original, escaped), + "fio_string_write_html_escape did nothing!"); + FIO_ASSERT(FIO_STR_INFO_IS_EQ(original, unescaped), + "fio_string_write_html_(un)escape roundtrip failed!"); + original.len = 0; + fio_string_write(&original, NULL, "ÿ", FIO_STRLEN("ÿ")); + original.buf[original.len++] = (char)0xE2; /* euro sign (UTF-8) */ + original.buf[original.len++] = (char)0x82; + original.buf[original.len++] = (char)0xAC; + original.buf[original.len++] = (char)0xC2; /* pounds (UTF-8) */ + original.buf[original.len++] = (char)0xA3; + original.buf[original.len++] = (char)0xC2; /* cents (UTF-8) */ + original.buf[original.len++] = (char)0xA2; + original.buf[original.len++] = (char)0xC2; /* copyright (UTF-8) */ + original.buf[original.len++] = (char)0xA9; + original.buf[original.len++] = (char)0xC2; /* trademark (UTF-8) */ + original.buf[original.len++] = (char)0xAE; + original.buf[original.len++] = (char)0xC2; /* nbsp; (UTF-8) */ + original.buf[original.len++] = (char)0xA0; + original.buf[original.len++] = (char)0x26; /* & */ + original.buf[original.len++] = (char)0x27; /* ' */ + original.buf[original.len++] = (char)0x22; /* " */ + original.buf[original.len] = 0; + unescaped.len = escaped.len = 0; + fio_string_write( + &escaped, + NULL, + "ÿ&eUro;£&cenT&Copy;® &'"", + 56); + } + original.buf[original.len] = 0; + unescaped.len = escaped.len = 0; + escaped.capa = 8; + FIO_ASSERT(fio_string_write_html_escape(&escaped, + NULL, + original.buf, + original.len), + "fio_string_write_html_escape should error on capacity"); + } + { /* Comparison testing */ + fprintf(stderr, "* Testing comparison\n"); + FIO_ASSERT(fio_string_is_greater(FIO_STR_INFO1((char *)"A"), + FIO_STR_INFO1((char *)"")), + "fio_string_is_greater failed for A vs __"); + FIO_ASSERT(fio_string_is_greater(FIO_STR_INFO1((char *)"hello world"), + FIO_STR_INFO1((char *)"hello worl")), + "fio_string_is_greater failed for hello worl(d)"); + FIO_ASSERT(fio_string_is_greater(FIO_STR_INFO1((char *)"01234567"), + FIO_STR_INFO1((char *)"012345664")), + "fio_string_is_greater failed for 01234567"); + FIO_ASSERT(!fio_string_is_greater(FIO_STR_INFO1((char *)""), + FIO_STR_INFO1((char *)"A")), + "fio_string_is_greater failed for A inv"); + FIO_ASSERT(!fio_string_is_greater(FIO_STR_INFO1((char *)"hello worl"), + FIO_STR_INFO1((char *)"hello world")), + "fio_string_is_greater failed for hello worl(d) inv"); + FIO_ASSERT(!fio_string_is_greater(FIO_STR_INFO1((char *)"012345664"), + FIO_STR_INFO1((char *)"01234567")), + "fio_string_is_greater failed for 01234567 inv"); + FIO_ASSERT(!fio_string_is_greater(FIO_STR_INFO1((char *)"Hzzzzzzzzzz"), + FIO_STR_INFO1((char *)"hello world")), + "fio_string_is_greater failed for Hello world"); + } + { /* testing fio_bstr helpers */ + fprintf(stderr, "* Testing fio_bstr helpers (micro test).\n"); + char *str = fio_bstr_write(NULL, "Hello", 5); + FIO_ASSERT(fio_bstr_info(str).len == 5 && + !memcmp(str, "Hello", fio_bstr_info(str).len + 1), + "fio_bstr_write failed!"); + FIO_ASSERT(fio_bstr_is_greater(str, NULL), + "fio_bstr_is_greater failed vs a NULL String"); + str = fio_bstr_write2(str, + FIO_STRING_WRITE_STR1((char *)" "), + FIO_STRING_WRITE_STR1((char *)"World!")); + FIO_ASSERT(fio_bstr_info(str).len == 12 && + !memcmp(str, "Hello World!", fio_bstr_info(str).len + 1), + "fio_bstr_write2 failed!"); + /* test copy-on-write for fio_bstr_copy */ + char *s_copy = fio_bstr_copy(str); + FIO_ASSERT(s_copy == str, "fio_bstr_copy should only copy on write"); + str = fio_bstr_write(str, "!", 1); + FIO_ASSERT(s_copy != str, "fio_bstr_s write after copy error!"); + FIO_ASSERT(fio_bstr_len(str) > fio_bstr_len(s_copy), + "fio_bstr copy after write length error!"); + FIO_ASSERT(!memcmp(str, s_copy, fio_bstr_len(s_copy)), + "fio_bstr copy after write copied data error!"); + FIO_ASSERT(FIO_BUF_INFO_IS_EQ(fio_bstr_buf(s_copy), + FIO_BUF_INFO2((char *)"Hello World!", 12)), + "fio_bstr old copy corrupted?"); + fio_bstr_free(s_copy); + fio_bstr_free(str); + } + { /* testing readfile */ + char *s = fio_bstr_readfile(NULL, __FILE__, 0, 0); + FIO_ASSERT(s && fio_bstr_len(s), "fio_bstr_readfile failed"); + FIO_LOG_DEBUG("readfile returned %zu bytes, starting with:\n%s", + fio_bstr_len(s), + s); + char *find_z = (char *)FIO_MEMCHR(s, 'Z', fio_bstr_len(s)); + if (find_z) { + int fd = open(__FILE__, 0, "r"); // fio_filename_open(__FILE__, 0); + FIO_ASSERT(fd != -1, "couldn't open file for testing: " __FILE__); + size_t z_index = fio_fd_find_next(fd, 'Z', 0); + FIO_ASSERT(z_index != FIO_FD_FIND_EOF, "fio_fd_find_next returned EOF"); + FIO_ASSERT(z_index == (size_t)(find_z - s), + "fio_fd_find_next index error (%zu != %zu)", + z_index, + (size_t)(find_z - s)); + close(fd); + char *s2 = fio_bstr_getdelim_file(NULL, __FILE__, 0, 'Z', 0); + FIO_ASSERT(fio_bstr_len(s2) == z_index + 1, + "fio_bstr_getdelim_file length error (%zu != %zu)?", + fio_bstr_len(s2), + z_index + 1); + FIO_ASSERT(s2[z_index] == 'Z', + "fio_bstr_getdelim_file copy error?\n%s", + s2); + fio_bstr_free(s2); + } else { + FIO_LOG_WARNING("couldn't find 'Z' after reading file (bstr)"); + } + fio_bstr_free(s); + } + +#if !defined(DEBUG) || defined(NODEBUG) + { /* speed testing comparison */ + char mem[4096]; + fio_str_info_s sa = FIO_STR_INFO3(mem, 0, 2047); + fio_str_info_s sb = FIO_STR_INFO3(mem + 2048, 0, 2047); + fio_string_readfile(&sa, NULL, __FILE__, 0, 0); + fio_string_write(&sb, NULL, sa.buf, sa.len); + sa.buf[sa.len - 1] += 1; + fio_buf_info_s sa_buf = FIO_STR2BUF_INFO(sa); + fio_buf_info_s sb_buf = FIO_STR2BUF_INFO(sb); + + const size_t test_repetitions = (1ULL << 19); + const size_t positions[] = {(sa.len - 1), ((sa.len >> 1) - 1), 30, 0}; + for (const size_t *ppos = positions; *ppos; ++ppos) { + sa.buf[*ppos] += 1; + sa.len = *ppos + 1; + sb.len = *ppos + 1; + fprintf(stderr, + "* Testing comparison speeds (%zu tests of %zu bytes):\n", + test_repetitions, + *ppos); + clock_t start = clock(); + for (size_t i = 0; i < test_repetitions; ++i) { + FIO_COMPILER_GUARD; + int r = fio_string_is_greater_buf(sa_buf, sb_buf); + FIO_ASSERT(r > 0, "fio_string_is_greater error?!"); + } + clock_t end = clock(); + fprintf(stderr, + "\t* fio_string_is_greater test cycles: %zu\n", + (size_t)(end - start)); + start = clock(); + for (size_t i = 0; i < test_repetitions; ++i) { + FIO_COMPILER_GUARD; + int r = memcmp(sa.buf, sb.buf, sa.len > sb.len ? sb.len : sa.len); + if (!r) + r = sa.len > sb.len; + FIO_ASSERT(r > 0, "memcmp error?!"); + } + end = clock(); + fprintf(stderr, + "\t* memcmp libc test cycles: %zu\n", + (size_t)(end - start)); + start = clock(); + for (size_t i = 0; i < test_repetitions; ++i) { + FIO_COMPILER_GUARD; + int r = strcmp(sa.buf, sb.buf); + FIO_ASSERT(r > 0, "strcmp error?!"); + } + end = clock(); + fprintf(stderr, + "\t* strcmp libc test cycles: %zu\n", + (size_t)(end - start)); + start = clock(); + for (size_t i = 0; i < test_repetitions; ++i) { + FIO_COMPILER_GUARD; + int r = + !fio_ct_is_eq(sa.buf, sb.buf, sa.len > sb.len ? sb.len : sa.len); + if (!r) + r = sa.len > sb.len; + FIO_ASSERT(r, "fio_ct_is_eq error?!"); + } + end = clock(); + fprintf(stderr, + "\t* fio_ct_is_eq test cycles: %zu (only equality)\n", + (size_t)(end - start)); + } + + fprintf(stderr, "* Testing fio_string_write_(i|u|hex|bin) speeds:\n"); + FIO_NAME_TEST(stl, atol_speed) + ("fio_string_write/fio_atol", + fio_atol, + FIO_NAME_TEST(stl, string_core_ltoa)); + } +#endif /* DEBUG */ +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_TIME Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_TIME_TEST___H) +#define H___FIO_TIME_TEST___H +#ifndef H___FIO_TIME___H +#define FIO_TIME +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#define FIO___GMTIME_TEST_INTERVAL ((60LL * 60 * 23) + 1027) /* 23:17:07 */ +#if 1 || FIO_OS_WIN +#define FIO___GMTIME_TEST_RANGE (1001LL * 376) /* test 0.5 millenia */ +#else +#define FIO___GMTIME_TEST_RANGE (3003LL * 376) /* test ~3 millenia */ +#endif + +#if FIO_OS_WIN && !defined(gmtime_r) +FIO_IFUNC struct tm *gmtime_r(const time_t *timep, struct tm *result) { + struct tm *t = gmtime(timep); + if (t && result) + *result = *t; + return result; +} +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, time)(void) { + fprintf(stderr, "* Testing facil.io fio_time2gm vs gmtime_r\n"); + struct tm tm1 = {0}, tm2 = {0}; + const time_t now = fio_time_real().tv_sec; +#if FIO_OS_WIN + const time_t end = (FIO___GMTIME_TEST_RANGE * FIO___GMTIME_TEST_INTERVAL); + time_t t = 1; /* Windows fails on some date ranges. */ +#else + const time_t end = + now + (FIO___GMTIME_TEST_RANGE * FIO___GMTIME_TEST_INTERVAL); + time_t t = now - (FIO___GMTIME_TEST_RANGE * FIO___GMTIME_TEST_INTERVAL); +#endif + FIO_ASSERT(t < end, "time testing range overflowed."); + do { + time_t tmp = t; + t += FIO___GMTIME_TEST_INTERVAL; + tm2 = fio_time2gm(tmp); + FIO_ASSERT(fio_gm2time(tm2) == tmp, + "fio_gm2time roundtrip error (%zu != %zu)", + (size_t)fio_gm2time(tm2), + (size_t)tmp); + gmtime_r(&tmp, &tm1); + if (tm1.tm_year != tm2.tm_year || tm1.tm_mon != tm2.tm_mon || + tm1.tm_mday != tm2.tm_mday || tm1.tm_yday != tm2.tm_yday || + tm1.tm_hour != tm2.tm_hour || tm1.tm_min != tm2.tm_min || + tm1.tm_sec != tm2.tm_sec || tm1.tm_wday != tm2.tm_wday) { + char buf[256]; + FIO_LOG_ERROR("system gmtime_r != fio_time2gm for %ld!\n", (long)t); + fio_time2rfc7231(buf, tmp); + FIO_ASSERT(0, + "\n" + "-- System:\n" + "\ttm_year: %d\n" + "\ttm_mon: %d\n" + "\ttm_mday: %d\n" + "\ttm_yday: %d\n" + "\ttm_hour: %d\n" + "\ttm_min: %d\n" + "\ttm_sec: %d\n" + "\ttm_wday: %d\n" + "-- facil.io:\n" + "\ttm_year: %d\n" + "\ttm_mon: %d\n" + "\ttm_mday: %d\n" + "\ttm_yday: %d\n" + "\ttm_hour: %d\n" + "\ttm_min: %d\n" + "\ttm_sec: %d\n" + "\ttm_wday: %d\n" + "-- As String:\n" + "\t%s", + tm1.tm_year, + tm1.tm_mon, + tm1.tm_mday, + tm1.tm_yday, + tm1.tm_hour, + tm1.tm_min, + tm1.tm_sec, + tm1.tm_wday, + tm2.tm_year, + tm2.tm_mon, + tm2.tm_mday, + tm2.tm_yday, + tm2.tm_hour, + tm2.tm_min, + tm2.tm_sec, + tm2.tm_wday, + buf); + } + } while (t < end); + { + char buf[48]; + buf[47] = 0; + FIO_MEMSET(buf, 'X', 47); + fio_time2rfc7231(buf, now); + FIO_LOG_DEBUG2("fio_time2rfc7231: %s", buf); + FIO_MEMSET(buf, 'X', 47); + fio_time2rfc2109(buf, now); + FIO_LOG_DEBUG2("fio_time2rfc2109: %s", buf); + FIO_MEMSET(buf, 'X', 47); + fio_time2rfc2822(buf, now); + FIO_LOG_DEBUG2("fio_time2rfc2822: %s", buf); + FIO_MEMSET(buf, 'X', 47); + fio_time2log(buf, now); + FIO_LOG_DEBUG2("fio_time2log: %s", buf); + } + { + uint64_t start, stop; +#if DEBUG + fprintf(stderr, "PERFOMEANCE TESTS IN DEBUG MODE ARE BIASED\n"); +#endif + fprintf(stderr, " Performance testing fio_time2gm vs gmtime_r\n"); + start = fio_time_micro(); + for (size_t i = 0; i < (1 << 17); ++i) { + volatile struct tm tm = fio_time2gm(now); + FIO_COMPILER_GUARD; + (void)tm; + } + stop = fio_time_micro(); + fprintf(stderr, + "\t- fio_time2gm speed test took:\t%zuus\n", + (size_t)(stop - start)); + start = fio_time_micro(); + for (size_t i = 0; i < (1 << 17); ++i) { + volatile struct tm tm; + time_t tmp = now; + gmtime_r(&tmp, (struct tm *)&tm); + FIO_COMPILER_GUARD; + } + stop = fio_time_micro(); + fprintf(stderr, + "\t- gmtime_r speed test took: \t%zuus\n", + (size_t)(stop - start)); + fprintf(stderr, "\n"); + struct tm tm_now = fio_time2gm(now); + start = fio_time_micro(); + for (size_t i = 0; i < (1 << 17); ++i) { + tm_now = fio_time2gm(now + i); + time_t t_tmp = fio_gm2time(tm_now); + FIO_COMPILER_GUARD; + (void)t_tmp; + } + stop = fio_time_micro(); + fprintf(stderr, + "\t- fio_gm2time speed test took:\t%zuus\n", + (size_t)(stop - start)); + start = fio_time_micro(); + for (size_t i = 0; i < (1 << 17); ++i) { + tm_now = fio_time2gm(now + i); + volatile time_t t_tmp = mktime((struct tm *)&tm_now); + FIO_COMPILER_GUARD; + (void)t_tmp; + } + stop = fio_time_micro(); + fprintf(stderr, + "\t- mktime speed test took: \t%zuus\n", + (size_t)(stop - start)); + fprintf(stderr, "\n"); + } + /* TODO: test fio_time_add, fio_time_add_milli, and fio_time_cmp */ +} +#undef FIO___GMTIME_TEST_INTERVAL +#undef FIO___GMTIME_TEST_RANGE + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_URL Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_URL_TEST___H) +#define H___FIO_URL_TEST___H +#ifndef H___FIO_URL___H +#define FIO_URL +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +/* Test for URI variations: + * + * * `/complete_path?query#target` + * + * i.e.: /index.html?page=1#list + * + * * `host:port/complete_path?query#target` + * + * i.e.: + * example.com + * example.com:8080 + * example.com/index.html + * example.com:8080/index.html + * example.com:8080/index.html?key=val#target + * + * * `user:password@host:port/path?query#target` + * + * i.e.: user:1234@example.com:8080/index.html + * + * * `username[:password]@host[:port][...]` + * + * i.e.: john:1234@example.com + * + * * `schema://user:password@host:port/path?query#target` + * + * i.e.: http://example.com/index.html?page=1#list + */ +FIO_SFUNC void FIO_NAME_TEST(stl, url)(void) { + fprintf(stderr, "* Testing URL (URI) parser.\n"); + struct { + char *url; + size_t len; + fio_url_s expected; + fio_url_tls_info_s tls; + } tests[] = { + { + .url = (char *)"file://go/home/", + 5, + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"file"), + .path = FIO_BUF_INFO1((char *)"go/home/"), + }, + }, + { + .url = (char *)"unix:///go/home/", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"unix"), + .path = FIO_BUF_INFO1((char *)"/go/home/"), + }, + }, + { + .url = (char *)"unix:///go/home/?query#target", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"unix"), + .path = FIO_BUF_INFO1((char *)"/go/home/"), + .query = FIO_BUF_INFO1((char *)"query"), + .target = FIO_BUF_INFO1((char *)"target"), + }, + }, + { + .url = (char *)"schema://user:password@host:port/path?query#target", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"schema"), + .user = FIO_BUF_INFO1((char *)"user"), + .password = FIO_BUF_INFO1((char *)"password"), + .host = FIO_BUF_INFO1((char *)"host"), + .port = FIO_BUF_INFO1((char *)"port"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"query"), + .target = FIO_BUF_INFO1((char *)"target"), + }, + }, + { + .url = (char *)"schema://user@host:port/path?query#target", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"schema"), + .user = FIO_BUF_INFO1((char *)"user"), + .host = FIO_BUF_INFO1((char *)"host"), + .port = FIO_BUF_INFO1((char *)"port"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"query"), + .target = FIO_BUF_INFO1((char *)"target"), + }, + }, + { + .url = (char *)"http://localhost.com:3000/home?is=1", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"http"), + .host = FIO_BUF_INFO1((char *)"localhost.com"), + .port = FIO_BUF_INFO1((char *)"3000"), + .path = FIO_BUF_INFO1((char *)"/home"), + .query = FIO_BUF_INFO1((char *)"is=1"), + }, + }, + { + .url = (char *)"/complete_path?query#target", + .expected = + { + .path = FIO_BUF_INFO1((char *)"/complete_path"), + .query = FIO_BUF_INFO1((char *)"query"), + .target = FIO_BUF_INFO1((char *)"target"), + }, + }, + { + .url = (char *)"/index.html?page=1#list", + .expected = + { + .path = FIO_BUF_INFO1((char *)"/index.html"), + .query = FIO_BUF_INFO1((char *)"page=1"), + .target = FIO_BUF_INFO1((char *)"list"), + }, + }, + { + .url = (char *)"example.com", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + }, + }, + + { + .url = (char *)"example.com:8080", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + }, + }, + { + .url = (char *)"example.com:8080?q=true", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + .query = FIO_BUF_INFO1((char *)"q=true"), + }, + }, + { + .url = (char *)"example.com/index.html", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/index.html"), + }, + }, + { + .url = (char *)"example.com:8080/index.html", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + .path = FIO_BUF_INFO1((char *)"/index.html"), + }, + }, + { + .url = (char *)"example.com:8080/index.html?key=val#target", + .expected = + { + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + .path = FIO_BUF_INFO1((char *)"/index.html"), + .query = FIO_BUF_INFO1((char *)"key=val"), + .target = FIO_BUF_INFO1((char *)"target"), + }, + }, + { + .url = (char *)"user:1234@example.com:8080/index.html", + .expected = + { + .user = FIO_BUF_INFO1((char *)"user"), + .password = FIO_BUF_INFO1((char *)"1234"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + .path = FIO_BUF_INFO1((char *)"/index.html"), + }, + }, + { + .url = (char *)"user@example.com:8080/index.html", + .expected = + { + .user = FIO_BUF_INFO1((char *)"user"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .port = FIO_BUF_INFO1((char *)"8080"), + .path = FIO_BUF_INFO1((char *)"/index.html"), + }, + }, + { + .url = (char *)"https://example.com/path", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"https"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"wss://example.com/path", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"wss"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"sses://example.com/path", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"sses"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"sses://example.com/path", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"sses"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"http://example.com/path?tls=true", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"http"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"tls=true"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"http://example.com/path?tls", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"http"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"tls"), + }, + .tls = {.tls = 1}, + }, + { + .url = (char *)"http://example.com/path?tls=something", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"http"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"tls=something"), + }, + .tls = + { + .key = FIO_BUF_INFO1((char *)"something"), + .cert = FIO_BUF_INFO1((char *)"something"), + .tls = 1, + }, + }, + { + .url = (char *)"http://example.com/path?key=something&cert=pubthing", + .expected = + { + .scheme = FIO_BUF_INFO1((char *)"http"), + .host = FIO_BUF_INFO1((char *)"example.com"), + .path = FIO_BUF_INFO1((char *)"/path"), + .query = FIO_BUF_INFO1((char *)"key=something&cert=pubthing"), + }, + .tls = + { + .key = FIO_BUF_INFO1((char *)"something"), + .cert = FIO_BUF_INFO1((char *)"pubthing"), + .tls = 1, + }, + }, + {.url = NULL}, + }; + for (size_t i = 0; tests[i].url; ++i) { + tests[i].len = strlen(tests[i].url); + fio_url_s result = fio_url_parse(tests[i].url, tests[i].len); + fio_url_tls_info_s tls = fio_url_is_tls(result); + FIO_LOG_DEBUG2("Result for: %s" + "\n\t scheme (%zu bytes): %.*s" + "\n\t user (%zu bytes): %.*s" + "\n\t password (%zu bytes): %.*s" + "\n\t host (%zu bytes): %.*s" + "\n\t port (%zu bytes): %.*s" + "\n\t path (%zu bytes): %.*s" + "\n\t query (%zu bytes): %.*s" + "\n\t target (%zu bytes): %.*s\n", + tests[i].url, + result.scheme.len, + (int)result.scheme.len, + result.scheme.buf, + result.user.len, + (int)result.user.len, + result.user.buf, + result.password.len, + (int)result.password.len, + result.password.buf, + result.host.len, + (int)result.host.len, + result.host.buf, + result.port.len, + (int)result.port.len, + result.port.buf, + result.path.len, + (int)result.path.len, + result.path.buf, + result.query.len, + (int)result.query.len, + result.query.buf, + result.target.len, + (int)result.target.len, + result.target.buf); + FIO_ASSERT( + result.scheme.len == tests[i].expected.scheme.len && + (!result.scheme.len || !memcmp(result.scheme.buf, + tests[i].expected.scheme.buf, + tests[i].expected.scheme.len)), + "scheme result failed for:\n\ttest[%zu]: %s\n\texpected: " + "%s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.scheme.buf, + (int)result.scheme.len, + result.scheme.buf); + FIO_ASSERT( + result.user.len == tests[i].expected.user.len && + (!result.user.len || !memcmp(result.user.buf, + tests[i].expected.user.buf, + tests[i].expected.user.len)), + "user result failed for:\n\ttest[%zu]: %s\n\texpected: %s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.user.buf, + (int)result.user.len, + result.user.buf); + FIO_ASSERT( + result.password.len == tests[i].expected.password.len && + (!result.password.len || !memcmp(result.password.buf, + tests[i].expected.password.buf, + tests[i].expected.password.len)), + "password result failed for:\n\ttest[%zu]: %s\n\texpected: %s\n\tgot: " + "%.*s", + i, + tests[i].url, + tests[i].expected.password.buf, + (int)result.password.len, + result.password.buf); + FIO_ASSERT( + result.host.len == tests[i].expected.host.len && + (!result.host.len || !memcmp(result.host.buf, + tests[i].expected.host.buf, + tests[i].expected.host.len)), + "host result failed for:\n\ttest[%zu]: %s\n\texpected: %s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.host.buf, + (int)result.host.len, + result.host.buf); + FIO_ASSERT( + result.port.len == tests[i].expected.port.len && + (!result.port.len || !memcmp(result.port.buf, + tests[i].expected.port.buf, + tests[i].expected.port.len)), + "port result failed for:\n\ttest[%zu]: %s\n\texpected: %s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.port.buf, + (int)result.port.len, + result.port.buf); + FIO_ASSERT( + result.path.len == tests[i].expected.path.len && + (!result.path.len || !memcmp(result.path.buf, + tests[i].expected.path.buf, + tests[i].expected.path.len)), + "path result failed for:\n\ttest[%zu]: %s\n\texpected: %s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.path.buf, + (int)result.path.len, + result.path.buf); + FIO_ASSERT(result.query.len == tests[i].expected.query.len && + (!result.query.len || !memcmp(result.query.buf, + tests[i].expected.query.buf, + tests[i].expected.query.len)), + "query result failed for:\n\ttest[%zu]: %s\n\texpected: " + "%s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.query.buf, + (int)result.query.len, + result.query.buf); + FIO_ASSERT( + result.target.len == tests[i].expected.target.len && + (!result.target.len || !memcmp(result.target.buf, + tests[i].expected.target.buf, + tests[i].expected.target.len)), + "target result failed for:\n\ttest[%zu]: %s\n\texpected: " + "%s\n\tgot: %.*s", + i, + tests[i].url, + tests[i].expected.target.buf, + (int)result.target.len, + result.target.buf); + + FIO_ASSERT( + 1, + "TSL detection result failed for:\n\ttest[%zu]: %s\n\texpected: " + "%s key:%s, cert:%s, pass%s\n\tgot: %s key:%.*s, cert:%.*s, pass%.*s", + i, + tests[i].url, + tests[i].tls.tls ? "TLS" : "none", + tests[i].tls.key.buf, + tests[i].tls.cert.buf, + tests[i].tls.pass.buf, + tls.tls ? "TLS" : "none", + (int)tls.key.len, + tls.key.buf, + (int)tls.cert.len, + tls.cert.buf, + (int)tls.pass.len, + tls.pass.buf); + } +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_CHACHA Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_CHACHA_TEST___H) +#define H___FIO_CHACHA_TEST___H +#ifndef H___FIO_CHACHA___H +#define FIO_CHACHA +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif + +#if HAVE_OPENSSL +// #include +// #include +// #include +// FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __poly1305_open_ssl_wrapper)(char +// *data, +// size_t len) +// { +// } +#endif /* HAVE_OPENSSL */ + +FIO_SFUNC uintptr_t fio__poly1305_speed_wrapper(char *msg, size_t len) { + uint64_t result[2] = {0}; + char *key = (char *)"\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe\x42" + "\xd5\x06\xa8" + "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41" + "\x49\xf5\x1b"; + fio_poly1305_auth(result, key, msg, len, NULL, 0); + return (uintptr_t)result[0]; +} + +FIO_SFUNC uintptr_t fio__chacha20_speed_wrapper(char *msg, size_t len) { + uint64_t result[2] = {0}; + char *key = (char *)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c" + "\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c" + "\x1d\x1e\x1f"; + char *nounce = (char *)"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00"; + fio_chacha20(msg, len, key, nounce, 1); + result[0] = fio_buf2u64u(msg); + return (uintptr_t)result[0]; +} + +FIO_SFUNC uintptr_t fio__chacha20poly1305_speed_wrapper(char *msg, size_t len) { + uint64_t result[2] = {0}; + char *key = (char *)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c" + "\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c" + "\x1d\x1e\x1f"; + char *nounce = (char *)"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00"; + fio_chacha20_poly1305_enc(result, msg, len, NULL, 0, key, nounce); + return (uintptr_t)result[0]; +} + +FIO_SFUNC uintptr_t fio__chacha20poly1305dec_speed_wrapper(char *msg, + size_t len) { + uint64_t result[2] = {0}; + char *key = (char *)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c" + "\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c" + "\x1d\x1e\x1f"; + char *nounce = (char *)"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00"; + fio_poly1305_auth(result, key, msg, len, NULL, 0); + fio_chacha20(msg, len, key, nounce, 1); + return (uintptr_t)result[0]; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, chacha)(void) { + fprintf(stderr, "* Testing ChaCha20 Poly1305\n"); + { /* test ChaCha20 independently */ + fprintf(stderr, "\t * Testing ChaCha20 separately\n"); + struct { + char key[33]; + char nounce[13]; + char *src; + char *expected; + } tests[] = { + { + .key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d" + "\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b" + "\x1c\x1d\x1e\x1f", + .nounce = "\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00", + .src = + (char *)"\x4c\x61\x64\x69\x65\x73\x20\x61\x6e\x64\x20\x47\x65" + "\x6e\x74\x6c\x65\x6d\x65\x6e\x20\x6f\x66\x20\x74\x68" + "\x65\x20\x63\x6c\x61\x73\x73\x20\x6f\x66\x20\x27\x39" + "\x39\x3a\x20\x49\x66\x20\x49\x20\x63\x6f\x75\x6c\x64" + "\x20\x6f\x66\x66\x65\x72\x20\x79\x6f\x75\x20\x6f\x6e" + "\x6c\x79\x20\x6f\x6e\x65\x20\x74\x69\x70\x20\x66\x6f" + "\x72\x20\x74\x68\x65\x20\x66\x75\x74\x75\x72\x65\x2c" + "\x20\x73\x75\x6e\x73\x63\x72\x65\x65\x6e\x20\x77\x6f" + "\x75\x6c\x64\x20\x62\x65\x20\x69\x74\x2e", + .expected = + (char *)"\x6e\x2e\x35\x9a\x25\x68\xf9\x80\x41\xba\x07\x28\xdd" + "\x0d\x69\x81\xe9\x7e\x7a\xec\x1d\x43\x60\xc2\x0a\x27" + "\xaf\xcc\xfd\x9f\xae\x0b\xf9\x1b\x65\xc5\x52\x47\x33" + "\xab\x8f\x59\x3d\xab\xcd\x62\xb3\x57\x16\x39\xd6\x24" + "\xe6\x51\x52\xab\x8f\x53\x0c\x35\x9f\x08\x61\xd8\x07" + "\xca\x0d\xbf\x50\x0d\x6a\x61\x56\xa3\x8e\x08\x8a\x22" + "\xb6\x5e\x52\xbc\x51\x4d\x16\xcc\xf8\x06\x81\x8c\xe9" + "\x1a\xb7\x79\x37\x36\x5a\xf9\x0b\xbf\x74\xa3\x5b\xe6" + "\xb4\x0b\x8e\xed\xf2\x78\x5e\x42\x87\x4d", + }, + { + .key = {0}, + .nounce = {0}, + .src = + (char *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + .expected = + (char *)"\x3a\xeb\x52\x24\xec\xf8\x49\x92\x9b\x9d\x82\x8d\xb1" + "\xce\xd4\xdd\x83\x20\x25\xe8\x01\x8b\x81\x60\xb8\x22" + "\x84\xf3\xc9\x49\xaa\x5a\x8e\xca\x00\xbb\xb4\xa7\x3b" + "\xda\xd1\x92\xb5\xc4\x2f\x73\xf2\xfd\x4e\x27\x36\x44" + "\xc8\xb3\x61\x25\xa6\x4a\xdd\xeb\x00\x6c\x13\xa0", + }, + {.expected = NULL}}; + for (size_t i = 0; tests[i].expected; ++i) { + size_t len = FIO_STRLEN(tests[i].src); + char buffer[4096]; + FIO_MEMCPY(buffer, tests[i].src, len); + fio_chacha20(buffer, len, tests[i].key, tests[i].nounce, 1); + FIO_ASSERT(!memcmp(buffer, tests[i].expected, len), + "ChaCha20 encoding failed"); + fio_chacha20(buffer, len, tests[i].key, tests[i].nounce, 1); + FIO_ASSERT(!memcmp(buffer, tests[i].src, len), + "ChaCha20 decoding failed"); + } + } + { /* test Poly1305 independently */ + fprintf(stderr, "\t * Testing Poly1305 separately\n"); + struct { + char key[33]; + char *msg; + char *expected; + } tests[] = {{ + .key = "\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe" + "\x42\xd5\x06\xa8\x01\x03\x80\x8a\xfb\x0d\xb2\xfd" + "\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b", + .msg = (char *)"Cryptographic Forum Research Group", + .expected = + (char *)"\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b" + "\xaf\x0c\x01\x27\xa9", + }, + {.expected = NULL}}; + char auth[24] = {0}; + char buf1[33] = {0}; + char buf2[33] = {0}; + for (size_t t = 0; tests[t].expected; ++t) { + fio_poly1305_auth(auth, + tests[t].key, + tests[t].msg, + FIO_STRLEN(tests[t].msg), + NULL, + 0); + for (int i = 0; i < 16; ++i) { + buf1[(i << 1)] = fio_i2c(((auth[i] >> 4) & 0xF)); + buf1[(i << 1) + 1] = fio_i2c(((auth[i]) & 0xF)); + buf2[(i << 1)] = fio_i2c(((tests[t].expected[i] >> 4) & 0xF)); + buf2[(i << 1) + 1] = fio_i2c(((tests[t].expected[i]) & 0xF)); + } + FIO_ASSERT(!memcmp(auth, tests[t].expected, 16), + "Poly1305 example authentication failed:\n\t%s != %s", + buf1, + buf2); + FIO_ASSERT(!fio_buf2u64u(auth + 16), + "Poly1305 authentication code overflow!"); + } + } + { /* test ChaCha20Poly1305 */ + fprintf(stderr, "\t * Testing ChaCha20Poly1305 together\n"); + struct { + char key[33]; + char nounce[13]; + char *ad; + size_t ad_len; + char *msg; + char *expected; + char mac[17]; + } tests[] = { + { + .key = "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d" + "\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b" + "\x9c\x9d\x9e\x9f", + .nounce = "\x07\x00\x00\x00\x40\x41\x42\x43\x44\x45\x46\x47", + .ad = (char *)"\x50\x51\x52\x53\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7", + .ad_len = 12, + .msg = + (char *)"\x4c\x61\x64\x69\x65\x73\x20\x61\x6e\x64\x20\x47\x65" + "\x6e\x74\x6c\x65\x6d\x65\x6e\x20\x6f\x66\x20\x74\x68" + "\x65\x20\x63\x6c\x61\x73\x73\x20\x6f\x66\x20\x27\x39" + "\x39\x3a\x20\x49\x66\x20\x49\x20\x63\x6f\x75\x6c\x64" + "\x20\x6f\x66\x66\x65\x72\x20\x79\x6f\x75\x20\x6f\x6e" + "\x6c\x79\x20\x6f\x6e\x65\x20\x74\x69\x70\x20\x66\x6f" + "\x72\x20\x74\x68\x65\x20\x66\x75\x74\x75\x72\x65\x2c" + "\x20\x73\x75\x6e\x73\x63\x72\x65\x65\x6e\x20\x77\x6f" + "\x75\x6c\x64\x20\x62\x65\x20\x69\x74\x2e", + .expected = + (char *)"\xd3\x1a\x8d\x34\x64\x8e\x60\xdb\x7b\x86\xaf\xbc\x53" + "\xef\x7e\xc2\xa4\xad\xed\x51\x29\x6e\x08\xfe\xa9\xe2" + "\xb5\xa7\x36\xee\x62\xd6\x3d\xbe\xa4\x5e\x8c\xa9\x67" + "\x12\x82\xfa\xfb\x69\xda\x92\x72\x8b\x1a\x71\xde\x0a" + "\x9e\x06\x0b\x29\x05\xd6\xa5\xb6\x7e\xcd\x3b\x36\x92" + "\xdd\xbd\x7f\x2d\x77\x8b\x8c\x98\x03\xae\xe3\x28\x09" + "\x1b\x58\xfa\xb3\x24\xe4\xfa\xd6\x75\x94\x55\x85\x80" + "\x8b\x48\x31\xd7\xbc\x3f\xf4\xde\xf0\x8e\x4b\x7a\x9d" + "\xe5\x76\xd2\x65\x86\xce\xc6\x4b\x61\x16", + .mac = "\x1a\xe1\x0b\x59\x4f\x09\xe2\x6a\x7e\x90\x2e\xcb\xd0\x60" + "\x06\x91", + }, + { + .key = "\x1c\x92\x40\xa5\xeb\x55\xd3\x8a\xf3\x33\x88\x86\x04\xf6" + "\xb5\xf0\x47\x39\x17\xc1\x40\x2b\x80\x09\x9d\xca\x5c\xbc" + "\x20\x70\x75\xc0", + .nounce = "\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08", + .ad = (char *)"\xf3\x33\x88\x86\x00\x00\x00\x00\x00\x00\x4e\x91", + .ad_len = 12, + .msg = + (char *)"\x49\x6e\x74\x65\x72\x6e\x65\x74\x2d\x44\x72\x61\x66" + "\x74\x73\x20\x61\x72\x65\x20\x64\x72\x61\x66\x74\x20" + "\x64\x6f\x63\x75\x6d\x65\x6e\x74\x73\x20\x76\x61\x6c" + "\x69\x64\x20\x66\x6f\x72\x20\x61\x20\x6d\x61\x78\x69" + "\x6d\x75\x6d\x20\x6f\x66\x20\x73\x69\x78\x20\x6d\x6f" + "\x6e\x74\x68\x73\x20\x61\x6e\x64\x20\x6d\x61\x79\x20" + "\x62\x65\x20\x75\x70\x64\x61\x74\x65\x64\x2c\x20\x72" + "\x65\x70\x6c\x61\x63\x65\x64\x2c\x20\x6f\x72\x20\x6f" + "\x62\x73\x6f\x6c\x65\x74\x65\x64\x20\x62\x79\x20\x6f" + "\x74\x68\x65\x72\x20\x64\x6f\x63\x75\x6d\x65\x6e\x74" + "\x73\x20\x61\x74\x20\x61\x6e\x79\x20\x74\x69\x6d\x65" + "\x2e\x20\x49\x74\x20\x69\x73\x20\x69\x6e\x61\x70\x70" + "\x72\x6f\x70\x72\x69\x61\x74\x65\x20\x74\x6f\x20\x75" + "\x73\x65\x20\x49\x6e\x74\x65\x72\x6e\x65\x74\x2d\x44" + "\x72\x61\x66\x74\x73\x20\x61\x73\x20\x72\x65\x66\x65" + "\x72\x65\x6e\x63\x65\x20\x6d\x61\x74\x65\x72\x69\x61" + "\x6c\x20\x6f\x72\x20\x74\x6f\x20\x63\x69\x74\x65\x20" + "\x74\x68\x65\x6d\x20\x6f\x74\x68\x65\x72\x20\x74\x68" + "\x61\x6e\x20\x61\x73\x20\x2f\xe2\x80\x9c\x77\x6f\x72" + "\x6b\x20\x69\x6e\x20\x70\x72\x6f\x67\x72\x65\x73\x73" + "\x2e\x2f\xe2\x80\x9d", + .expected = + (char *)"\x64\xa0\x86\x15\x75\x86\x1a\xf4\x60\xf0\x62\xc7\x9b" + "\xe6\x43\xbd\x5e\x80\x5c\xfd\x34\x5c\xf3\x89\xf1\x08" + "\x67\x0a\xc7\x6c\x8c\xb2\x4c\x6c\xfc\x18\x75\x5d\x43" + "\xee\xa0\x9e\xe9\x4e\x38\x2d\x26\xb0\xbd\xb7\xb7\x3c" + "\x32\x1b\x01\x00\xd4\xf0\x3b\x7f\x35\x58\x94\xcf\x33" + "\x2f\x83\x0e\x71\x0b\x97\xce\x98\xc8\xa8\x4a\xbd\x0b" + "\x94\x81\x14\xad\x17\x6e\x00\x8d\x33\xbd\x60\xf9\x82" + "\xb1\xff\x37\xc8\x55\x97\x97\xa0\x6e\xf4\xf0\xef\x61" + "\xc1\x86\x32\x4e\x2b\x35\x06\x38\x36\x06\x90\x7b\x6a" + "\x7c\x02\xb0\xf9\xf6\x15\x7b\x53\xc8\x67\xe4\xb9\x16" + "\x6c\x76\x7b\x80\x4d\x46\xa5\x9b\x52\x16\xcd\xe7\xa4" + "\xe9\x90\x40\xc5\xa4\x04\x33\x22\x5e\xe2\x82\xa1\xb0" + "\xa0\x6c\x52\x3e\xaf\x45\x34\xd7\xf8\x3f\xa1\x15\x5b" + "\x00\x47\x71\x8c\xbc\x54\x6a\x0d\x07\x2b\x04\xb3\x56" + "\x4e\xea\x1b\x42\x22\x73\xf5\x48\x27\x1a\x0b\xb2\x31" + "\x60\x53\xfa\x76\x99\x19\x55\xeb\xd6\x31\x59\x43\x4e" + "\xce\xbb\x4e\x46\x6d\xae\x5a\x10\x73\xa6\x72\x76\x27" + "\x09\x7a\x10\x49\xe6\x17\xd9\x1d\x36\x10\x94\xfa\x68" + "\xf0\xff\x77\x98\x71\x30\x30\x5b\xea\xba\x2e\xda\x04" + "\xdf\x99\x7b\x71\x4d\x6c\x6f\x2c\x29\xa6\xad\x5c\xb4" + "\x02\x2b\x02\x70\x9b", + .mac = "\xee\xad\x9d\x67\x89\x0c\xbb\x22\x39\x23\x36\xfe\xa1\x85" + "\x1f\x38", + }, + {.expected = NULL}}; + for (size_t i = 0; tests[i].expected; ++i) { + size_t len = strlen(tests[i].msg); + char buffer[1024]; + char mac[24] = {0}, mac2[24] = {0}; + FIO_MEMCPY(buffer, tests[i].msg, len); + fio_chacha20_poly1305_enc(mac, + buffer, + len, + tests[i].ad, + tests[i].ad_len, + tests[i].key, + tests[i].nounce); + FIO_ASSERT(!memcmp(buffer, tests[i].expected, len), + "ChaCha20Poly1305 encoding failed"); + fio_chacha20_poly1305_auth(mac2, + buffer, + len, + tests[i].ad, + tests[i].ad_len, + tests[i].key, + tests[i].nounce); + FIO_ASSERT(!memcmp(mac, mac2, 16), + "ChaCha20Poly1305 authentication != Poly1305 code"); + FIO_ASSERT(!memcmp(mac, tests[i].mac, 16), + "ChaCha20Poly1305 authentication code failed"); + FIO_ASSERT(!fio_chacha20_poly1305_dec(mac, + buffer, + len, + tests[i].ad, + tests[i].ad_len, + tests[i].key, + tests[i].nounce), + "fio_chacha20_poly1305_dec returned error for %s", + tests[i].msg); + FIO_ASSERT(!fio_buf2u64u(mac + 16), + "ChaCha20Poly1305 authentication code overflow!"); + FIO_ASSERT(!fio_buf2u64u(mac2 + 16), + "ChaCha20Poly1305 authentication code (2) overflow!"); + FIO_ASSERT( + !memcmp(buffer, tests[i].msg, len), + "ChaCha20Poly1305 decoding failed for %s\nshould have been %.*s", + tests[i].msg, + (int)len, + buffer); + } + } + { /* test roundtrip */ + fprintf(stderr, "\t * Testing ChaCha20Poly1305 round-trip.\n"); + fio_u256 key = + fio_u256_init64(fio_rand64(), fio_rand64(), fio_rand64(), fio_rand64()); + FIO_STR_INFO_TMP_VAR(ad, 128); + FIO_STR_INFO_TMP_VAR(plaintext, 1024); + FIO_STR_INFO_TMP_VAR(cyphertext, 1024); + FIO_STR_INFO_TMP_VAR(decrypted, 1024); + fio_string_write2(&ad, + NULL, + FIO_STRING_WRITE_STR1( + "This is unencrypted additional data with a nonce:"), + FIO_STRING_WRITE_HEX(fio_rand64())); + fio_string_write2( + &plaintext, + NULL, + FIO_STRING_WRITE_STR1( + "This is unencrypted text that will eventually be encrypted, the " + "following are the whole 0-255 byte values:")); + for (size_t i = 0; i < 256; ++i) { + plaintext.buf[plaintext.len++] = (char)i; + } + plaintext.buf[plaintext.len] = 0; + FIO_MEMCPY(cyphertext.buf, plaintext.buf, plaintext.len); + cyphertext.len = plaintext.len; + fio_chacha20_poly1305_enc(ad.buf + ad.len, + cyphertext.buf, + cyphertext.len, + ad.buf, /* additional data */ + ad.len, + key.u8, + ad.buf + ad.len - 12); + FIO_MEMCPY(decrypted.buf, cyphertext.buf, cyphertext.len); + decrypted.len = cyphertext.len; + FIO_ASSERT(!fio_chacha20_poly1305_dec(ad.buf + ad.len, + decrypted.buf, + decrypted.len, + ad.buf, /* additional data */ + ad.len, + key.u8, + ad.buf + ad.len - 12), + "fio_chacha20_poly1305_dec failed!"); + FIO_ASSERT(FIO_MEMCMP(cyphertext.buf, plaintext.buf, plaintext.len), + "chacha20 cypher-text should be different than plain-text."); + FIO_ASSERT(!FIO_MEMCMP(decrypted.buf, plaintext.buf, plaintext.len), + "chacha20_poly1305 roundtrip error!"); + } + +#if !DEBUG + fio_test_hash_function(fio__poly1305_speed_wrapper, + (char *)"Poly1305", + 7, + 0, + 0); + fio_test_hash_function(fio__poly1305_speed_wrapper, + (char *)"Poly1305", + 13, + 0, + 0); + fio_test_hash_function(fio__poly1305_speed_wrapper, + (char *)"Poly1305 (unaligned)", + 13, + 3, + 0); +#if HAVE_OPENSSL && 0 + fio_test_hash_function(__poly1305_open_ssl_wrapper, + (char *)"Poly1305", + 7, + 0, + 0); + fio_test_hash_function(__poly1305_open_ssl_wrapper, + (char *)"Poly1305", + 13, + 0, + 0); + fio_test_hash_function(__poly1305_open_ssl_wrapper, + (char *)"Poly1305 (unaligned)", + 13, + 3, + 0); +#endif /* HAVE_OPENSSL */ + fprintf(stderr, "\n"); + fio_test_hash_function(fio__chacha20_speed_wrapper, + (char *)"ChaCha20", + 6, + 0, + 0); + fio_test_hash_function(fio__chacha20_speed_wrapper, + (char *)"ChaCha20", + 7, + 0, + 0); + fio_test_hash_function(fio__chacha20_speed_wrapper, + (char *)"ChaCha20", + 13, + 0, + 0); + fio_test_hash_function(fio__chacha20_speed_wrapper, + (char *)"ChaCha20 (unaligned)", + 13, + 3, + 0); + fprintf(stderr, "\n"); + fio_test_hash_function(fio__chacha20poly1305dec_speed_wrapper, + (char *)"ChaCha20Poly1305 (auth+decrypt)", + 7, + 0, + 0); + fio_test_hash_function(fio__chacha20poly1305dec_speed_wrapper, + (char *)"ChaCha20Poly1305 (auth+decrypt)", + 13, + 0, + 0); + fprintf(stderr, "\n"); + fio_test_hash_function(fio__chacha20poly1305_speed_wrapper, + (char *)"ChaCha20Poly1305 (encrypt+MAC)", + 7, + 0, + 0); + fio_test_hash_function(fio__chacha20poly1305_speed_wrapper, + (char *)"ChaCha20Poly1305 (encrypt+MAC)", + 13, + 0, + 0); +#endif +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + FIO_SHA Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_SHA_TEST___H) +#define FIO___TEST_REINCLUDE +#define H___FIO_SHA_TEST___H +#ifndef H___FIO_SHA1___H +#define FIO_SHA1 +#include FIO_INCLUDE_FILE +#endif +#ifndef H___FIO_SHA2___H +#define FIO_SHA2 +#include FIO_INCLUDE_FILE +#endif +#undef FIO___TEST_REINCLUDE + +/* ***************************************************************************** +SHA1 +***************************************************************************** */ + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha1_wrapper)(char *data, size_t len) { + fio_sha1_s h = fio_sha1((const void *)data, (uint64_t)len); + return *(uintptr_t *)h.digest; +} + +#if HAVE_OPENSSL +#include +#include +#include + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha1_open_ssl_wrapper)(char *data, + size_t len) { + fio_u256 result; + SHA1((const unsigned char *)data, len, result.u8); + return result.u64[0]; +} + +#endif + +FIO_SFUNC void FIO_NAME_TEST(stl, sha1)(void) { + fprintf(stderr, "* Testing SHA-1\n"); + struct { + const char *str; + const char *sha1; + } data[] = { + { + .str = "", + .sha1 = "\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18" + "\x90\xaf\xd8\x07\x09", + }, + { + .str = "The quick brown fox jumps over the lazy dog", + .sha1 = "\x2f\xd4\xe1\xc6\x7a\x2d\x28\xfc\xed\x84\x9e\xe1\xbb\x76\xe7" + "\x39\x1b\x93\xeb\x12", + }, + { + .str = "The quick brown fox jumps over the lazy cog", + .sha1 = "\xde\x9f\x2c\x7f\xd2\x5e\x1b\x3a\xfa\xd3\xe8\x5a\x0b\xd1\x7d" + "\x9b\x10\x0d\xb4\xb3", + }, + }; + for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + fio_sha1_s sha1 = fio_sha1(data[i].str, FIO_STRLEN(data[i].str)); + + FIO_ASSERT(!memcmp(sha1.digest, data[i].sha1, fio_sha1_len()), + "SHA1 mismatch for \"%s\"", + data[i].str); + } +#if !DEBUG + fio_test_hash_function(FIO_NAME_TEST(stl, __sha1_wrapper), + (char *)"fio_sha1", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha1_wrapper), + (char *)"fio_sha1", + 13, + 0, + 1); +#if HAVE_OPENSSL + fprintf(stderr, "* Comparing to " OPENSSL_VERSION_TEXT "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha1_open_ssl_wrapper), + (char *)"OpenSSL SHA-1", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha1_open_ssl_wrapper), + (char *)"OpenSSL SHA-1", + 13, + 0, + 1); +#endif /* HAVE_OPENSSL */ +#endif /* !DEBUG */ +} + +/* ***************************************************************************** +SHA2 +***************************************************************************** */ + +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha256_wrapper)(char *data, + size_t len) { + fio_u256 h = fio_sha256((const void *)data, (uint64_t)len); + return (uintptr_t)(h.u64[0]); +} +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha512_wrapper)(char *data, + size_t len) { + fio_u512 h = fio_sha512((const void *)data, (uint64_t)len); + return (uintptr_t)(h.u64[0]); +} + +#if HAVE_OPENSSL +#include +#include +#include +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha256_open_ssl_wrapper)(char *data, + size_t len) { + fio_u256 result; + SHA256((const unsigned char *)data, len, result.u8); + return result.u64[0]; +} +FIO_SFUNC uintptr_t FIO_NAME_TEST(stl, __sha512_open_ssl_wrapper)(char *data, + size_t len) { + fio_u512 result; + SHA512((const unsigned char *)data, len, result.u8); + return result.u64[0]; +} +#endif /* HAVE_OPENSSL */ + +FIO_SFUNC void FIO_NAME_TEST(stl, sha2)(void) { + fprintf(stderr, "* Testing SHA-2\n"); + struct { + const char *str; + const char *sha256; + const char *sha512; + } data[] = { + { + .str = (char *)"abc", + .sha256 = (char *)"\xBA\x78\x16\xBF\x8F\x01\xCF\xEA\x41\x41\x40\xDE" + "\x5D\xAE\x22\x23\xB0\x03\x61\xA3\x96\x17\x7A\x9C" + "\xB4\x10\xFF\x61\xF2\x00\x15\xAD", + .sha512 = + (char *)"\xDD\xAF\x35\xA1\x93\x61\x7A\xBA\xCC\x41\x73\x49\xAE" + "\x20\x41\x31\x12\xE6\xFA\x4E\x89\xA9\x7E\xA2\x0A\x9E" + "\xEE\xE6\x4B\x55\xD3\x9A\x21\x92\x99\x2A\x27\x4F\xC1" + "\xA8\x36\xBA\x3C\x23\xA3\xFE\xEB\xBD\x45\x4D\x44\x23" + "\x64\x3C\xE8\x0E\x2A\x9A\xC9\x4F\xA5\x4C\xA4\x9F", + }, + { + .str = (char *)"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnl" + "mnomnopnopq", + .sha256 = (char *)"\x24\x8D\x6A\x61\xD2\x06\x38\xB8\xE5\xC0\x26" + "\x93\x0C\x3E\x60\x39\xA3\x3C\xE4\x59\x64\xFF" + "\x21\x67\xF6\xEC\xED\xD4\x19\xDB\x06\xC1", + .sha512 = + (char *)"\x20\x4A\x8F\xC6\xDD\xA8\x2F\x0A\x0C\xED\x7B\xEB\x8E\x08" + "\xA4\x16\x57\xC1\x6E\xF4\x68\xB2\x28\xA8\x27\x9B\xE3\x31" + "\xA7\x03\xC3\x35\x96\xFD\x15\xC1\x3B\x1B\x07\xF9\xAA\x1D" + "\x3B\xEA\x57\x78\x9C\xA0\x31\xAD\x85\xC7\xA7\x1D\xD7\x03" + "\x54\xEC\x63\x12\x38\xCA\x34\x45", + }, + { + .str = (char *)"The quick brown fox jumps over the lazy dog", + .sha256 = (char *)"\xD7\xA8\xFB\xB3\x07\xD7\x80\x94\x69\xCA\x9A\xBC" + "\xB0\x08\x2E\x4F\x8D\x56\x51\xE4\x6D\x3C\xDB\x76" + "\x2D\x02\xD0\xBF\x37\xC9\xE5\x92", + .sha512 = + (char *)"\x07\xE5\x47\xD9\x58\x6F\x6A\x73\xF7\x3F\xBA\xC0\x43\x5E" + "\xD7\x69\x51\x21\x8F\xB7\xD0\xC8\xD7\x88\xA3\x09\xD7\x85" + "\x43\x6B\xBB\x64\x2E\x93\xA2\x52\xA9\x54\xF2\x39\x12\x54" + "\x7D\x1E\x8A\x3B\x5E\xD6\xE1\xBF\xD7\x09\x78\x21\x23\x3F" + "\xA0\x53\x8F\x3D\xB8\x54\xFE\xE6", + }, + { + .str = (char *)"The quick brown fox jumps over the lazy cog", + .sha256 = (char *)"\xE4\xC4\xD8\xF3\xBF\x76\xB6\x92\xDE\x79\x1A\x17" + "\x3E\x05\x32\x11\x50\xF7\xA3\x45\xB4\x64\x84\xFE" + "\x42\x7F\x6A\xCC\x7E\xCC\x81\xBE", + .sha512 = + (char *)"\x3E\xEE\xE1\xD0\xE1\x17\x33\xEF\x15\x2A\x6C\x29\x50\x3B" + "\x3A\xE2\x0C\x4F\x1F\x3C\xDA\x4C\xB2\x6F\x1B\xC1\xA4\x1F" + "\x91\xC7\xFE\x4A\xB3\xBD\x86\x49\x40\x49\xE2\x01\xC4\xBD" + "\x51\x55\xF3\x1E\xCB\x7A\x3C\x86\x06\x84\x3C\x4C\xC8\xDF" + "\xCA\xB7\xDA\x11\xC8\xAE\x50\x45", + }, + { + .str = (char *)"", + .sha256 = (char *)"\xE3\xB0\xC4\x42\x98\xFC\x1C\x14\x9A\xFB\xF4\xC8" + "\x99\x6F\xB9\x24\x27\xAE\x41\xE4\x64\x9B\x93\x4C" + "\xA4\x95\x99\x1B\x78\x52\xB8\x55", + .sha512 = + (char *)"\xCF\x83\xE1\x35\x7E\xEF\xB8\xBD\xF1\x54\x28\x50\xD6\x6D" + "\x80\x07\xD6\x20\xE4\x05\x0B\x57\x15\xDC\x83\xF4\xA9\x21" + "\xD3\x6C\xE9\xCE\x47\xD0\xD1\x3C\x5D\x85\xF2\xB0\xFF\x83" + "\x18\xD2\x87\x7E\xEC\x2F\x63\xB9\x31\xBD\x47\x41\x7A\x81" + "\xA5\x38\x32\x7A\xF9\x27\xDA\x3E", + }, + }; + for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + if (!data[i].str) + continue; + if (data[i].sha256) { + fio_u256 sha256 = fio_sha256(data[i].str, FIO_STRLEN(data[i].str)); + FIO_ASSERT(!memcmp(sha256.u8, data[i].sha256, 32), + "SHA256 mismatch for \"%s\":\n\t %X%X%X%X...%X%X%X%X", + data[i].str, + sha256.u8[0], + sha256.u8[1], + sha256.u8[2], + sha256.u8[3], + sha256.u8[28], + sha256.u8[29], + sha256.u8[30], + sha256.u8[31]); + } + if (data[i].sha512) { + fio_u512 sha512 = fio_sha512(data[i].str, FIO_STRLEN(data[i].str)); + FIO_ASSERT( + !memcmp(sha512.u8, data[i].sha512, 64), + "SHA512 mismatch for \"%s\":\n\t %X%X%X%X%X%X%X%X...%X%X%X%X%X%X%X%X", + data[i].str, + sha512.u8[0], + sha512.u8[1], + sha512.u8[2], + sha512.u8[3], + sha512.u8[4], + sha512.u8[5], + sha512.u8[6], + sha512.u8[7], + sha512.u8[24], + sha512.u8[25], + sha512.u8[26], + sha512.u8[27], + sha512.u8[28], + sha512.u8[29], + sha512.u8[30], + sha512.u8[31]); + } + } +#if !DEBUG + fio_test_hash_function(FIO_NAME_TEST(stl, __sha256_wrapper), + (char *)"fio_sha256", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha256_wrapper), + (char *)"fio_sha256", + 13, + 0, + 1); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha512_wrapper), + (char *)"fio_sha512", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha512_wrapper), + (char *)"fio_sha512", + 13, + 0, + 1); +#if HAVE_OPENSSL + fprintf(stderr, "* Comparing to " OPENSSL_VERSION_TEXT "\n"); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha256_open_ssl_wrapper), + (char *)"OpenSSL SHA-256", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha256_open_ssl_wrapper), + (char *)"OpenSSL SHA-256", + 13, + 0, + 1); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha512_open_ssl_wrapper), + (char *)"OpenSSL SHA-512", + 5, + 0, + 0); + fio_test_hash_function(FIO_NAME_TEST(stl, __sha512_open_ssl_wrapper), + (char *)"OpenSSL SHA-512", + 13, + 0, + 1); +#endif /* HAVE_OPENSSL */ +#endif /* !DEBUG */ +} + +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + Testing + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_TEST_ALL___H) +#define H___FIO_TEST_ALL___H + +/* ***************************************************************************** +Locking - Speed Test +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, math_speed)(void) { + uint64_t n = 0, d = 1; + uint64_t start[2], end[2]; + start[0] = fio_time_nano(); + for (size_t i = 0; i < 64; ++i) { + n = (n << 7) ^ 0xAA; + uint64_t q = 0, r = 0; + FIO_COMPILER_GUARD; + for (size_t j = 0; j < 64; ++j) { + d = (d << 3) ^ 0xAA; + FIO_COMPILER_GUARD; + fio_math_div(&q, &r, &n, &d, 1); + FIO_COMPILER_GUARD; + } + (void)q; + } + end[0] = fio_time_nano(); + n = 0, d = 1; + start[1] = fio_time_nano(); + for (size_t i = 0; i < 64; ++i) { + n = (n << 7) ^ 0xAA; + uint64_t q = 0; + FIO_COMPILER_GUARD; + for (size_t j = 0; j < 64; ++j) { + d = (d << 3) ^ 0xAA; + FIO_COMPILER_GUARD; + q = n / d; + FIO_COMPILER_GUARD; + } + (void)q; + } + end[1] = fio_time_nano(); + FIO_LOG_INFO("\t fio_math_div test took %zu us (vs. %zu us) for a single " + "64 bit word.", + (size_t)(end[0] - start[0]), + (size_t)(end[1] - start[1])); +} + +/* ***************************************************************************** +Locking - Speed Test +***************************************************************************** */ +#define FIO___LOCK_TEST_TASK (1LU << 25) +#define FIO___LOCK_TEST_THREADS 32U +#define FIO___LOCK_TEST_REPEAT 1 + +FIO_SFUNC void fio___lock_speedtest_task_inner(void *s) { + size_t *r = (size_t *)s; + static size_t i; + for (i = 0; i < FIO___LOCK_TEST_TASK; ++i) { + FIO_COMPILER_GUARD; + ++r[0]; + } +} + +static void *fio___lock_mytask_lock(void *s) { + static fio_lock_i lock = FIO_LOCK_INIT; + fio_lock(&lock); + if (s) + fio___lock_speedtest_task_inner(s); + fio_unlock(&lock); + return NULL; +} + +#ifdef H___FIO_LOCK2___H +static void *fio___lock_mytask_lock2(void *s) { + static fio_lock2_s lock = {FIO_LOCK_INIT}; + fio_lock2(&lock, 1); + if (s) + fio___lock_speedtest_task_inner(s); + fio_unlock2(&lock, 1); + return NULL; +} +#endif + +static void *fio___lock_mytask_mutex(void *s) { +#if FIO_OS_WIN + static fio_thread_mutex_t mutex; +#else + static fio_thread_mutex_t mutex = FIO_THREAD_MUTEX_INIT; +#endif + fio_thread_mutex_lock(&mutex); + if (s) + fio___lock_speedtest_task_inner(s); + fio_thread_mutex_unlock(&mutex); + return NULL; +} + +FIO_SFUNC void FIO_NAME_TEST(stl, lock_speed)(void) { + uint64_t start, end; + fio_thread_t threads[FIO___LOCK_TEST_THREADS]; + + struct { + size_t type_size; + const char *type_name; + const char *name; + void *(*task)(void *); + } test_funcs[] = { + { + .type_size = sizeof(fio_lock_i), + .type_name = "fio_lock_i", + .name = "fio_lock (spinlock)", + .task = fio___lock_mytask_lock, + }, + { + .type_size = sizeof(fio_thread_mutex_t), + .type_name = "fio_thread_mutex_t", + .name = "OS threads (pthread_mutex / Windows handle)", + .task = fio___lock_mytask_mutex, + }, + { + .name = NULL, + .task = NULL, + }, + }; + fprintf(stderr, "* Speed testing The following types:\n"); + for (size_t fn = 0; test_funcs[fn].name; ++fn) { + fprintf(stderr, + "\t%s\t(%zu bytes)\n", + test_funcs[fn].type_name, + test_funcs[fn].type_size); + } + + start = fio_time_micro(); + for (size_t i = 0; i < FIO___LOCK_TEST_TASK; ++i) { + FIO_COMPILER_GUARD; + } + end = fio_time_micro(); + fprintf(stderr, + "\n* Speed testing locking schemes - no contention, short work (%zu " + "mms):\n" + "\t\t(%zu itterations)\n", + (size_t)(end - start), + (size_t)FIO___LOCK_TEST_TASK); + + for (size_t test_repeat = 0; test_repeat < FIO___LOCK_TEST_REPEAT; + ++test_repeat) { + if (FIO___LOCK_TEST_REPEAT > 1) + fprintf(stderr, + "%s (%zu)\n", + (test_repeat ? "Round" : "Warmup"), + test_repeat); + for (size_t fn = 0; test_funcs[fn].name; ++fn) { + test_funcs[fn].task(NULL); /* warmup */ + start = fio_time_micro(); + for (size_t i = 0; i < FIO___LOCK_TEST_TASK; ++i) { + FIO_COMPILER_GUARD; + test_funcs[fn].task(NULL); + } + end = fio_time_micro(); + fprintf(stderr, + "\t%s: %zu mms\n", + test_funcs[fn].name, + (size_t)(end - start)); + } + } + + fprintf(stderr, + "\n* Speed testing locking schemes - no contention, long work "); + start = fio_time_micro(); + for (size_t i = 0; i < FIO___LOCK_TEST_THREADS; ++i) { + size_t result = 0; + FIO_COMPILER_GUARD; + fio___lock_speedtest_task_inner(&result); + } + end = fio_time_micro(); + fprintf(stderr, " %zu mms\n", (size_t)(end - start)); + clock_t long_work = end - start; + fprintf(stderr, "(%zu mms):\n", (size_t)long_work); + for (size_t test_repeat = 0; test_repeat < FIO___LOCK_TEST_REPEAT; + ++test_repeat) { + if (FIO___LOCK_TEST_REPEAT > 1) + fprintf(stderr, + "%s (%zu)\n", + (test_repeat ? "Round" : "Warmup"), + test_repeat); + for (size_t fn = 0; test_funcs[fn].name; ++fn) { + size_t result = 0; + test_funcs[fn].task((void *)&result); /* warmup */ + result = 0; + start = fio_time_micro(); + for (size_t i = 0; i < FIO___LOCK_TEST_THREADS; ++i) { + FIO_COMPILER_GUARD; + test_funcs[fn].task(&result); + } + end = fio_time_micro(); + fprintf(stderr, + "\t%s: %zu mms (%zu mms)\n", + test_funcs[fn].name, + (size_t)(end - start), + (size_t)(end - (start + long_work))); + FIO_ASSERT(result == (FIO___LOCK_TEST_TASK * FIO___LOCK_TEST_THREADS), + "%s final result error.", + test_funcs[fn].name); + } + } + + fprintf(stderr, + "\n* Speed testing locking schemes - %zu threads, long work (%zu " + "mms):\n", + (size_t)FIO___LOCK_TEST_THREADS, + (size_t)long_work); + for (size_t test_repeat = 0; test_repeat < FIO___LOCK_TEST_REPEAT; + ++test_repeat) { + if (FIO___LOCK_TEST_REPEAT > 1) + fprintf(stderr, + "%s (%zu)\n", + (test_repeat ? "Round" : "Warmup"), + test_repeat); + for (size_t fn = 0; test_funcs[fn].name; ++fn) { + size_t result = 0; + test_funcs[fn].task((void *)&result); /* warmup */ + result = 0; + start = fio_time_micro(); + for (size_t i = 0; i < FIO___LOCK_TEST_THREADS; ++i) { + fio_thread_create(threads + i, test_funcs[fn].task, &result); + } + for (size_t i = 0; i < FIO___LOCK_TEST_THREADS; ++i) { + fio_thread_join(threads + i); + } + end = fio_time_micro(); + fprintf(stderr, + "\t%s: %zu mms (%zu mms)\n", + test_funcs[fn].name, + (size_t)(end - start), + (size_t)(end - (start + long_work))); + FIO_ASSERT(result == (FIO___LOCK_TEST_TASK * FIO___LOCK_TEST_THREADS), + "%s final result error.", + test_funcs[fn].name); + } + } +} + +/* ***************************************************************************** +Testing function +***************************************************************************** */ + +FIO_SFUNC void fio____test_dynamic_types__stack_poisoner(void) { +#define FIO___STACK_POISON_LENGTH (1ULL << 18) + uint8_t buf[FIO___STACK_POISON_LENGTH]; + FIO_COMPILER_GUARD; + FIO_MEMSET(buf, (int)(0xA0U), FIO___STACK_POISON_LENGTH); + FIO_COMPILER_GUARD; + fio_rand_bytes(buf, FIO___STACK_POISON_LENGTH); + FIO_COMPILER_GUARD; + fio_trylock(buf); +#undef FIO___STACK_POISON_LENGTH +} + +FIO_SFUNC void fio_test_dynamic_types(void) { + char *filename = (char *)FIO_INCLUDE_FILE; + while (filename[0] == '.' && filename[1] == '/') + filename += 2; + fio____test_dynamic_types__stack_poisoner(); + fprintf(stderr, "===============\n"); + fprintf(stderr, "Testing facil.io CSTL (%s)\n", filename); + fprintf( + stderr, + "Version: \x1B[1m" FIO_VERSION_STRING "\x1B[0m\n" + "The facil.io library was originally coded by \x1B[1mBoaz Segev\x1B[0m.\n" + "Please give credit where credit is due.\n" + "\x1B[1mYour support is only fair\x1B[0m - give value for value.\n" + "(code contributions / donations)\n\n"); + fprintf(stderr, "===============\n"); + FIO_LOG_DEBUG("example FIO_LOG_DEBUG message."); + FIO_LOG_DEBUG2("example FIO_LOG_DEBUG2 message."); + FIO_LOG_INFO("example FIO_LOG_INFO message."); + FIO_LOG_WARNING("example FIO_LOG_WARNING message."); + FIO_LOG_SECURITY("example FIO_LOG_SECURITY message."); + FIO_LOG_ERROR("example FIO_LOG_ERROR message."); + FIO_LOG_FATAL("example FIO_LOG_FATAL message."); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, type_sizes)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, random)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, atomics)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, core)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, atol)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, math)(); + FIO_NAME_TEST(stl, math_speed)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, sort)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, url)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, glob_matching)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, imap_core)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, state)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, string_core_helpers)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, ary____test)(); + FIO_NAME_TEST(stl, ary2____test)(); + FIO_NAME_TEST(stl, ary3____test)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, uset___test_size_t)(); + FIO_NAME_TEST(stl, umap___test_size)(); + FIO_NAME_TEST(stl, omap___test_size_t)(); + FIO_NAME_TEST(stl, omap___test_size_lru)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, fio_big_str)(); + FIO_NAME_TEST(stl, fio_small_str)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, mustache)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, time)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, queue)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, cli)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, stream)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, poll)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, files)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, memalt)(); + fprintf(stderr, "===============\n"); + /* test memory allocator that initializes memory to zero */ + FIO_NAME_TEST(FIO_NAME(stl, fio_mem_test_safe), mem)(); + fprintf(stderr, "===============\n"); + /* test memory allocator that allows junk data in allocations */ + FIO_NAME_TEST(FIO_NAME(stl, fio_mem_test_unsafe), mem)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, sock)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, fiobj)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, io)(); + FIO_NAME_TEST(stl, pubsub)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, http_s)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, risky)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, sha1)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, sha2)(); + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, chacha)(); +#if !DEBUG + fprintf(stderr, "===============\n"); + FIO_NAME_TEST(stl, lock_speed)(); +#endif + fprintf(stderr, "===============\n"); + { + char timebuf[64]; + fio_time2rfc7231(timebuf, fio_time_real().tv_sec); + fprintf(stderr, + "On %s\n" + "Testing \x1B[1mPASSED\x1B[0m " + "for facil.io core version: " + "\x1B[1m" FIO_VERSION_STRING "\x1B[0m" + "\n", + timebuf); + } + fprintf(stderr, + "\nThe facil.io library was originally coded by \x1B[1mBoaz " + "Segev\x1B[0m.\n" + "\x1B[1mValue deserves to be valued.\x1B[0m\n" + "(please consider code contributions / donations)\n\n"); +} + +/* ***************************************************************************** +Testing cleanup +***************************************************************************** */ +#undef FIO_TEST_ALL +#undef FIO_TEST_REPEAT +/* ***************************************************************************** +C++ extern end +***************************************************************************** */ +/* support C++ */ +#ifdef __cplusplus +} +#endif +/* ***************************************************************************** +Finish testing segment +***************************************************************************** */ +#endif /* FIO_TEST_ALL / H___TESTS_FINISH___H */ + +#if defined(FIO___TEST_ALL_RECURSION) +#undef FIO___TEST_ALL_RECURSION +#define FIO_TEST_ALL +#endif +/* ***************************************************************************** + +***************************************************************************** */ +/* ************************************************************************* */ +#if !defined(H___FIO_CSTL_COMBINED___H) +/* ***************************************************************************** + Including facil.io modules for multi-file header option +***************************************************************************** */ +#ifndef FIO_INCLUDE_FILE +#define FIO_INCLUDE_FILE "fio-stl/include.h" +#include "000 core.h" +#include "001 patches.h" +#endif + +#include "000 dependencies.h" + +#include "001 header.h" +#ifdef FIO_LOG +#include "001 logging.h" +#endif +#ifdef FIO_MEMALT +#include "001 memalt.h" +#endif + +#ifdef FIO_ATOL +#include "002 atol.h" +#endif +#ifdef FIO_GLOB_MATCH +#include "002 glob matching.h" +#endif +#ifdef FIO_IMAP_CORE +#include "002 imap.h" +#endif +#ifdef FIO_MATH +#include "002 math.h" +#endif +#ifdef FIO_RAND +#include "002 random.h" +#endif +#ifdef FIO_SIGNAL +#include "002 signals.h" +#endif +#ifdef FIO_SORT_NAME +#include "002 sort.h" +#endif +#ifdef FIO_THREADS +#include "002 threads.h" +#endif +#if defined(FIO_URL) || defined(FIO_URI) +#include "002 url.h" +#endif + +#ifdef FIO_FILES +#include "004 files.h" +#endif +#ifdef FIO_JSON +#include "004 json.h" +#endif +#ifdef FIO_SOCK +#include "004 sock.h" +#endif +#if defined(FIO_STATE) && !defined(FIO___RECURSIVE_INCLUDE) +#include "004 state callbacks.h" +#endif +#ifdef FIO_TIME +#include "004 time.h" +#endif + +#if defined(FIO_CLI) && !defined(FIO___RECURSIVE_INCLUDE) +#include "005 cli.h" +#endif + +#if defined(FIO_MEMORY_NAME) || defined(FIO_MALLOC) || defined(FIOBJ_MALLOC) +#include "010 mem.h" +#endif + +#if defined(FIO_POLL) && !defined(FIO___RECURSIVE_INCLUDE) +#include "102 poll api.h" +#include "102 poll epoll.h" +#include "102 poll kqueue.h" +#include "102 poll poll.h" +#endif +#ifdef FIO_STR +#include "102 string core.h" +#endif +#ifdef FIO_STREAM +#include "102 stream.h" +#endif +#ifdef FIO_QUEUE +#include "102 queue.h" +#endif + +#ifdef FIO_MUSTACHE +#include "104 mustache.h" +#endif + +#if defined(FIO_STR_SMALL) || defined(FIO_STR_NAME) +#include "200 string.h" +#endif +#ifdef FIO_ARRAY_NAME +#include "201 array.h" +#endif +#if defined(FIO_UMAP_NAME) || defined(FIO_OMAP_NAME) || defined(FIO_MAP_NAME) +#include "210 map.h" +#endif + +#if defined(FIO_MAP2_NAME) +#include "210 map2.h" +#endif + +#include "249 reference counter.h" /* required: pointer tagging cleanup is here */ + +#if defined(FIO_FIOBJ) && !defined(FIO___RECURSIVE_INCLUDE) +#include "250 fiobj.h" +#endif + +#ifdef FIO_CRYPTO_CORE +#include "150 crypto core.h" +#endif + +#ifdef FIO_SHA1 +#include "152 sha1.h" +#endif +#ifdef FIO_SHA2 +#include "152 sha2.h" +#endif +#ifdef FIO_CHACHA +#include "152 chacha20poly1305.h" +#endif + +#ifdef FIO_ED25519 +#include "154 ed25519.h" +#endif + +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) +#include "400 io api.h" +#include "401 io types.h" +#include "402 io reactor.h" +#if defined(HAVE_OPENSSL) +#include "411 openssl.h" +#endif +#endif /* FIO_IO */ + +#if defined(FIO_PUBSUB) && !defined(FIO___RECURSIVE_INCLUDE) +#include "420 pubsub.h" +#endif + +#ifdef FIO_HTTP1_PARSER +#include "431 http1 parser.h" +#endif +#ifdef FIO_WEBSOCKET_PARSER +#include "431 websocket parser.h" +#endif + +#if defined(FIO_HTTP_HANDLE) && !defined(FIO___RECURSIVE_INCLUDE) +#include "431 http handle.h" +#endif + +#if defined(FIO_HTTP) && !defined(FIO___RECURSIVE_INCLUDE) +#include "439 http.h" +#endif + +#ifndef FIO___DEV___ +#include "700 cleanup.h" +#endif + +#if defined(FIO_TEST_ALL) && !defined(H___FIO_TESTS_START___H) +#include "900 tests start.h" +#include "902 atol.h" +#include "902 atomics.h" +#include "902 cli.h" +#include "902 core.h" +#include "902 files.h" +#include "902 fiobj.h" +#include "902 glob matching.h" +#include "902 http handle.h" +#include "902 imap.h" +#include "902 io.h" +#include "902 math.h" +#include "902 memalt.h" +#include "902 mustache.h" +#include "902 poll.h" +#include "902 pubsub.h" +#include "902 queue.h" +#include "902 random.h" +#include "902 sock.h" +#include "902 sort.h" +#include "902 state callbacks.h" +#include "902 stream.h" +#include "902 string core.h" +#include "902 time.h" +#include "902 url.h" +#include "903 chacha.h" +#include "903 sha.h" +#include "998 tests finish.h" +#endif + +#endif /* !H___FIO_CSTL_COMBINED___H */ +/* ************************************************************************* */ diff --git a/ext/iodine/fio.c b/ext/iodine/fio.c deleted file mode 100644 index c2980250..00000000 --- a/ext/iodine/fio.c +++ /dev/null @@ -1,11995 +0,0 @@ -/* ***************************************************************************** -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -***************************************************************************** */ -#ifdef __MINGW32__ -/** iodine/ruby specific, don use: #define FD_SETSIZE 1024 */ -#define FIO_FORCE_MALLOC 1 -#define FIO_DISABLE_HOT_RESTART 1 -#endif - -#include - -#define FIO_INCLUDE_STR -#include - -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_INCLUDE_LINKED_LIST -#include - -#include -#include -#include -#include -#ifndef __MINGW32__ -#include -#endif -#include - -#ifndef __MINGW32__ -#include -#include -#include - -#include -#include -#include -#include -#endif -#include -#include -#ifndef __MINGW32__ -#include -#include - -#include -#endif - -#ifdef __MINGW32__ -#include -#include -#include -#endif - -#if HAVE_OPENSSL -#include -#include -#include -#endif - -#if HAVE_OPENSSL -#include -#include -#include -#endif - -/* force poll for testing? */ -#ifndef FIO_ENGINE_POLL -#define FIO_ENGINE_POLL 0 -#endif - -#if !FIO_ENGINE_POLL && !FIO_ENGINE_EPOLL && !FIO_ENGINE_KQUEUE && !FIO_ENGINE_WSAPOLL -#if defined(__linux__) -#define FIO_ENGINE_EPOLL 1 -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ - defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) -#define FIO_ENGINE_KQUEUE 1 -#elif defined(__MINGW32__) -#define FIO_ENGINE_WSAPOLL 1 -#else -#define FIO_ENGINE_POLL 1 -#endif -#endif - -/* for kqueue and epoll only */ -#ifndef FIO_POLL_MAX_EVENTS -#define FIO_POLL_MAX_EVENTS 64 -#endif - -#ifndef FIO_POLL_TICK -#define FIO_POLL_TICK 1000 -#endif - -#ifndef FIO_USE_URGENT_QUEUE -#ifndef __MINGW32__ -#define FIO_USE_URGENT_QUEUE 1 -#endif -#endif - -#ifndef DEBUG_SPINLOCK -#define DEBUG_SPINLOCK 0 -#endif - -/* Slowloris mitigation (must be less than 1<<16) */ -#ifndef FIO_SLOWLORIS_LIMIT -#define FIO_SLOWLORIS_LIMIT (1 << 10) -#endif - -#ifndef FIO_TLS_WEAK -#define FIO_TLS_WEAK __attribute__((weak)) -#endif - -/* Mitigates MAP_ANONYMOUS not being defined on older versions of MacOS */ -#if !defined(MAP_ANONYMOUS) -#if defined(MAP_ANON) -#define MAP_ANONYMOUS MAP_ANON -#else -#define MAP_ANONYMOUS 0 -#endif -#endif - -/* ***************************************************************************** -Windows support functions -***************************************************************************** */ -#ifdef __MINGW32__ - -#define WIFEXITED(s) (!((s)&0xFF)) -#define WEXITSTATUS(s) (((s)>>8)&0xFF) - -FARPROC accept_ptr; -FARPROC bind_ptr; -FARPROC closesocket_ptr; -FARPROC connect_ptr; -FARPROC getsockopt_ptr; -FARPROC ioctlsocket_ptr; -FARPROC listen_ptr; -FARPROC recv_ptr; -FARPROC send_ptr; -FARPROC setsockopt_ptr; -FARPROC socket_ptr; - -int fork() { - fprintf(stderr, "fork() is not supported on Windows.\n"); - errno = ENOSYS; - return -1; -} - -int ioctl (int fd, u_long request, int* argp) { - int error; - u_long flags; - flags = *argp; - error = ioctlsocket_ptr(fd, request, &flags); - if (error > 0) { return -1; } - else { return 0; } -} - -int kill(int pid, int sig) { - /* Credit to Jan Biedermann (GitHub: @janbiedermann) */ - HANDLE handle; - DWORD status; - if (sig < 0 || sig >= NSIG) { - errno = EINVAL; - return -1; - } -#ifdef SIGCONT - if (sig == SIGCONT) { - errno = ENOSYS; - return -1; - } -#endif - - if (pid == -1) - pid = 0; - - if (!pid) - handle = GetCurrentProcess(); - else - handle = - OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, FALSE, pid); - if (!handle) - goto something_went_wrong; - - switch (sig) { - case SIGKILL: - case SIGTERM: - case SIGINT: /* terminate */ - if (!TerminateProcess(handle, 1)) - goto something_went_wrong; - break; - case 0: /* check status */ - if (!GetExitCodeProcess(handle, &status)) - goto something_went_wrong; - if (status != STILL_ACTIVE) { - errno = ESRCH; - goto cleanup_after_error; - } - break; - default: /* not supported? */ - errno = ENOSYS; - goto cleanup_after_error; - } - - if (pid) { - CloseHandle(handle); - } - return 0; - -something_went_wrong: - switch (GetLastError()) { - case ERROR_INVALID_PARAMETER: - errno = ESRCH; - break; - case ERROR_ACCESS_DENIED: - errno = EPERM; - if (handle && GetExitCodeProcess(handle, &status) && status != STILL_ACTIVE) - errno = ESRCH; - break; - default: - errno = GetLastError(); - } -cleanup_after_error: - if (handle && pid) - CloseHandle(handle); - return -1; -} - -ssize_t pread(int fd, void *buf, size_t count, off_t offset) { - /* Credit to Jan Biedermann (GitHub: @janbiedermann) */ - ssize_t bytes_read = 0; - HANDLE handle = (HANDLE)_get_osfhandle(fd); - if (handle == INVALID_HANDLE_VALUE) - goto bad_file; - OVERLAPPED overlapped = {0}; - if (offset > 0) - overlapped.Offset = offset; - if (ReadFile(handle, buf, count, (u_long *)&bytes_read, &overlapped)) - return bytes_read; - if (GetLastError() == ERROR_HANDLE_EOF) - return bytes_read; - errno = EIO; - return -1; -bad_file: - errno = EBADF; - return -1; -} - -ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { - /* Credit to Jan Biedermann (GitHub: @janbiedermann) */ - ssize_t bytes_written = 0; - HANDLE handle = (HANDLE)_get_osfhandle(fd); - if (handle == INVALID_HANDLE_VALUE) - goto bad_file; - OVERLAPPED overlapped = {0}; - if (offset > 0) - overlapped.Offset = offset; - if (WriteFile(handle, buf, count, (u_long *)&bytes_written, &overlapped)) - return bytes_written; - errno = EIO; - return -1; -bad_file: - errno = EBADF; - return -1; -} - -pid_t wait(int *stat_loc) { - fprintf(stderr, "wait() is not supported on Windows.\n"); - errno = ENOSYS; - return -1; -} - -pid_t waitpid(pid_t pid, int *stat_loc, int options) { - fprintf(stderr, "waitpid() is not supported on Windows.\n"); - errno = ENOSYS; - return -1; -} -#endif - -/* ***************************************************************************** -Event deferring (declarations) -***************************************************************************** */ - -static void deferred_on_close(void *uuid_, void *pr_); -static void deferred_on_shutdown(void *arg, void *arg2); -static void deferred_on_ready(void *arg, void *arg2); -static void deferred_on_data(void *uuid, void *arg2); -static void deferred_ping(void *arg, void *arg2); - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - Main State Machine Data Structures - - - - - - - - - - - - -***************************************************************************** */ - -typedef void (*fio_uuid_link_fn)(void *); -#define FIO_SET_NAME fio_uuid_links -#define FIO_SET_OBJ_TYPE fio_uuid_link_fn -#define FIO_SET_OBJ_COMPARE(o1, o2) 1 -#include - -/** User-space socket buffer data */ -typedef struct fio_packet_s fio_packet_s; -struct fio_packet_s { - fio_packet_s *next; - int (*write_func)(int fd, struct fio_packet_s *packet); - void (*dealloc)(void *buffer); - union { - void *buffer; - intptr_t fd; - } data; - uintptr_t offset; - uintptr_t length; -}; - -/** Connection data (fd_data) */ -typedef struct { - /* current data to be send */ - fio_packet_s *packet; - /** the last packet in the queue. */ - fio_packet_s **packet_last; - /* Data sent so far */ - size_t sent; - /* fd protocol */ - fio_protocol_s *protocol; - /* timer handler */ - time_t active; - /** The number of pending packets that are in the queue. */ - uint16_t packet_count; - /* timeout settings */ - uint8_t timeout; - /* indicates that the fd should be considered scheduled (added to poll) */ - fio_lock_i scheduled; - /* protocol lock */ - fio_lock_i protocol_lock; - /* used to convert `fd` to `uuid` and validate connections */ - uint8_t counter; - /* socket lock */ - fio_lock_i sock_lock; - /** Connection is open */ - uint8_t open; - /** indicated that the connection should be closed. */ - uint8_t close; - /** peer address length */ - uint8_t addr_len; - /** peer address */ - uint8_t addr[48]; - /** RW hooks. */ - fio_rw_hook_s *rw_hooks; - /** RW udata. */ - void *rw_udata; - /* Objects linked to the UUID */ - fio_uuid_links_s links; -#ifdef __MINGW32__ - /* Winsock operating system socket handle */ - SOCKET socket_handle; - int osffd; -#endif -} fio_fd_data_s; - -typedef struct { - struct timespec last_cycle; - /* connection capacity */ - uint32_t capa; - /* connections counted towards shutdown (NOT while running) */ - uint32_t connection_count; - /* thread list */ - fio_ls_s thread_ids; - /* active workers */ - uint16_t workers; - /* timer handler */ - uint16_t threads; - /* timeout review loop flag */ - uint8_t need_review; - /* spinning down process */ - uint8_t volatile active; - /* worker process flag - true also for single process */ - uint8_t is_worker; - /* polling and global lock */ - fio_lock_i lock; - /* The highest active fd with a protocol object */ - uint32_t max_protocol_fd; - /* timer handler */ - pid_t parent; -#if FIO_ENGINE_POLL || FIO_ENGINE_WSAPOLL - struct pollfd *poll; -#endif - fio_fd_data_s info[]; -} fio_data_s; - -/** The logging level */ -#if DEBUG -int FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEBUG; -#else -int FIO_LOG_LEVEL = FIO_LOG_LEVEL_INFO; -#endif -static fio_data_s *fio_data = NULL; - -/* used for protocol locking by task type. */ -typedef struct { - fio_lock_i locks[3]; - unsigned rsv : 8; -} protocol_metadata_s; - -/* used for accessing the protocol locking in a safe byte aligned way. */ -union protocol_metadata_union_u { - size_t opaque; - protocol_metadata_s meta; -}; - -#define fd_data(fd) (fio_data->info[(uintptr_t)(fd)]) -#define uuid_data(uuid) fd_data(fio_uuid2fd((uuid))) -#define fd2uuid(fd) \ - ((intptr_t)((((uintptr_t)(fd)) << 8) | fd_data((fd)).counter)) - -/** - * Returns the maximum number of open files facil.io can handle per worker - * process. - * - * Total OS limits might apply as well but aren't shown. - * - * The value of 0 indicates either that the facil.io library wasn't initialized - * yet or that it's resources were released. - */ -size_t fio_capa(void) { - if (fio_data) - return fio_data->capa; - return 0; -} - -/* ***************************************************************************** -Packet allocation (for socket's user-buffer) -***************************************************************************** */ - -static inline void fio_packet_free(fio_packet_s *packet) { - packet->dealloc(packet->data.buffer); - fio_free(packet); -} -static inline fio_packet_s *fio_packet_alloc(void) { - fio_packet_s *packet = fio_malloc(sizeof(*packet)); - FIO_ASSERT_ALLOC(packet); - return packet; -} - -/* ***************************************************************************** -Core Connection Data Clearing -***************************************************************************** */ - -/* resets connection data, marking it as either open or closed. */ -static inline int fio_clear_fd(intptr_t fd, uint8_t is_open) { - fio_packet_s *packet; - fio_protocol_s *protocol; - fio_rw_hook_s *rw_hooks; - void *rw_udata; - fio_uuid_links_s links; - fio_lock(&(fd_data(fd).sock_lock)); - links = fd_data(fd).links; - packet = fd_data(fd).packet; - protocol = fd_data(fd).protocol; - rw_hooks = fd_data(fd).rw_hooks; - rw_udata = fd_data(fd).rw_udata; -#ifdef __MINGW32__ - SOCKET socket_handle = fd_data(fd).socket_handle; - int osffd = fd_data(fd).osffd; -#endif - fd_data(fd) = (fio_fd_data_s){ - .open = is_open, - .sock_lock = fd_data(fd).sock_lock, - .protocol_lock = fd_data(fd).protocol_lock, - .rw_hooks = (fio_rw_hook_s *)&FIO_DEFAULT_RW_HOOKS, - .counter = fd_data(fd).counter + 1, - .packet_last = &fd_data(fd).packet, -#ifdef __MINGW32__ - .socket_handle = socket_handle, - .osffd = osffd, -#endif - }; - if (is_open && fio_data->max_protocol_fd < fd) { - fio_data->max_protocol_fd = fd; - } else { - while (fio_data->max_protocol_fd && - !fd_data(fio_data->max_protocol_fd).open) - --fio_data->max_protocol_fd; - } - fio_unlock(&(fd_data(fd).sock_lock)); - if (rw_hooks && rw_hooks->cleanup) - rw_hooks->cleanup(rw_udata); - while (packet) { - fio_packet_s *tmp = packet; - packet = packet->next; - fio_packet_free(tmp); - } - if (fio_uuid_links_count(&links)) { - FIO_SET_FOR_LOOP(&links, pos) { - if (pos->hash) - pos->obj((void *)pos->hash); - } - } - fio_uuid_links_free(&links); - if (protocol && protocol->on_close) { - fio_defer(deferred_on_close, (void *)fd2uuid(fd), protocol); - } - // FIO_LOG_DEBUG("FD %d re-initialized (state: %p-%s).", (int)fd, - // (void *)fd2uuid(fd), (is_open ? "open" : "closed")); - return 0; -} - -static inline void fio_force_close_in_poll(intptr_t uuid) { - uuid_data(uuid).close = 2; - fio_force_close(uuid); -} - -/* ***************************************************************************** -Protocol Locking and UUID validation -***************************************************************************** */ - -/* Macro for accessing the protocol locking / metadata. */ -#define prt_meta(prt) (((union protocol_metadata_union_u *)(&(prt)->rsv))->meta) - -/** locks a connection's protocol returns a pointer that need to be unlocked. */ -inline static fio_protocol_s *protocol_try_lock(intptr_t fd, - enum fio_protocol_lock_e type) { - errno = 0; - if (fio_trylock(&fd_data(fd).protocol_lock)) - goto would_block; - fio_protocol_s *pr = fd_data(fd).protocol; - if (!pr) { - fio_unlock(&fd_data(fd).protocol_lock); - goto invalid; - } - if (fio_trylock(&prt_meta(pr).locks[type])) { - fio_unlock(&fd_data(fd).protocol_lock); - goto would_block; - } - fio_unlock(&fd_data(fd).protocol_lock); - return pr; -would_block: - errno = EWOULDBLOCK; - return NULL; -invalid: - errno = EBADF; - return NULL; -} -/** See `fio_protocol_try_lock` for details. */ -inline static void protocol_unlock(fio_protocol_s *pr, - enum fio_protocol_lock_e type) { - fio_unlock(&prt_meta(pr).locks[type]); -} - -/** returns 1 if the UUID is valid and 0 if it isn't. */ -#define uuid_is_valid(uuid) \ - ((intptr_t)(uuid) >= 0 && \ - ((uint32_t)fio_uuid2fd((uuid))) < fio_data->capa && \ - ((uintptr_t)(uuid)&0xFF) == uuid_data((uuid)).counter) - -/* public API. */ -fio_protocol_s *fio_protocol_try_lock(intptr_t uuid, - enum fio_protocol_lock_e type) { - if (!uuid_is_valid(uuid)) { - errno = EBADF; - return NULL; - } - return protocol_try_lock(fio_uuid2fd(uuid), type); -} - -/* public API. */ -void fio_protocol_unlock(fio_protocol_s *pr, enum fio_protocol_lock_e type) { - protocol_unlock(pr, type); -} - -/* ***************************************************************************** -UUID validation and state -***************************************************************************** */ - -/* public API. */ -intptr_t fio_fd2uuid(int fd) { - if (fd < 0 || (size_t)fd >= fio_data->capa) - return -1; - if (!fd_data(fd).open) { - fio_lock(&fd_data(fd).protocol_lock); - fio_clear_fd(fd, 1); - fio_unlock(&fd_data(fd).protocol_lock); - } - return fd2uuid(fd); -} - -/* public API. */ -int fio_is_valid(intptr_t uuid) { return uuid_is_valid(uuid); } - -/* public API. */ -int fio_is_closed(intptr_t uuid) { - return !uuid_is_valid(uuid) || !uuid_data(uuid).open || uuid_data(uuid).close; -} - -void fio_stop(void) { - if (fio_data) - fio_data->active = 0; -} - -/* public API. */ -int16_t fio_is_running(void) { return fio_data && fio_data->active; } - -/* public API. */ -struct timespec fio_last_tick(void) { - return fio_data->last_cycle; -} - -#define touchfd(fd) fd_data((fd)).active = fio_data->last_cycle.tv_sec - -/* public API. */ -void fio_touch(intptr_t uuid) { - if (uuid_is_valid(uuid)) - touchfd(fio_uuid2fd(uuid)); -} - -/* public API. */ -fio_str_info_s fio_peer_addr(intptr_t uuid) { - if (fio_is_closed(uuid) || !uuid_data(uuid).addr_len) - return (fio_str_info_s){.data = NULL, .len = 0, .capa = 0}; - return (fio_str_info_s){.data = (char *)uuid_data(uuid).addr, - .len = uuid_data(uuid).addr_len, - .capa = 0}; -} - -/** - * Writes the local machine address (qualified host name) to the buffer. - * - * Returns the amount of data written (excluding the NUL byte). - * - * `limit` is the maximum number of bytes in the buffer, including the NUL byte. - * - * If the returned value == limit - 1, the result might have been truncated. - * - * If 0 is returned, an erro might have occured (see `errno`) and the contents - * of `dest` is undefined. - */ -size_t fio_local_addr(char *dest, size_t limit) { - if (gethostname(dest, limit)) - return 0; - - struct addrinfo hints, *info; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 - hints.ai_socktype = SOCK_STREAM; // TCP stream sockets - hints.ai_flags = AI_CANONNAME; // get cannonical name - - if (getaddrinfo(dest, "http", &hints, &info) != 0) - return 0; - - for (struct addrinfo *pos = info; pos; pos = pos->ai_next) { - if (pos->ai_canonname) { - size_t len = strlen(pos->ai_canonname); - if (len >= limit) - len = limit - 1; - memcpy(dest, pos->ai_canonname, len); - dest[len] = 0; - freeaddrinfo(info); - return len; - } - } - - freeaddrinfo(info); - return 0; -} - -/* ***************************************************************************** -UUID attachments (linking objects to the UUID's lifetime) -***************************************************************************** */ - -/* public API. */ -void fio_uuid_link(intptr_t uuid, void *obj, void (*on_close)(void *obj)) { - if (!uuid_is_valid(uuid)) - goto invalid; - fio_lock(&uuid_data(uuid).sock_lock); - if (!uuid_is_valid(uuid)) - goto locked_invalid; - fio_uuid_links_overwrite(&uuid_data(uuid).links, (uintptr_t)obj, on_close, - NULL); - fio_unlock(&uuid_data(uuid).sock_lock); - return; -locked_invalid: - fio_unlock(&uuid_data(uuid).sock_lock); -invalid: - errno = EBADF; - on_close(obj); -} - -/* public API. */ -int fio_uuid_unlink(intptr_t uuid, void *obj) { - if (!uuid_is_valid(uuid)) - goto invalid; - fio_lock(&uuid_data(uuid).sock_lock); - if (!uuid_is_valid(uuid)) - goto locked_invalid; - /* default object comparison is always true */ - int ret = - fio_uuid_links_remove(&uuid_data(uuid).links, (uintptr_t)obj, NULL, NULL); - if (ret) - errno = ENOTCONN; - fio_unlock(&uuid_data(uuid).sock_lock); - return ret; -locked_invalid: - fio_unlock(&uuid_data(uuid).sock_lock); -invalid: - errno = EBADF; - return -1; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - Default Thread / Fork handler - - And Concurrency Helpers - - - - - - - - - - - - -***************************************************************************** */ - -/** -OVERRIDE THIS to replace the default `fork` implementation. - -Behaves like the system's `fork`. -*/ -#pragma weak fio_fork -int __attribute__((weak)) fio_fork(void) { return fork(); } - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Accepts a pointer to a function and a single argument that should be executed - * within a new thread. - * - * The function should allocate memory for the thread object and return a - * pointer to the allocated memory that identifies the thread. - * - * On error NULL should be returned. - */ -#pragma weak fio_thread_new -void *__attribute__((weak)) -fio_thread_new(void *(*thread_func)(void *), void *arg) { - pthread_t *thread = malloc(sizeof(*thread)); - FIO_ASSERT_ALLOC(thread); - if (pthread_create(thread, NULL, thread_func, arg)) - goto error; - return thread; -error: - free(thread); - return NULL; -} - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Frees the memory associated with a thread identifier (allows the thread to - * run it's course, just the identifier is freed). - */ -#pragma weak fio_thread_free -void __attribute__((weak)) fio_thread_free(void *p_thr) { - if (*((pthread_t *)p_thr)) { - pthread_detach(*((pthread_t *)p_thr)); - } - free(p_thr); -} - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Accepts a pointer returned from `fio_thread_new` (should also free any - * allocated memory) and joins the associated thread. - * - * Return value is ignored. - */ -#pragma weak fio_thread_join -int __attribute__((weak)) fio_thread_join(void *p_thr) { - if (!p_thr || !(*((pthread_t *)p_thr))) - return -1; - pthread_join(*((pthread_t *)p_thr), NULL); - *((pthread_t *)p_thr) = (pthread_t)NULL; - free(p_thr); - return 0; -} - -/* ***************************************************************************** -Suspending and renewing thread execution (signaling events) -***************************************************************************** */ - -#ifndef DEFER_THROTTLE -#define DEFER_THROTTLE 2097148UL -#endif -#ifndef FIO_DEFER_THROTTLE_LIMIT -#define FIO_DEFER_THROTTLE_LIMIT 134217472UL -#endif - -/** - * The polling throttling model will use pipes to suspend and resume threads... - * - * However, it seems the approach is currently broken, at least on macOS. - * I don't know why. - * - * If polling is disabled, the progressive throttling model will be used. - * - * The progressive throttling makes concurrency and parallelism likely, but uses - * progressive nano-sleep throttling system that is less exact. - */ -#ifndef FIO_DEFER_THROTTLE_POLL -#ifdef __MINGW32__ -#define FIO_DEFER_THROTTLE_POLL 1 -#else -#define FIO_DEFER_THROTTLE_POLL 0 -#endif -#endif - -typedef struct fio_thread_queue_s { - fio_ls_embd_s node; -#ifdef __MINGW32__ - HANDLE handle; - int in_list; -#else - int fd_wait; /* used for waiting (read signal) */ - int fd_signal; /* used for signalling (write) */ -#endif -} fio_thread_queue_s; - -fio_ls_embd_s fio_thread_queue = FIO_LS_INIT(fio_thread_queue); -fio_lock_i fio_thread_lock = FIO_LOCK_INIT; - -static pthread_key_t fio_thread_data_key; -static pthread_once_t fio_thread_data_once = PTHREAD_ONCE_INIT; -static void init_fio_thread_data_key(void) { - pthread_key_create(&fio_thread_data_key, free); -} -static void init_fio_thread_data_ptr(void) { - fio_thread_queue_s *fio_thread_data = malloc(sizeof(fio_thread_queue_s)); - FIO_ASSERT_ALLOC(fio_thread_data); - memset(fio_thread_data, 0, sizeof(fio_thread_queue_s)); -#ifdef __MINGW32__ - fio_thread_data->handle = INVALID_HANDLE_VALUE; -#else - fio_thread_data->fd_wait = -1; - fio_thread_data->fd_signal = -1; -#endif - pthread_setspecific(fio_thread_data_key, fio_thread_data); -} - -FIO_FUNC inline void fio_thread_make_suspendable(void) { - pthread_once(&fio_thread_data_once, init_fio_thread_data_key); - fio_thread_queue_s *fio_thread_data = (fio_thread_queue_s *)pthread_getspecific(fio_thread_data_key); - if (!fio_thread_data) { - init_fio_thread_data_ptr(); - fio_thread_data = (fio_thread_queue_s *)pthread_getspecific(fio_thread_data_key); - } -#ifdef __MINGW32__ - /** create automatically reseting event */ - fio_thread_data->handle = CreateEvent(NULL, FALSE, FALSE, TEXT("thread signal")); - fio_thread_data->in_list = 0; -#else - if (fio_thread_data->fd_signal >= 0) - return; - int fd[2] = {0, 0}; - int ret = pipe(fd); - FIO_ASSERT(ret == 0, "`pipe` failed."); - FIO_ASSERT(fio_set_non_block(fd[0]) == 0, - "(fio) couldn't set internal pipe to non-blocking mode."); - FIO_ASSERT(fio_set_non_block(fd[1]) == 0, - "(fio) couldn't set internal pipe to non-blocking mode."); - fio_thread_data->fd_wait = fd[0]; - fio_thread_data->fd_signal = fd[1]; -#endif -} - -FIO_FUNC inline void fio_thread_cleanup(void) { - fio_thread_queue_s *fio_thread_data = (fio_thread_queue_s *)pthread_getspecific(fio_thread_data_key); -#ifdef __MINGW32__ - HANDLE h = fio_thread_data->handle; - fio_thread_data->handle = INVALID_HANDLE_VALUE; - CloseHandle(h); -#else - if (fio_thread_data->fd_signal < 0) - return; - close(fio_thread_data->fd_wait); - close(fio_thread_data->fd_signal); - fio_thread_data->fd_wait = -1; - fio_thread_data->fd_signal = -1; -#endif -} - -/* suspend thread execution (might be resumed unexpectedly) */ -FIO_FUNC void fio_thread_suspend(void) { - fio_thread_queue_s *fio_thread_data = (fio_thread_queue_s *)pthread_getspecific(fio_thread_data_key); -#ifdef __MINGW32__ - fio_lock(&fio_thread_lock); - /** don't add thread to queue if its already in there - * to prevent queue breakage - * can happen if WaitForSingleObject returns for other reasons */ - if (!fio_thread_data->in_list) { - fio_ls_embd_push(&fio_thread_queue, &fio_thread_data->node); - fio_thread_data->in_list = 1; - } - fio_unlock(&fio_thread_lock); - WaitForSingleObject(fio_thread_data->handle, 500); -#else - fio_lock(&fio_thread_lock); - fio_ls_embd_push(&fio_thread_queue, &fio_thread_data->node); - fio_unlock(&fio_thread_lock); - struct pollfd list = { - .events = (POLLPRI | POLLIN), - .fd = fio_thread_data->fd_wait, - }; - if (poll(&list, 1, 5000) > 0) { - /* thread was removed from the list through signal */ - uint64_t data; - int r = read(fio_thread_data->fd_wait, &data, sizeof(data)); - (void)r; - } else { - /* remove self from list */ - fio_lock(&fio_thread_lock); - fio_ls_embd_remove(&fio_thread_data->node); - fio_unlock(&fio_thread_lock); - } -#endif -} - -/* wake up a single thread */ -FIO_FUNC void fio_thread_signal(void) { -#ifdef __MINGW32__ - fio_lock(&fio_thread_lock); - fio_thread_queue_s *t = (fio_thread_queue_s *)fio_ls_embd_shift(&fio_thread_queue); - if (t) { - t->in_list = 0; - fio_unlock(&fio_thread_lock); - SetEvent(t->handle); - } else { - fio_unlock(&fio_thread_lock); - } -#else - fio_thread_queue_s *t; - int fd = -2; - fio_lock(&fio_thread_lock); - t = (fio_thread_queue_s *)fio_ls_embd_shift(&fio_thread_queue); - if (t) - fd = t->fd_signal; - fio_unlock(&fio_thread_lock); - if (fd >= 0) { - uint64_t data = 1; - int r = write(fd, (void *)&data, sizeof(data)); - (void)r; - } else if (fd == -1) { - /* hardly the best way, but there's a thread sleeping on air */ - kill(getpid(), SIGCONT); - } -#endif -} - -/* wake up all threads */ -FIO_FUNC void fio_thread_broadcast(void) { - while (fio_ls_embd_any(&fio_thread_queue)) { - fio_thread_signal(); - } -} - -static pthread_key_t static_throttle_key; -static pthread_once_t static_throttle_once = PTHREAD_ONCE_INIT; -static void init_static_throttle_key(void) { - pthread_key_create(&static_throttle_key, NULL); - pthread_setspecific(static_throttle_key, (void *)262143UL); -} - -static size_t fio_poll(void); -/** - * A thread entering this function should wait for new events. - */ -static void fio_defer_thread_wait(void) { -#if FIO_ENGINE_POLL - fio_poll(); - return; -#endif - if (FIO_DEFER_THROTTLE_POLL) { - fio_thread_suspend(); - } else { - /* keeps threads active (concurrent), but reduces performance */ - pthread_once(&static_throttle_once, init_static_throttle_key); - size_t static_throttle = (size_t)pthread_getspecific(static_throttle_key); - fio_throttle_thread(static_throttle); - if (fio_defer_has_queue()) - pthread_setspecific(static_throttle_key, (void *)1); - else if (static_throttle < FIO_DEFER_THROTTLE_LIMIT) - pthread_setspecific(static_throttle_key, (void *)((static_throttle << 1) | 1)); - } -} - -static inline void fio_defer_on_thread_start(void) { - if (FIO_DEFER_THROTTLE_POLL) - fio_thread_make_suspendable(); -} -static inline void fio_defer_thread_signal(void) { - if (FIO_DEFER_THROTTLE_POLL) - fio_thread_signal(); -} -static inline void fio_defer_on_thread_end(void) { - if (FIO_DEFER_THROTTLE_POLL) { - fio_thread_broadcast(); - fio_thread_cleanup(); - } -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - Task Management - - Task / Event schduling and execution - - - - - - - - - - - - - - - -***************************************************************************** */ - -#ifndef DEFER_QUEUE_BLOCK_COUNT -#if UINTPTR_MAX <= 0xFFFFFFFF -/* Almost a page of memory on most 32 bit machines: ((4096/4)-8)/3 */ -#define DEFER_QUEUE_BLOCK_COUNT 338 -#else -/* Almost a page of memory on most 64 bit machines: ((4096/8)-8)/3 */ -#define DEFER_QUEUE_BLOCK_COUNT 168 -#endif -#endif - -/* task node data */ -typedef struct { - void (*func)(void *, void *); - void *arg1; - void *arg2; -} fio_defer_task_s; - -/* task queue block */ -typedef struct fio_defer_queue_block_s fio_defer_queue_block_s; -struct fio_defer_queue_block_s { - fio_defer_task_s tasks[DEFER_QUEUE_BLOCK_COUNT]; - fio_defer_queue_block_s *next; - size_t write; - size_t read; - unsigned char state; -}; - -/* task queue object */ -typedef struct { /* a lock for the state machine, used for multi-threading - support */ - fio_lock_i lock; - /* current active block to pop tasks */ - fio_defer_queue_block_s *reader; - /* current active block to push tasks */ - fio_defer_queue_block_s *writer; - /* static, built-in, queue */ - fio_defer_queue_block_s static_queue; -} fio_task_queue_s; - -/* the state machine - this holds all the data about the task queue and pool */ -static fio_task_queue_s task_queue_normal = { - .reader = &task_queue_normal.static_queue, - .writer = &task_queue_normal.static_queue}; - -static fio_task_queue_s task_queue_urgent = { - .reader = &task_queue_urgent.static_queue, - .writer = &task_queue_urgent.static_queue}; - -/* ***************************************************************************** -Internal Task API -***************************************************************************** */ - -#if DEBUG -static size_t fio_defer_count_alloc, fio_defer_count_dealloc; -#define COUNT_ALLOC fio_atomic_add(&fio_defer_count_alloc, 1) -#define COUNT_DEALLOC fio_atomic_add(&fio_defer_count_dealloc, 1) -#define COUNT_RESET \ - do { \ - fio_defer_count_alloc = fio_defer_count_dealloc = 0; \ - } while (0) -#else -#define COUNT_ALLOC -#define COUNT_DEALLOC -#define COUNT_RESET -#endif - -static inline void fio_defer_push_task_fn(fio_defer_task_s task, - fio_task_queue_s *queue) { - fio_lock(&queue->lock); - - /* test if full */ - if (queue->writer->state && queue->writer->write == queue->writer->read) { - /* return to static buffer or allocate new buffer */ - if (queue->static_queue.state == 2) { - queue->writer->next = &queue->static_queue; - } else { - queue->writer->next = fio_malloc(sizeof(*queue->writer->next)); - COUNT_ALLOC; - if (!queue->writer->next) - goto critical_error; - } - queue->writer = queue->writer->next; - queue->writer->write = 0; - queue->writer->read = 0; - queue->writer->state = 0; - queue->writer->next = NULL; - } - - /* place task and finish */ - queue->writer->tasks[queue->writer->write++] = task; - /* cycle buffer */ - if (queue->writer->write == DEFER_QUEUE_BLOCK_COUNT) { - queue->writer->write = 0; - queue->writer->state = 1; - } - fio_unlock(&queue->lock); - return; - -critical_error: - fio_unlock(&queue->lock); - FIO_ASSERT_ALLOC(NULL) -} - -#define fio_defer_push_task(func_, arg1_, arg2_) \ - do { \ - fio_defer_push_task_fn( \ - (fio_defer_task_s){.func = func_, .arg1 = arg1_, .arg2 = arg2_}, \ - &task_queue_normal); \ - fio_defer_thread_signal(); \ - } while (0) - -#if FIO_USE_URGENT_QUEUE -#define fio_defer_push_urgent(func_, arg1_, arg2_) \ - fio_defer_push_task_fn( \ - (fio_defer_task_s){.func = func_, .arg1 = arg1_, .arg2 = arg2_}, \ - &task_queue_urgent) -#else -#define fio_defer_push_urgent(func_, arg1_, arg2_) \ - fio_defer_push_task(func_, arg1_, arg2_) -#endif - -static inline fio_defer_task_s fio_defer_pop_task(fio_task_queue_s *queue) { - fio_defer_task_s ret = (fio_defer_task_s){.func = NULL}; - fio_defer_queue_block_s *to_free = NULL; - /* lock the state machine, grab/create a task and place it at the tail */ - fio_lock(&queue->lock); - - /* empty? */ - if (queue->reader->write == queue->reader->read && !queue->reader->state) - goto finish; - /* collect task */ - ret = queue->reader->tasks[queue->reader->read++]; - /* cycle */ - if (queue->reader->read == DEFER_QUEUE_BLOCK_COUNT) { - queue->reader->read = 0; - queue->reader->state = 0; - } - /* did we finish the queue in the buffer? */ - if (queue->reader->write == queue->reader->read) { - if (queue->reader->next) { - to_free = queue->reader; - queue->reader = queue->reader->next; - } else { - if (queue->reader != &queue->static_queue && - queue->static_queue.state == 2) { - to_free = queue->reader; - queue->writer = &queue->static_queue; - queue->reader = &queue->static_queue; - } - queue->reader->write = queue->reader->read = queue->reader->state = 0; - } - } - -finish: - if (to_free == &queue->static_queue) { - queue->static_queue.state = 2; - queue->static_queue.next = NULL; - } - fio_unlock(&queue->lock); - - if (to_free && to_free != &queue->static_queue) { - fio_free(to_free); - COUNT_DEALLOC; - } - return ret; -} - -/* same as fio_defer_clear_queue , just inlined */ -static inline void fio_defer_clear_tasks_for_queue(fio_task_queue_s *queue) { - fio_lock(&queue->lock); - while (queue->reader) { - fio_defer_queue_block_s *tmp = queue->reader; - queue->reader = queue->reader->next; - if (tmp != &queue->static_queue) { - COUNT_DEALLOC; - free(tmp); - } - } - queue->static_queue = (fio_defer_queue_block_s){.next = NULL}; - queue->reader = queue->writer = &queue->static_queue; - fio_unlock(&queue->lock); -} - -/** - * Performs a single task from the queue, returning -1 if the queue was empty. - */ -static inline int -fio_defer_perform_single_task_for_queue(fio_task_queue_s *queue) { - fio_defer_task_s task = fio_defer_pop_task(queue); - if (!task.func) - return -1; - task.func(task.arg1, task.arg2); - return 0; -} - -static inline void fio_defer_clear_tasks(void) { - fio_defer_clear_tasks_for_queue(&task_queue_normal); -#if FIO_USE_URGENT_QUEUE - fio_defer_clear_tasks_for_queue(&task_queue_urgent); -#endif -} - -static void fio_defer_on_fork(void) { - task_queue_normal.lock = FIO_LOCK_INIT; -#if FIO_USE_URGENT_QUEUE - task_queue_urgent.lock = FIO_LOCK_INIT; -#endif -} - -/* ***************************************************************************** -External Task API -***************************************************************************** */ - -/** Defer an execution of a function for later. */ -int fio_defer(void (*func)(void *, void *), void *arg1, void *arg2) { - /* must have a task to defer */ - if (!func) - goto call_error; - fio_defer_push_task(func, arg1, arg2); - return 0; - -call_error: - return -1; -} - -/** Performs all deferred functions until the queue had been depleted. */ -void fio_defer_perform(void) { -#if FIO_USE_URGENT_QUEUE - while (fio_defer_perform_single_task_for_queue(&task_queue_urgent) == 0 || - fio_defer_perform_single_task_for_queue(&task_queue_normal) == 0) - ; -#else - while (fio_defer_perform_single_task_for_queue(&task_queue_normal) == 0) - ; -#endif - // for (;;) { - // #if FIO_USE_URGENT_QUEUE - // fio_defer_task_s task = fio_defer_pop_task(&task_queue_urgent); - // if (!task.func) - // task = fio_defer_pop_task(&task_queue_normal); - // #else - // fio_defer_task_s task = fio_defer_pop_task(&task_queue_normal); - // #endif - // if (!task.func) - // return; - // task.func(task.arg1, task.arg2); - // } -} - -/** Returns true if there are deferred functions waiting for execution. */ -int fio_defer_has_queue(void) { -#if FIO_USE_URGENT_QUEUE - return task_queue_urgent.reader != task_queue_urgent.writer || - task_queue_urgent.reader->write != task_queue_urgent.reader->read || - task_queue_normal.reader != task_queue_normal.writer || - task_queue_normal.reader->write != task_queue_normal.reader->read; -#else - return task_queue_normal.reader != task_queue_normal.writer || - task_queue_normal.reader->write != task_queue_normal.reader->read; -#endif -} - -/** Clears the queue. */ -void fio_defer_clear_queue(void) { fio_defer_clear_tasks(); } - -/* Thread pool task */ -static void *fio_defer_cycle(void *ignr) { - fio_defer_on_thread_start(); - for (;;) { - fio_defer_perform(); - if (!fio_is_running()) - break; - fio_defer_thread_wait(); - } - fio_defer_on_thread_end(); - return ignr; -} - -/* thread pool type */ -typedef struct { - size_t thread_count; - void *threads[]; -} fio_defer_thread_pool_s; - -/* joins a thread pool */ -static void fio_defer_thread_pool_join(fio_defer_thread_pool_s *pool) { - for (size_t i = 0; i < pool->thread_count; ++i) { - fio_thread_join(pool->threads[i]); - } - free(pool); -} - -/* creates a thread pool */ -static fio_defer_thread_pool_s *fio_defer_thread_pool_new(size_t count) { - if (!count) - count = 1; - fio_defer_thread_pool_s *pool = - malloc(sizeof(*pool) + (count * sizeof(void *))); - FIO_ASSERT_ALLOC(pool); - pool->thread_count = count; - for (size_t i = 0; i < count; ++i) { - pool->threads[i] = fio_thread_new(fio_defer_cycle, NULL); - if (!pool->threads[i]) { - pool->thread_count = i; - goto error; - } - } - return pool; -error: - FIO_LOG_FATAL("couldn't spawn threads for thread pool, attempting shutdown."); - fio_stop(); - fio_defer_thread_pool_join(pool); - return NULL; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - Timers - - - - - - - - - - -***************************************************************************** */ - -typedef struct { - fio_ls_embd_s node; - struct timespec due; - size_t interval; /*in ms */ - size_t repetitions; - void (*task)(void *); - void *arg; - void (*on_finish)(void *); -} fio_timer_s; - -static fio_ls_embd_s fio_timers = FIO_LS_INIT(fio_timers); - -static fio_lock_i fio_timer_lock = FIO_LOCK_INIT; - -/** Marks the current time as facil.io's cycle time */ -static inline void fio_mark_time(void) { - clock_gettime(CLOCK_REALTIME, &fio_data->last_cycle); -} - -/** Calculates the due time for a task, given it's interval */ -static struct timespec fio_timer_calc_due(size_t interval) { - struct timespec now = fio_last_tick(); - if (interval >= 1000) { - unsigned long long secs = interval / 1000; - now.tv_sec += secs; - interval -= secs * 1000; - } - now.tv_nsec += (interval * 1000000UL); - if (now.tv_nsec >= 1000000000L) { - now.tv_nsec -= 1000000000L; - now.tv_sec += 1; - } - return now; -} - -/** Returns the number of miliseconds until the next event, up to FIO_POLL_TICK - */ -static size_t fio_timer_calc_first_interval(void) { - if (fio_defer_has_queue()) - return 0; - if (fio_ls_embd_is_empty(&fio_timers)) { - return FIO_POLL_TICK; - } - struct timespec now = fio_last_tick(); - struct timespec due = - FIO_LS_EMBD_OBJ(fio_timer_s, node, fio_timers.next)->due; - if (due.tv_sec < now.tv_sec || - (due.tv_sec == now.tv_sec && due.tv_nsec <= now.tv_nsec)) - return 0; - size_t interval = 1000L * (due.tv_sec - now.tv_sec); - if (due.tv_nsec >= now.tv_nsec) { - interval += (due.tv_nsec - now.tv_nsec) / 1000000L; - } else { - interval -= (now.tv_nsec - due.tv_nsec) / 1000000L; - } - if (interval > FIO_POLL_TICK) - interval = FIO_POLL_TICK; - return interval; -} - -/* simple a<=>b if "a" is bigger a negative result is returned, eq == 0. */ -static int fio_timer_compare(struct timespec a, struct timespec b) { - if (a.tv_sec == b.tv_sec) { - if (a.tv_nsec < b.tv_nsec) - return 1; - if (a.tv_nsec > b.tv_nsec) - return -1; - return 0; - } - if (a.tv_sec < b.tv_sec) - return 1; - return -1; -} - -/** Places a timer in an ordered linked list. */ -static void fio_timer_add_order(fio_timer_s *timer) { - timer->due = fio_timer_calc_due(timer->interval); - // fio_ls_embd_s *pos = &fio_timers; - fio_lock(&fio_timer_lock); - FIO_LS_EMBD_FOR(&fio_timers, node) { - fio_timer_s *t2 = FIO_LS_EMBD_OBJ(fio_timer_s, node, node); - if (fio_timer_compare(timer->due, t2->due) >= 0) { - fio_ls_embd_push(node, &timer->node); - goto finish; - } - } - fio_ls_embd_push(&fio_timers, &timer->node); -finish: - fio_unlock(&fio_timer_lock); -} - -/** Performs a timer task and re-adds it to the queue (or cleans it up) */ -static void fio_timer_perform_single(void *timer_, void *ignr) { - fio_timer_s *timer = timer_; - timer->task(timer->arg); - if (!timer->repetitions || fio_atomic_sub(&timer->repetitions, 1)) - goto reschedule; - if (timer->on_finish) - timer->on_finish(timer->arg); - free(timer); - return; - (void)ignr; -reschedule: - fio_timer_add_order(timer); -} - -/** schedules all timers that are due to be performed. */ -static void fio_timer_schedule(void) { - struct timespec now = fio_last_tick(); - fio_lock(&fio_timer_lock); - while (fio_ls_embd_any(&fio_timers) && - fio_timer_compare( - FIO_LS_EMBD_OBJ(fio_timer_s, node, fio_timers.next)->due, now) >= - 0) { - fio_ls_embd_s *tmp = fio_ls_embd_remove(fio_timers.next); - fio_defer(fio_timer_perform_single, FIO_LS_EMBD_OBJ(fio_timer_s, node, tmp), - NULL); - } - fio_unlock(&fio_timer_lock); -} - -static void fio_timer_clear_all(void) { - fio_lock(&fio_timer_lock); - while (fio_ls_embd_any(&fio_timers)) { - fio_timer_s *timer = - FIO_LS_EMBD_OBJ(fio_timer_s, node, fio_ls_embd_pop(&fio_timers)); - if (timer->on_finish) - timer->on_finish(timer->arg); - free(timer); - } - fio_unlock(&fio_timer_lock); -} - -/** - * Creates a timer to run a task at the specified interval. - * - * The task will repeat `repetitions` times. If `repetitions` is set to 0, task - * will repeat forever. - * - * Returns -1 on error. - * - * The `on_finish` handler is always called (even on error). - */ -int fio_run_every(size_t milliseconds, size_t repetitions, void (*task)(void *), - void *arg, void (*on_finish)(void *)) { - if (!task || (milliseconds == 0 && !repetitions)) - return -1; - fio_timer_s *timer = malloc(sizeof(*timer)); - FIO_ASSERT_ALLOC(timer); - fio_mark_time(); - *timer = (fio_timer_s){ - .due = fio_timer_calc_due(milliseconds), - .interval = milliseconds, - .repetitions = repetitions, - .task = task, - .arg = arg, - .on_finish = on_finish, - }; - fio_timer_add_order(timer); - return 0; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - Concurrency Helpers - - - - - - - - - - - - -***************************************************************************** */ - -volatile uint8_t fio_signal_children_flag = 0; -volatile fio_lock_i fio_signal_set_flag = 0; -/* store old signal handlers to propagate signal handling */ -#ifdef __MINGW32__ -void (*fio_old_sig_int)(int); -void (*fio_old_sig_term)(int); -#else -static struct sigaction fio_old_sig_chld; -static struct sigaction fio_old_sig_pipe; -static struct sigaction fio_old_sig_term; -static struct sigaction fio_old_sig_int; -#if !FIO_DISABLE_HOT_RESTART -static struct sigaction fio_old_sig_usr1; -#endif -#endif - -#ifndef __MINGW32__ -/* there are no process children on Windows, as there is only one worker */ - -/* - * Zombie Reaping - * With thanks to Dr Graham D Shaw. - * http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html - */ -static void reap_child_handler(int sig) { - (void)(sig); - int old_errno = errno; - while (waitpid(-1, NULL, WNOHANG) > 0) - ; - errno = old_errno; - if (fio_old_sig_chld.sa_handler != SIG_IGN && - fio_old_sig_chld.sa_handler != SIG_DFL) - fio_old_sig_chld.sa_handler(sig); -} - -/* initializes zombie reaping for the process */ -void fio_reap_children(void) { - struct sigaction sa; - if (fio_old_sig_chld.sa_handler) - return; - sa.sa_handler = reap_child_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; - if (sigaction(SIGCHLD, &sa, &fio_old_sig_chld) == -1) { - perror("Child reaping initialization failed"); - kill(0, SIGINT); - exit(errno); - } -} -#endif - -/* handles the SIGUSR1, SIGINT and SIGTERM signals. */ -static void sig_int_handler(int sig) { -#ifdef __MINGW32__ - void (*old)(int) = NULL; -#else - struct sigaction *old = NULL; -#endif - switch (sig) { -#if !FIO_DISABLE_HOT_RESTART - case SIGUSR1: - fio_signal_children_flag = 1; - old = &fio_old_sig_usr1; - break; -#endif - /* fallthrough */ - case SIGINT: - if (!old) -#ifdef __MINGW32__ - old = fio_old_sig_int; -#else - old = &fio_old_sig_int; -#endif - /* fallthrough */ - case SIGTERM: - if (!old) -#ifdef __MINGW32__ - old = fio_old_sig_term; -#else - old = &fio_old_sig_term; -#endif - fio_stop(); - break; -#ifndef __MINGW32__ - case SIGPIPE: - if (!old) - old = &fio_old_sig_pipe; -#endif - /* fallthrough */ - default: - break; - } -#ifdef __MINGW32__ - if (old) - fio_old_sig_int(sig); -#else - /* propagate signale handling to previous existing handler (if any) */ - if (old && old->sa_handler != SIG_IGN && old->sa_handler != SIG_DFL) - old->sa_handler(sig); -#endif -} - -/* setup handling for the SIGUSR1, SIGPIPE, SIGINT and SIGTERM signals. */ -static void fio_signal_handler_setup(void) { - /* setup signal handling */ -#ifdef __MINGW32__ - fio_old_sig_int = signal(SIGINT, sig_int_handler); - fio_old_sig_term = signal(SIGTERM, sig_int_handler); -#else - struct sigaction act; - if (fio_trylock(&fio_signal_set_flag)) - return; - - memset(&act, 0, sizeof(act)); - - act.sa_handler = sig_int_handler; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART | SA_NOCLDSTOP; - - if (sigaction(SIGINT, &act, &fio_old_sig_int)) { - perror("couldn't set signal handler"); - return; - }; - - if (sigaction(SIGTERM, &act, &fio_old_sig_term)) { - perror("couldn't set signal handler"); - return; - }; -#if !FIO_DISABLE_HOT_RESTART - if (sigaction(SIGUSR1, &act, &fio_old_sig_usr1)) { - perror("couldn't set signal handler"); - return; - }; -#endif - - act.sa_handler = SIG_IGN; - if (sigaction(SIGPIPE, &act, &fio_old_sig_pipe)) { - perror("couldn't set signal handler"); - return; - }; -#endif -} - -void fio_signal_handler_reset(void) { -#ifdef __MINGW32__ - signal(SIGINT, fio_old_sig_int); - signal(SIGTERM, fio_old_sig_term); -#else - struct sigaction old; - if (fio_signal_set_flag) - return; - fio_unlock(&fio_signal_set_flag); - memset(&old, 0, sizeof(old)); - sigaction(SIGINT, &fio_old_sig_int, &old); - sigaction(SIGTERM, &fio_old_sig_term, &old); - - sigaction(SIGPIPE, &fio_old_sig_pipe, &old); - if (fio_old_sig_chld.sa_handler) - sigaction(SIGCHLD, &fio_old_sig_chld, &old); -#if !FIO_DISABLE_HOT_RESTART - sigaction(SIGUSR1, &fio_old_sig_usr1, &old); - memset(&fio_old_sig_usr1, 0, sizeof(fio_old_sig_usr1)); -#endif -#endif - memset(&fio_old_sig_int, 0, sizeof(fio_old_sig_int)); - memset(&fio_old_sig_term, 0, sizeof(fio_old_sig_term)); -#ifndef __MINGW32__ - memset(&fio_old_sig_pipe, 0, sizeof(fio_old_sig_pipe)); - memset(&fio_old_sig_chld, 0, sizeof(fio_old_sig_chld)); -#endif -} - -/** - * Returns 1 if the current process is a worker process or a single process. - * - * Otherwise returns 0. - * - * NOTE: When cluster mode is off, the root process is also the worker process. - * This means that single process instances don't automatically respawn - * after critical errors. - */ -int fio_is_worker(void) { return fio_data->is_worker; } - -/** - * Returns 1 if the current process is the master (root) process. - * - * Otherwise returns 0. - */ -int fio_is_master(void) { - return fio_data->is_worker == 0 || fio_data->workers == 1; -} - -/** returns facil.io's parent (root) process pid. */ -pid_t fio_parent_pid(void) { return fio_data->parent; } - -static inline size_t fio_detect_cpu_cores(void) { - ssize_t cpu_count = 0; -#if defined(__MINGW32__) - SYSTEM_INFO sys_info; - GetSystemInfo(&sys_info); - cpu_count = sys_info.dwNumberOfProcessors; -#elif defined(_SC_NPROCESSORS_ONLN) - cpu_count = sysconf(_SC_NPROCESSORS_ONLN); - if (cpu_count < 0) { - FIO_LOG_WARNING("CPU core count auto-detection failed."); - return 0; - } -#else - FIO_LOG_WARNING("CPU core count auto-detection failed."); -#endif - return cpu_count; -} - -/** - * Returns the number of expected threads / processes to be used by facil.io. - * - * The pointers should start with valid values that match the expected threads / - * processes values passed to `fio_run`. - * - * The data in the pointers will be overwritten with the result. - */ -void fio_expected_concurrency(int16_t *threads, int16_t *processes) { - if (!threads || !processes) - return; - if (!*threads && !*processes) { - /* both options set to 0 - default to cores*cores matrix */ - ssize_t cpu_count = fio_detect_cpu_cores(); -#if FIO_CPU_CORES_LIMIT - if (cpu_count > FIO_CPU_CORES_LIMIT) { - static int print_cores_warning = 1; - if (print_cores_warning) { - FIO_LOG_WARNING( - "Detected %zu cores. Capping auto-detection of cores to %zu.\n" - " Avoid this message by setting threads / workers manually.\n" - " To increase auto-detection limit, recompile with:\n" - " -DFIO_CPU_CORES_LIMIT=%zu", - (size_t)cpu_count, (size_t)FIO_CPU_CORES_LIMIT, (size_t)cpu_count); - print_cores_warning = 0; - } - cpu_count = FIO_CPU_CORES_LIMIT; - } -#endif -#ifdef __MINGW32__ - *threads = (int16_t)cpu_count; - *processes = 1; -#else - *threads = *processes = (int16_t)cpu_count; - if (cpu_count > 3) { - /* leave a core available for the kernel */ - --(*processes); - } -#endif - } else if (*threads < 0 || *processes < 0) { - /* Set any option that is less than 0 be equal to cores/value */ - /* Set any option equal to 0 be equal to the other option in value */ - ssize_t cpu_count = fio_detect_cpu_cores(); - size_t thread_cpu_adjust = (*threads <= 0 ? 1 : 0); - size_t worker_cpu_adjust = (*processes <= 0 ? 1 : 0); - - if (cpu_count > 0) { - int16_t tmp = 0; - if (*threads < 0) - tmp = (int16_t)(cpu_count / (*threads * -1)); - else if (*threads == 0) { - tmp = -1 * *processes; - thread_cpu_adjust = 0; - } else - tmp = *threads; - if (*processes < 0) - *processes = (int16_t)(cpu_count / (*processes * -1)); - else if (*processes == 0) { - *processes = -1 * *threads; - worker_cpu_adjust = 0; - } - *threads = tmp; - tmp = *processes; - if (worker_cpu_adjust && (*processes * *threads) >= cpu_count && - cpu_count > 3) { - /* leave a resources available for the kernel */ - --*processes; - } - if (thread_cpu_adjust && (*threads * tmp) >= cpu_count && cpu_count > 3) { - /* leave a resources available for the kernel */ - --*threads; - } - } - } - - /* make sure we have at least one (or exactly one on Windows) process and at least one thread */ -#ifdef __MINGW32__ - FIO_LOG_WARNING("Using only 1 worker on Windows, because fork() support is missing."); - *processes = 1; -#else - if (*processes <= 0) - *processes = 1; -#endif - if (*threads <= 0) - *threads = 1; -} - -static fio_lock_i fio_fork_lock = FIO_LOCK_INIT; - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - Polling State Machine - epoll - - - - - - - - - - - - - - -***************************************************************************** */ -#if FIO_ENGINE_EPOLL -#include - -/** - * Returns a C string detailing the IO engine selected during compilation. - * - * Valid values are "kqueue", "epoll" and "poll". - */ -char const *fio_engine(void) { return "epoll"; } - -/* epoll tester, in and out */ -static int evio_fd[3] = {-1, -1, -1}; - -static void fio_poll_close(void) { - for (int i = 0; i < 3; ++i) { - if (evio_fd[i] != -1) { - close(evio_fd[i]); - evio_fd[i] = -1; - } - } -} - -static void fio_poll_init(void) { - fio_poll_close(); - for (int i = 0; i < 3; ++i) { - evio_fd[i] = epoll_create1(EPOLL_CLOEXEC); - if (evio_fd[i] == -1) - goto error; - } - for (int i = 1; i < 3; ++i) { - struct epoll_event chevent = { - .events = (EPOLLOUT | EPOLLIN), - .data.fd = evio_fd[i], - }; - if (epoll_ctl(evio_fd[0], EPOLL_CTL_ADD, evio_fd[i], &chevent) == -1) - goto error; - } - return; -error: - FIO_LOG_FATAL("couldn't initialize epoll."); - fio_poll_close(); - exit(errno); - return; -} - -static inline int fio_poll_add2(int fd, uint32_t events, int ep_fd) { - struct epoll_event chevent; - int ret; - do { - errno = 0; - chevent = (struct epoll_event){ - .events = events, - .data.fd = fd, - }; - ret = epoll_ctl(ep_fd, EPOLL_CTL_MOD, fd, &chevent); - if (ret == -1 && errno == ENOENT) { - errno = 0; - chevent = (struct epoll_event){ - .events = events, - .data.fd = fd, - }; - ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, fd, &chevent); - } - } while (errno == EINTR); - - return ret; -} - -static inline void fio_poll_add_read(intptr_t fd) { - fio_poll_add2(fd, (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), - evio_fd[1]); - return; -} - -static inline void fio_poll_add_write(intptr_t fd) { - fio_poll_add2(fd, (EPOLLOUT | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), - evio_fd[2]); - return; -} - -static inline void fio_poll_add(intptr_t fd) { - if (fio_poll_add2(fd, (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), - evio_fd[1]) == -1) - return; - fio_poll_add2(fd, (EPOLLOUT | EPOLLRDHUP | EPOLLHUP | EPOLLONESHOT), - evio_fd[2]); - return; -} - -FIO_FUNC inline void fio_poll_remove_fd(intptr_t fd) { - struct epoll_event chevent = {.events = (EPOLLOUT | EPOLLIN), .data.fd = fd}; - epoll_ctl(evio_fd[1], EPOLL_CTL_DEL, fd, &chevent); - epoll_ctl(evio_fd[2], EPOLL_CTL_DEL, fd, &chevent); -} - -static size_t fio_poll(void) { - int timeout_millisec = fio_timer_calc_first_interval(); - struct epoll_event internal[2]; - struct epoll_event events[FIO_POLL_MAX_EVENTS]; - int total = 0; - /* wait for events and handle them */ - int internal_count = epoll_wait(evio_fd[0], internal, 2, timeout_millisec); - if (internal_count == 0) - return internal_count; - for (int j = 0; j < internal_count; ++j) { - int active_count = - epoll_wait(internal[j].data.fd, events, FIO_POLL_MAX_EVENTS, 0); - if (active_count > 0) { - for (int i = 0; i < active_count; i++) { - if (events[i].events & (~(EPOLLIN | EPOLLOUT))) { - // errors are hendled as disconnections (on_close) - fio_force_close_in_poll(fd2uuid(events[i].data.fd)); - } else { - // no error, then it's an active event(s) - if (events[i].events & EPOLLOUT) { - fio_defer_push_urgent(deferred_on_ready, - (void *)fd2uuid(events[i].data.fd), NULL); - } - if (events[i].events & EPOLLIN) - fio_defer_push_task(deferred_on_data, - (void *)fd2uuid(events[i].data.fd), NULL); - } - } // end for loop - total += active_count; - } - } - return total; -} - -#endif -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - Polling State Machine - kqueue - - - - - - - - - - - - - - -***************************************************************************** */ -#if FIO_ENGINE_KQUEUE -#include - -/** - * Returns a C string detailing the IO engine selected during compilation. - * - * Valid values are "kqueue", "epoll" and "poll". - */ -char const *fio_engine(void) { return "kqueue"; } - -static int evio_fd = -1; - -static void fio_poll_close(void) { close(evio_fd); } - -static void fio_poll_init(void) { - fio_poll_close(); - evio_fd = kqueue(); - if (evio_fd == -1) { - FIO_LOG_FATAL("couldn't open kqueue.\n"); - exit(errno); - } -} - -static inline void fio_poll_add_read(intptr_t fd) { - struct kevent chevent[1]; - EV_SET(chevent, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, - 0, 0, ((void *)fd)); - do { - errno = 0; - kevent(evio_fd, chevent, 1, NULL, 0, NULL); - } while (errno == EINTR); - return; -} - -static inline void fio_poll_add_write(intptr_t fd) { - struct kevent chevent[1]; - EV_SET(chevent, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, - 0, 0, ((void *)fd)); - do { - errno = 0; - kevent(evio_fd, chevent, 1, NULL, 0, NULL); - } while (errno == EINTR); - return; -} - -static inline void fio_poll_add(intptr_t fd) { - struct kevent chevent[2]; - EV_SET(chevent, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, - 0, 0, ((void *)fd)); - EV_SET(chevent + 1, fd, EVFILT_WRITE, - EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT, 0, 0, ((void *)fd)); - do { - errno = 0; - kevent(evio_fd, chevent, 2, NULL, 0, NULL); - } while (errno == EINTR); - return; -} - -FIO_FUNC inline void fio_poll_remove_fd(intptr_t fd) { - if (evio_fd < 0) - return; - struct kevent chevent[2]; - EV_SET(chevent, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); - EV_SET(chevent + 1, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); - do { - errno = 0; - kevent(evio_fd, chevent, 2, NULL, 0, NULL); - } while (errno == EINTR); -} - -static size_t fio_poll(void) { - if (evio_fd < 0) - return -1; - int timeout_millisec = fio_timer_calc_first_interval(); - struct kevent events[FIO_POLL_MAX_EVENTS] = {{0}}; - - const struct timespec timeout = { - .tv_sec = (timeout_millisec / 1000), - .tv_nsec = ((timeout_millisec & (~1023UL)) * 1000000)}; - /* wait for events and handle them */ - int active_count = - kevent(evio_fd, NULL, 0, events, FIO_POLL_MAX_EVENTS, &timeout); - - if (active_count > 0) { - for (int i = 0; i < active_count; i++) { - // test for event(s) type - if (events[i].filter == EVFILT_WRITE) { - fio_defer_push_urgent(deferred_on_ready, - ((void *)fd2uuid(events[i].udata)), NULL); - } else if (events[i].filter == EVFILT_READ) { - fio_defer_push_task(deferred_on_data, (void *)fd2uuid(events[i].udata), - NULL); - } - if (events[i].flags & (EV_EOF | EV_ERROR)) { - fio_force_close_in_poll(fd2uuid(events[i].udata)); - } - } - } else if (active_count < 0) { - if (errno == EINTR) - return 0; - return -1; - } - return active_count; -} - -#endif -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - Polling State Machine - poll - - - - - - - - - - - - - - -***************************************************************************** */ - -#if FIO_ENGINE_POLL - -/** - * Returns a C string detailing the IO engine selected during compilation. - * - * Valid values are "kqueue", "epoll" and "poll". - */ -char const *fio_engine(void) { return "poll"; } - -#define FIO_POLL_READ_EVENTS (POLLPRI | POLLIN) -#define FIO_POLL_WRITE_EVENTS (POLLOUT) - -static void fio_poll_close(void) {} - -static void fio_poll_init(void) {} - -static inline void fio_poll_remove_fd(int fd) { - fio_data->poll[fd].fd = -1; - fio_data->poll[fd].events = 0; -} - -static inline void fio_poll_add_read(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events |= FIO_POLL_READ_EVENTS; -} - -static inline void fio_poll_add_write(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events |= FIO_POLL_WRITE_EVENTS; -} - -static inline void fio_poll_add(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events = FIO_POLL_READ_EVENTS | FIO_POLL_WRITE_EVENTS; -} - -static inline void fio_poll_remove_read(int fd) { - fio_lock(&fio_data->lock); - if (fio_data->poll[fd].events & FIO_POLL_WRITE_EVENTS) - fio_data->poll[fd].events = FIO_POLL_WRITE_EVENTS; - else { - fio_poll_remove_fd(fd); - } - fio_unlock(&fio_data->lock); -} - -static inline void fio_poll_remove_write(int fd) { - fio_lock(&fio_data->lock); - if (fio_data->poll[fd].events & FIO_POLL_READ_EVENTS) - fio_data->poll[fd].events = FIO_POLL_READ_EVENTS; - else { - fio_poll_remove_fd(fd); - } - fio_unlock(&fio_data->lock); -} - -/** returns non-zero if events were scheduled, 0 if idle */ -static size_t fio_poll(void) { - /* shrink fd poll range */ - size_t end = fio_data->capa; // max_protocol_fd might break TLS - size_t start = 0; - struct pollfd *list = NULL; - fio_lock(&fio_data->lock); - while (start < end && fio_data->poll[start].fd == -1) - ++start; - while (start < end && fio_data->poll[end - 1].fd == -1) - --end; - if (start != end) { - /* copy poll list for multi-threaded poll */ - list = fio_malloc(sizeof(struct pollfd) * end); - memcpy(list + start, fio_data->poll + start, - (sizeof(struct pollfd)) * (end - start)); - } - fio_unlock(&fio_data->lock); - - int timeout = fio_timer_calc_first_interval(); - size_t count = 0; - - if (start == end) { - fio_throttle_thread((timeout * 1000000UL)); - } else if (poll(list + start, end - start, timeout) == -1) { - goto finish; - } - for (size_t i = start; i < end; ++i) { - if (list[i].revents) { - touchfd(i); - ++count; - if (list[i].revents & FIO_POLL_WRITE_EVENTS) { - // FIO_LOG_DEBUG("Poll Write %zu => %p", i, (void *)fd2uuid(i)); - fio_poll_remove_write(i); - fio_defer_push_urgent(deferred_on_ready, (void *)fd2uuid(i), NULL); - } - if (list[i].revents & FIO_POLL_READ_EVENTS) { - // FIO_LOG_DEBUG("Poll Read %zu => %p", i, (void *)fd2uuid(i)); - fio_poll_remove_read(i); - fio_defer_push_task(deferred_on_data, (void *)fd2uuid(i), NULL); - } - if (list[i].revents & (POLLHUP | POLLERR)) { - // FIO_LOG_DEBUG("Poll Hangup %zu => %p", i, (void *)fd2uuid(i)); - fio_poll_remove_fd(i); - fio_force_close_in_poll(fd2uuid(i)); - } - if (list[i].revents & POLLNVAL) { - // FIO_LOG_DEBUG("Poll Invalid %zu => %p", i, (void *)fd2uuid(i)); - fio_poll_remove_fd(i); - fio_lock(&fd_data(i).protocol_lock); - fio_clear_fd(i, 0); - fio_unlock(&fd_data(i).protocol_lock); - } - } - } -finish: - fio_free(list); - return count; -} - -#endif /* FIO_ENGINE_POLL */ - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - Polling State Machine - wsapoll - - - - - - - - - - - - - - -***************************************************************************** */ - -#if FIO_ENGINE_WSAPOLL - -void fio_clear_handle(int fd); -int fio_fd4handle(SOCKET handle); - -/** - * Returns a C string detailing the IO engine selected during compilation. - * - * Valid values are "kqueue", "epoll", "poll" and "wsapoll". - */ -char const *fio_engine(void) { return "wsapoll"; } - -#define FIO_POLL_READ_EVENTS (POLLIN) -#define FIO_POLL_WRITE_EVENTS (POLLOUT) - -static void fio_poll_close(void) { - WSACleanup(); -} - -static void fio_poll_init(void) { - int result; - WSADATA wsa_data; - result = WSAStartup(MAKEWORD(2,2), &wsa_data); - if (result != 0) { - FIO_LOG_FATAL("WSA startup failed.\n"); - exit(result); - } -} - -static inline void fio_poll_remove_fd(int fd) { - fio_data->poll[fd].fd = -1; - fio_data->poll[fd].events = 0; -} - -static inline void fio_poll_add_read(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events |= FIO_POLL_READ_EVENTS; -} - -static inline void fio_poll_add_write(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events |= FIO_POLL_WRITE_EVENTS; -} - -static inline void fio_poll_add(int fd) { - fio_data->poll[fd].fd = fd; - fio_data->poll[fd].events = FIO_POLL_READ_EVENTS | FIO_POLL_WRITE_EVENTS; -} - -static inline void fio_poll_remove_read(int fd) { - fio_lock(&fio_data->lock); - if (fio_data->poll[fd].events & FIO_POLL_WRITE_EVENTS) - fio_data->poll[fd].events = FIO_POLL_WRITE_EVENTS; - else { - fio_poll_remove_fd(fd); - } - fio_unlock(&fio_data->lock); -} - -static inline void fio_poll_remove_write(int fd) { - fio_lock(&fio_data->lock); - if (fio_data->poll[fd].events & FIO_POLL_READ_EVENTS) - fio_data->poll[fd].events = FIO_POLL_READ_EVENTS; - else { - fio_poll_remove_fd(fd); - } - fio_unlock(&fio_data->lock); -} - -/** returns non-zero if events were scheduled, 0 if idle */ -static size_t fio_poll(void) { - /* shrink fd poll range */ - size_t end = fio_data->capa; // max_protocol_fd might break TLS - size_t start = 0; - struct pollfd *list = NULL; - fio_lock(&fio_data->lock); - while (start < end && fio_data->poll[start].fd == (SOCKET)-1) - ++start; - while (start < end && fio_data->poll[end-1].fd == (SOCKET)-1) - --end; - fio_unlock(&fio_data->lock); - - /* copy poll list for multi-threaded poll */ - list = fio_malloc(sizeof(*list) * (end - start + 1)); - FIO_ASSERT_ALLOC(list); - - // replace facil fds with actual Windows socket handles in list - size_t i = 0; - size_t j = 0; - - for(i = start; i <= end; i++) { - if (fd_data(i).socket_handle != INVALID_SOCKET && fd_data(i).socket_handle > 0) { - list[j].fd = fd_data(i).socket_handle; - list[j].events = fio_data->poll[i].events; - list[j].revents = fio_data->poll[i].revents; - j++; - } - } - - if (WSAPoll(list, j, 1) == SOCKET_ERROR) { - int error = WSAGetLastError(); - FIO_LOG_DEBUG("fio_poll WSAPoll error %i", error); - goto finish; - } - - size_t count = 0; - int fd; - - for (i = 0; i < j; i++) { - if (list[i].fd != INVALID_SOCKET && list[i].revents) { - fd = fio_fd4handle(list[i].fd); - if (fd == -1) - continue; - touchfd(fd); - ++count; - - if (list[i].revents & FIO_POLL_WRITE_EVENTS) { - // FIO_LOG_DEBUG("Poll Write %zu => %p", fd, (void *)fd2uuid(fd)); - fio_poll_remove_write(fd); - fio_defer_push_urgent(deferred_on_ready, (void *)fd2uuid(fd), NULL); - } - if (list[i].revents & FIO_POLL_READ_EVENTS) { - // FIO_LOG_DEBUG("Poll Read %zu => %p", fd, (void *)fd2uuid(fd)); - fio_poll_remove_read(fd); - fio_defer_push_task(deferred_on_data, (void *)fd2uuid(fd), NULL); - continue; - } - if (list[i].revents & (POLLHUP | POLLERR)) { - // FIO_LOG_DEBUG("Poll Hangup %zu => %p", fd, (void *)fd2uuid(fd)); - fio_poll_remove_fd(fd); - fio_force_close_in_poll(fd2uuid(fd)); - continue; - } - if (list[i].revents & POLLNVAL) { - // FIO_LOG_DEBUG("Poll Invalid %zu => %p", fd, (void *)fd2uuid(fd)); - fio_poll_remove_fd(fd); - fio_lock(&fd_data(fd).protocol_lock); - fio_clear_handle(fd); - fio_clear_fd(fd, 0); - fio_unlock(&fd_data(fd).protocol_lock); - } - } - } -finish: - fio_free(list); - return count; -} - -#endif /* FIO_ENGINE_WSAPOLL */ - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - IO Callbacks / Event Handling - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Mock Protocol Callbacks and Service Funcions -***************************************************************************** */ -static void mock_on_ev(intptr_t uuid, fio_protocol_s *protocol) { - (void)uuid; - (void)protocol; -} - -static void mock_on_data(intptr_t uuid, fio_protocol_s *protocol) { - fio_suspend(uuid); - (void)protocol; -} - -static uint8_t mock_on_shutdown(intptr_t uuid, fio_protocol_s *protocol) { - return 0; - (void)protocol; - (void)uuid; -} - -static uint8_t mock_on_shutdown_eternal(intptr_t uuid, - fio_protocol_s *protocol) { - return 255; - (void)protocol; - (void)uuid; -} - -static void mock_ping(intptr_t uuid, fio_protocol_s *protocol) { - (void)protocol; - fio_force_close(uuid); -} -static void mock_ping2(intptr_t uuid, fio_protocol_s *protocol) { - (void)protocol; - touchfd(fio_uuid2fd(uuid)); - if (uuid_data(uuid).timeout == 255) - return; - protocol->ping = mock_ping; - uuid_data(uuid).timeout = 8; - fio_close(uuid); -} - -FIO_FUNC void mock_ping_eternal(intptr_t uuid, fio_protocol_s *protocol) { - (void)protocol; - fio_touch(uuid); -} - -/* ***************************************************************************** -Deferred event handlers - these tasks safely forward the events to the Protocol -***************************************************************************** */ - -static void deferred_on_close(void *uuid_, void *pr_) { - fio_protocol_s *pr = pr_; - if (pr->rsv) - goto postpone; - pr->on_close((intptr_t)uuid_, pr); - return; -postpone: - fio_defer_push_task(deferred_on_close, uuid_, pr_); -} - -static void deferred_on_shutdown(void *arg, void *arg2) { - if (!uuid_data(arg).protocol) { - return; - } - fio_protocol_s *pr = protocol_try_lock(fio_uuid2fd(arg), FIO_PR_LOCK_TASK); - if (!pr) { - if (errno == EBADF) - return; - goto postpone; - } - touchfd(fio_uuid2fd(arg)); - uint8_t r = pr->on_shutdown ? pr->on_shutdown((intptr_t)arg, pr) : 0; - if (r) { - if (r == 255) { - uuid_data(arg).timeout = 0; - } else { - fio_atomic_add(&fio_data->connection_count, 1); - uuid_data(arg).timeout = r; - } - pr->ping = mock_ping2; - protocol_unlock(pr, FIO_PR_LOCK_TASK); - } else { - fio_atomic_add(&fio_data->connection_count, 1); - uuid_data(arg).timeout = 8; - pr->ping = mock_ping; - protocol_unlock(pr, FIO_PR_LOCK_TASK); - fio_close((intptr_t)arg); - } - return; -postpone: - fio_defer_push_task(deferred_on_shutdown, arg, NULL); - (void)arg2; -} - -static void deferred_on_ready_usr(void *arg, void *arg2) { - errno = 0; - fio_protocol_s *pr = protocol_try_lock(fio_uuid2fd(arg), FIO_PR_LOCK_WRITE); - if (!pr) { - if (errno == EBADF) - return; - goto postpone; - } - pr->on_ready((intptr_t)arg, pr); - protocol_unlock(pr, FIO_PR_LOCK_WRITE); - return; -postpone: - fio_defer_push_task(deferred_on_ready, arg, NULL); - (void)arg2; -} - -static void deferred_on_ready(void *arg, void *arg2) { - errno = 0; - if (fio_flush((intptr_t)arg) > 0 || errno == EWOULDBLOCK || errno == EAGAIN) { - if (arg2) - fio_defer_push_urgent(deferred_on_ready, arg, NULL); - else - fio_poll_add_write(fio_uuid2fd(arg)); - return; - } - if (!uuid_data(arg).protocol) { - return; - } - - fio_defer_push_task(deferred_on_ready_usr, arg, NULL); -} - -static void deferred_on_data(void *uuid, void *arg2) { - if (fio_is_closed((intptr_t)uuid)) { - return; - } - if (!uuid_data(uuid).protocol) - goto no_protocol; - fio_protocol_s *pr = protocol_try_lock(fio_uuid2fd(uuid), FIO_PR_LOCK_TASK); - if (!pr) { - if (errno == EBADF) { - return; - } - goto postpone; - } - fio_unlock(&uuid_data(uuid).scheduled); - pr->on_data((intptr_t)uuid, pr); - protocol_unlock(pr, FIO_PR_LOCK_TASK); - if (!fio_trylock(&uuid_data(uuid).scheduled)) { - fio_poll_add_read(fio_uuid2fd((intptr_t)uuid)); - } - return; - -postpone: - if (arg2) { - /* the event is being forced, so force rescheduling */ - fio_defer_push_task(deferred_on_data, (void *)uuid, (void *)1); - } else { - /* the protocol was locked, so there might not be any need for the event */ - fio_poll_add_read(fio_uuid2fd((intptr_t)uuid)); - } - return; - -no_protocol: - /* a missing protocol might still want to invoke the RW hook flush */ - deferred_on_ready(uuid, arg2); - return; -} - -static void deferred_ping(void *arg, void *arg2) { - if (!uuid_data(arg).protocol || - (uuid_data(arg).timeout && - (uuid_data(arg).timeout + uuid_data(arg).active > - (fio_data->last_cycle.tv_sec)))) { - return; - } - fio_protocol_s *pr = protocol_try_lock(fio_uuid2fd(arg), FIO_PR_LOCK_WRITE); - if (!pr) - goto postpone; - pr->ping((intptr_t)arg, pr); - protocol_unlock(pr, FIO_PR_LOCK_WRITE); - return; -postpone: - fio_defer_push_task(deferred_ping, arg, NULL); - (void)arg2; -} - -/* ***************************************************************************** -Forcing / Suspending IO events -***************************************************************************** */ - -void fio_force_event(intptr_t uuid, enum fio_io_event ev) { - if (!uuid_is_valid(uuid)) - return; - switch (ev) { - case FIO_EVENT_ON_DATA: - fio_trylock(&uuid_data(uuid).scheduled); - fio_defer_push_task(deferred_on_data, (void *)uuid, (void *)1); - break; - case FIO_EVENT_ON_TIMEOUT: - fio_defer_push_task(deferred_ping, (void *)uuid, NULL); - break; - case FIO_EVENT_ON_READY: - fio_defer_push_urgent(deferred_on_ready, (void *)uuid, NULL); - break; - } -} - -void fio_suspend(intptr_t uuid) { - if (uuid_is_valid(uuid)) - fio_trylock(&uuid_data(uuid).scheduled); -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - IO Socket Layer - - Read / Write / Accept / Connect / etc' - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Internal socket initialization functions -***************************************************************************** */ - -/** -Sets a socket to non blocking state. - -This function is called automatically for the new socket, when using -`fio_accept` or `fio_connect`. -*/ -int fio_set_non_block(int fd) { -/* If they have O_NONBLOCK, use the Posix way to do it */ -#if defined(O_NONBLOCK) - /* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */ - int flags; - if (-1 == (flags = fcntl(fd, F_GETFL, 0))) - flags = 0; -#ifdef O_CLOEXEC - return fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC); -#else - return fcntl(fd, F_SETFL, flags | O_NONBLOCK); -#endif -#elif defined(FIONBIO) - /* Otherwise, use the old way of doing it */ - static int flags = 1; - return ioctl(fd, FIONBIO, &flags); -#else -#error No functions / argumnet macros for non-blocking sockets. -#endif -} - -static void fio_tcp_addr_cpy(int fd, int family, struct sockaddr *addrinfo) { - const char *result = - inet_ntop(family, - family == AF_INET - ? (void *)&(((struct sockaddr_in *)addrinfo)->sin_addr) - : (void *)&(((struct sockaddr_in6 *)addrinfo)->sin6_addr), - (char *)fd_data(fd).addr, sizeof(fd_data(fd).addr)); - if (result) { - fd_data(fd).addr_len = strlen((char *)fd_data(fd).addr); - } else { - fd_data(fd).addr_len = 0; - fd_data(fd).addr[0] = 0; - } -} - -#ifdef __MINGW32__ -int fio_handle2fd(SOCKET handle) { - int found = 0; - int fd = -1; - int it = 0; - while(!found) { - fd++; - if (fd >= (int)fio_data->capa) { - fd = 0; - it++; - if (it > 2) - return -1; - fio_reschedule_thread(); - } - if (fd_data(fd).socket_handle != INVALID_SOCKET && fd_data(fd).socket_handle > 0) - continue; - fio_lock(&(fd_data(fd).sock_lock)); - if (fd_data(fd).socket_handle != INVALID_SOCKET && fd_data(fd).socket_handle > 0) { - fio_unlock(&(fd_data(fd).sock_lock)); - continue; - } else { - fd_data(fd).socket_handle = handle; - found = 1; - fio_unlock(&(fd_data(fd).sock_lock)); - } - } - return fd; -} - -int fio_fd4handle(SOCKET handle) { - unsigned int fd = 0; - while(fd < fio_data->capa) { - if (fd_data(fd).socket_handle == handle) { - return fd; - } - fd++; - } - return -1; -} - -int fio_osffd4fd(unsigned int fd) { - if (fd_data(fd).osffd != -1) - return fd_data(fd).osffd; - int osffd = _open_osfhandle(fd_data(fd).socket_handle, _O_RDWR); - fd_data(fd).osffd = osffd; - return osffd; -} - -void fio_clear_handle(int fd) { - fio_lock(&(fd_data(fd).sock_lock)); - fd_data(fd).socket_handle = INVALID_SOCKET; - fd_data(fd).osffd = -1; - fio_unlock(&(fd_data(fd).sock_lock)); -} -#endif - -/** - * `fio_accept` accepts a new socket connection from a server socket - see the - * server flag on `fio_socket`. - * - * NOTE: this function does NOT attach the socket to the IO reactor -see - * `fio_attach`. - */ -intptr_t fio_accept(intptr_t srv_uuid) { - struct sockaddr_in6 addrinfo[2]; /* grab a slice of stack (aligned) */ - socklen_t addrlen = sizeof(addrinfo); -#ifdef __MINGW32__ - SOCKET client; -#else - int client; -#endif -#ifdef SOCK_NONBLOCK - client = accept4(fio_uuid2fd(srv_uuid), (struct sockaddr *)addrinfo, &addrlen, - SOCK_NONBLOCK | SOCK_CLOEXEC); - if (client <= 0) - return -1; -#else -#ifdef __MINGW32__ - client = accept_ptr(fd_data(fio_uuid2fd(srv_uuid)).socket_handle, (struct sockaddr *)addrinfo, &addrlen); - if (client == INVALID_SOCKET) - return -1; -#else - client = accept(fio_uuid2fd(srv_uuid), (struct sockaddr *)addrinfo, &addrlen); - if (client <= 0) - return -1; -#endif - if (fio_set_non_block(client) == -1) { -#ifdef __MINGW32__ - closesocket_ptr(client); -#else - close(client); -#endif - return -1; - } -#endif - // avoid the TCP delay algorithm. - { - #ifdef __MINGW32__ - char optval = 1; - setsockopt_ptr(client, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); - #else - int optval = 1; - setsockopt(client, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); -#endif - } - // handle socket buffers. - { - int optval = 0; - socklen_t size = (socklen_t)sizeof(optval); -#ifdef __MINGW32__ - if (!getsockopt_ptr(client, SOL_SOCKET, SO_SNDBUF, (char *)&optval, &size) && - optval <= 131072) { - optval = 131072; - setsockopt_ptr(client, SOL_SOCKET, SO_SNDBUF, (char *)&optval, sizeof(optval)); - optval = 131072; - setsockopt_ptr(client, SOL_SOCKET, SO_RCVBUF, (char *)&optval, sizeof(optval)); - } -#else - if (!getsockopt(client, SOL_SOCKET, SO_SNDBUF, &optval, &size) && - optval <= 131072) { - optval = 131072; - setsockopt(client, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval)); - optval = 131072; - setsockopt(client, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval)); - } -#endif - } -#ifdef __MINGW32__ - client = fio_handle2fd(client); - if (client == (SOCKET)-1) - return -1; -#endif - fio_lock(&fd_data(client).protocol_lock); - fio_clear_fd(client, 1); - fio_unlock(&fd_data(client).protocol_lock); - /* copy peer address */ - if (((struct sockaddr *)addrinfo)->sa_family == AF_UNIX) { - fd_data(client).addr_len = uuid_data(srv_uuid).addr_len; - if (uuid_data(srv_uuid).addr_len) { - memcpy(fd_data(client).addr, uuid_data(srv_uuid).addr, - uuid_data(srv_uuid).addr_len + 1); - } - } else { - fio_tcp_addr_cpy(client, ((struct sockaddr *)addrinfo)->sa_family, - (struct sockaddr *)addrinfo); - } - - return fd2uuid(client); -} - -/* Creates a TCP/IP socket - returning it's uuid (or -1) */ -static intptr_t fio_tcp_socket(const char *address, const char *port, - uint8_t server) { - /* TCP/IP socket */ - // setup the address - struct addrinfo hints = {0}; - struct addrinfo *addrinfo; // will point to the results - memset(&hints, 0, sizeof hints); // make sure the struct is empty - hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 - hints.ai_socktype = SOCK_STREAM; // TCP stream sockets - hints.ai_flags = AI_PASSIVE; // fill in my IP for me - if (getaddrinfo(address, port, &hints, &addrinfo)) { - // perror("addr err"); - return -1; - } - // get the file descriptor -#ifdef __MINGW32__ - SOCKET fd = - socket_ptr(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); - if (fd == INVALID_SOCKET) { - freeaddrinfo(addrinfo); - return -1; - } - // ensure dual-mode socket, enable IPV4 - DWORD v6val = 0; - setsockopt_ptr(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6val, sizeof(v6val)); -#else - int fd = - socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); - if (fd <= 0) { - freeaddrinfo(addrinfo); - return -1; - } -#endif - // make sure the socket is non-blocking - if (fio_set_non_block(fd) < 0) { - freeaddrinfo(addrinfo); -#ifdef __MINGW32__ - closesocket_ptr(fd); -#else - close(fd); // socket -#endif - return -1; - } - if (server) { - { - // avoid the "address taken" -#ifdef __MINGW32__ - char optval = 1; - setsockopt_ptr(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); -#else - int optval = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); -#endif - } - // bind the address to the socket - int bound = 0; -#ifdef __MINGW32__ - for (struct addrinfo *i = addrinfo; i != NULL; i = i->ai_next) { - if (!bind_ptr(fd, i->ai_addr, i->ai_addrlen)) - bound = 1; - } -#else - for (struct addrinfo *i = addrinfo; i != NULL; i = i->ai_next) { - if (!bind(fd, i->ai_addr, i->ai_addrlen)) - bound = 1; - } -#endif - if (!bound) { - // perror("bind err"); - freeaddrinfo(addrinfo); -#ifdef __MINGW32__ - closesocket_ptr(fd); -#else - close(fd); -#endif - return -1; - } -#ifdef TCP_FASTOPEN - { - // support TCP Fast Open when available - int optval = 128; -#ifdef __MINGW32__ - setsockopt_ptr(fd, addrinfo->ai_protocol, TCP_FASTOPEN, &optval, - sizeof(optval)); -#else - setsockopt(fd, addrinfo->ai_protocol, TCP_FASTOPEN, &optval, - sizeof(optval)); -#endif - } -#endif -#ifdef __MINGW32__ - if (listen_ptr(fd, SOMAXCONN) < 0) { - freeaddrinfo(addrinfo); - closesocket_ptr(fd); - return -1; - } -#else - if (listen(fd, SOMAXCONN) < 0) { - freeaddrinfo(addrinfo); - close(fd); - return -1; - } -#endif - } else { -#ifdef __MINGW32__ - char optval = 1; - setsockopt_ptr(fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); -#else - int optval = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); -#endif - errno = 0; - for (struct addrinfo *i = addrinfo; i; i = i->ai_next) { -#ifdef __MINGW32__ - int connres = connect_ptr(fd, i->ai_addr, i->ai_addrlen); - if (connres == SOCKET_ERROR) { - int error = WSAGetLastError(); - if (error == WSAEISCONN || error == WSAEWOULDBLOCK || error == WSAEINPROGRESS) - goto socket_okay; - } else if (connres == 0) { goto socket_okay; } -#else - if (connect(fd, i->ai_addr, i->ai_addrlen) == 0 || errno == EINPROGRESS) - goto socket_okay; -#endif - } - freeaddrinfo(addrinfo); -#ifdef __MINGW32__ - closesocket_ptr(fd); -#else - close(fd); // socket -#endif - return -1; - } -socket_okay: -#ifdef __MINGW32__ - fd = fio_handle2fd(fd); - if (fd == (SOCKET)-1) - return -1; -#endif - fio_lock(&fd_data(fd).protocol_lock); - fio_clear_fd(fd, 1); - fio_unlock(&fd_data(fd).protocol_lock); - fio_tcp_addr_cpy(fd, addrinfo->ai_family, (void *)addrinfo); - freeaddrinfo(addrinfo); - intptr_t ufd = fd2uuid(fd); - return ufd; -} - -#ifdef __MINGW32__ -/* Creates a tcp socket in the 10000 to 19999 port range */ -static intptr_t fio_unix_socket(const char *address, uint8_t server) { - static char *localhost = "localhost"; - char localport[6]; - int vary = _getpid(); - int iterations = sizeof(int) * 8; - int i = iterations; - while (vary > 9999) { - i--; - vary &= ~(1u << i); - } - sprintf_s(localport, 6, "1%04u", vary); - FIO_LOG_WARNING("Using tcp socket on localhost port %s for IPC instead of unix socket on Windows.", localport); - return fio_tcp_socket(localhost, localport, server); -} -#else -/* Creates a Unix socket - returning it's uuid (or -1) */ -static intptr_t fio_unix_socket(const char *address, uint8_t server) { - /* Unix socket */ - struct sockaddr_un addr = {0}; - size_t addr_len = strlen(address); - if (addr_len >= sizeof(addr.sun_path)) { - FIO_LOG_ERROR("(fio_unix_socket) address too long (%zu bytes > %zu bytes).", - addr_len, sizeof(addr.sun_path) - 1); - errno = ENAMETOOLONG; - return -1; - } - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, address, addr_len + 1); /* copy the NUL byte. */ -#if defined(__APPLE__) - addr.sun_len = addr_len; -#endif - // get the file descriptor - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) { - return -1; - } - if (fio_set_non_block(fd) == -1) { - close(fd); - return -1; - } - if (server) { - unlink(addr.sun_path); -#ifndef FIO_SOCK_AVOID_UMASK - int org_umask = umask(0x1FF); - int btmp = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); - umask(org_umask); -#else - int btmp = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); -#endif - if (btmp == -1) { - // perror("couldn't bind unix socket"); - close(fd); - return -1; - } - /* chmod for foreign connections... if possible */ - if(chmod(addr.sun_path, 0x1FF)) - FIO_LOG_WARNING("chmod failed for %s (%d: %s)", address, errno, strerror(errno)); - if (listen(fd, SOMAXCONN) < 0) { - // perror("couldn't start listening to unix socket"); - close(fd); - return -1; - } - } else { - if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1 && - errno != EINPROGRESS) { - close(fd); - return -1; - } - } - fio_lock(&fd_data(fd).protocol_lock); - fio_clear_fd(fd, 1); - fio_unlock(&fd_data(fd).protocol_lock); - if (addr_len < sizeof(fd_data(fd).addr)) { - memcpy(fd_data(fd).addr, address, addr_len + 1); /* copy the NUL byte. */ - fd_data(fd).addr_len = addr_len; - } - return fd2uuid(fd); -} -#endif - -/* PUBLIC API: opens a server or client socket */ -intptr_t fio_socket(const char *address, const char *port, uint8_t server) { - intptr_t uuid; - if (port) { - char *pos = (char *)port; - int64_t n = fio_atol(&pos); - /* make sure port is only numerical */ - if (*pos) { - FIO_LOG_ERROR("(fio_socket) port %s is not a number.", port); - errno = EINVAL; - return -1; - } - /* a negative port number will revert to a Unix socket. */ - if (n <= 0) { - if (n < -1) - FIO_LOG_WARNING("(fio_socket) negative port number %s is ignored.", - port); - port = NULL; - } - } - if (!address && !port) { - FIO_LOG_ERROR("(fio_socket) both address and port are missing or invalid."); - errno = EINVAL; - return -1; - } - if (!port) { - do { - errno = 0; - uuid = fio_unix_socket(address, server); - } while (errno == EINTR); - } else { - do { - errno = 0; - uuid = fio_tcp_socket(address, port, server); - } while (errno == EINTR); - } - return uuid; -} - -/* ***************************************************************************** -Internal socket flushing related functions -***************************************************************************** */ - -#ifndef BUFFER_FILE_READ_SIZE -#define BUFFER_FILE_READ_SIZE 49152 -#endif - -#if !defined(USE_SENDFILE) && !defined(USE_SENDFILE_LINUX) && \ - !defined(USE_SENDFILE_BSD) && !defined(USE_SENDFILE_APPLE) -#if defined(__linux__) /* linux sendfile works */ -#define USE_SENDFILE_LINUX 1 -#elif defined(__FreeBSD__) /* FreeBSD sendfile should work - not tested */ -#define USE_SENDFILE_BSD 1 -#elif defined(__APPLE__) /* Is the apple sendfile still broken? */ -#define USE_SENDFILE_APPLE 2 -#else /* sendfile might not be available - always set to 0 */ -#define USE_SENDFILE 0 -#endif - -#endif - -#ifdef __MINGW32__ -static void fio_sock_perform_close_fd(intptr_t fd) { - SOCKET s = fd_data(fd).socket_handle; - int osffd = fd_data(fd).osffd; - fio_clear_handle(fd); - if (osffd != -1) { - _close(osffd); - } else if (s != -1) { - closesocket_ptr(s); - } -} -#else -static void fio_sock_perform_close_fd(intptr_t fd) { close(fd); } -#endif - -static inline void fio_sock_packet_rotate_unsafe(uintptr_t fd) { - fio_packet_s *packet = fd_data(fd).packet; - fd_data(fd).packet = packet->next; - fio_atomic_sub(&fd_data(fd).packet_count, 1); - if (!packet->next) { - fd_data(fd).packet_last = &fd_data(fd).packet; - fd_data(fd).packet_count = 0; - } else if (&packet->next == fd_data(fd).packet_last) { - fd_data(fd).packet_last = &fd_data(fd).packet; - } - fio_packet_free(packet); -} - -static int fio_sock_write_buffer(int fd, fio_packet_s *packet) { - int written = fd_data(fd).rw_hooks->write( - fd2uuid(fd), fd_data(fd).rw_udata, - ((uint8_t *)packet->data.buffer + packet->offset), packet->length); - if (written > 0) { - packet->length -= written; - packet->offset += written; - if (!packet->length) { - fio_sock_packet_rotate_unsafe(fd); - } - } - return written; -} - -static int fio_sock_write_from_fd(int fd, fio_packet_s *packet) { - ssize_t asked = 0; - ssize_t sent = 0; - ssize_t total = 0; - char buff[BUFFER_FILE_READ_SIZE]; - do { - packet->offset += sent; - packet->length -= sent; - retry: - asked = pread(packet->data.fd, buff, - ((packet->length < BUFFER_FILE_READ_SIZE) - ? packet->length - : BUFFER_FILE_READ_SIZE), - packet->offset); - if (asked <= 0) - goto read_error; - sent = fd_data(fd).rw_hooks->write(fd2uuid(fd), fd_data(fd).rw_udata, buff, - asked); - } while (sent == asked && packet->length); - if (sent >= 0) { - packet->offset += sent; - packet->length -= sent; - total += sent; - if (!packet->length) { - fio_sock_packet_rotate_unsafe(fd); - return 1; - } - } - return total; - -read_error: - if (sent == 0) { - fio_sock_packet_rotate_unsafe(fd); - return 1; - } - if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) - goto retry; - return -1; -} - -#if USE_SENDFILE_LINUX /* linux sendfile API */ -#include - -static int fio_sock_sendfile_from_fd(int fd, fio_packet_s *packet) { - ssize_t sent; - sent = - sendfile64(fd, packet->data.fd, (off_t *)&packet->offset, packet->length); - if (sent < 0) - return -1; - packet->length -= sent; - if (!packet->length) - fio_sock_packet_rotate_unsafe(fd); - return sent; -} - -#elif USE_SENDFILE_BSD || USE_SENDFILE_APPLE /* FreeBSD / Apple API */ -#include - -static int fio_sock_sendfile_from_fd(int fd, fio_packet_s *packet) { - off_t act_sent = 0; - ssize_t ret = 0; - while (packet->length) { - act_sent = packet->length; -#if USE_SENDFILE_APPLE - ret = sendfile(packet->data.fd, fd, packet->offset, &act_sent, NULL, 0); -#else - ret = sendfile(packet->data.fd, fd, packet->offset, (size_t)act_sent, NULL, - &act_sent, 0); -#endif - if (ret < 0) - goto error; - packet->length -= act_sent; - packet->offset += act_sent; - } - fio_sock_packet_rotate_unsafe(fd); - return act_sent; -error: - if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { - packet->length -= act_sent; - packet->offset += act_sent; - } - return -1; -} - -#else -static int (*fio_sock_sendfile_from_fd)(int fd, fio_packet_s *packet) = - fio_sock_write_from_fd; - -#endif - -/* ***************************************************************************** -Socket / Connection Functions -***************************************************************************** */ - -/** - * Returns the information available about the socket's peer address. - * - * If no information is available, the struct will be initialized with zero - * (`addr == NULL`). - * The information is only available when the socket was accepted using - * `fio_accept` or opened using `fio_connect`. - */ - -/** - * `fio_read` attempts to read up to count bytes from the socket into the - * buffer starting at `buffer`. - * - * `fio_read`'s return values are wildly different then the native return - * values and they aim at making far simpler sense. - * - * `fio_read` returns the number of bytes read (0 is a valid return value which - * simply means that no bytes were read from the buffer). - * - * On a fatal connection error that leads to the connection being closed (or if - * the connection is already closed), `fio_read` returns -1. - * - * The value 0 is the valid value indicating no data was read. - * - * Data might be available in the kernel's buffer while it is not available to - * be read using `fio_read` (i.e., when using a transport layer, such as TLS). - */ -ssize_t fio_read(intptr_t uuid, void *buffer, size_t count) { - if (!uuid_is_valid(uuid) || !uuid_data(uuid).open) { - errno = EBADF; - return -1; - } - if (count == 0) - return 0; - fio_lock(&uuid_data(uuid).sock_lock); - ssize_t (*rw_read)(intptr_t, void *, void *, size_t) = - uuid_data(uuid).rw_hooks->read; - void *udata = uuid_data(uuid).rw_udata; - fio_unlock(&uuid_data(uuid).sock_lock); - int old_errno = errno; - ssize_t ret; -retry_int: - ret = rw_read(uuid, udata, buffer, count); - if (ret > 0) { - fio_touch(uuid); - return ret; - } - if (ret < 0 && errno == EINTR) - goto retry_int; - if (ret < 0 && - (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOTCONN)) { - errno = old_errno; - return 0; - } - fio_force_close(uuid); - return -1; -} - -/** - * `fio_write2_fn` is the actual function behind the macro `fio_write2`. - */ -ssize_t fio_write2_fn(intptr_t uuid, fio_write_args_s options) { - if (!uuid_is_valid(uuid)) - goto error; - - /* create packet */ - fio_packet_s *packet = fio_packet_alloc(); - *packet = (fio_packet_s){ - .length = options.length, - .offset = options.offset, - .data.buffer = (void *)options.data.buffer, - }; - if (options.is_fd) { - packet->write_func = (uuid_data(uuid).rw_hooks == &FIO_DEFAULT_RW_HOOKS) - ? fio_sock_sendfile_from_fd - : fio_sock_write_from_fd; - packet->dealloc = - (options.after.dealloc ? options.after.dealloc - : (void (*)(void *))fio_sock_perform_close_fd); - } else { - packet->write_func = fio_sock_write_buffer; - packet->dealloc = (options.after.dealloc ? options.after.dealloc : free); - } - /* add packet to outgoing list */ - uint8_t was_empty = 1; - fio_lock(&uuid_data(uuid).sock_lock); - if (!uuid_is_valid(uuid)) { - goto locked_error; - } - if (uuid_data(uuid).packet) - was_empty = 0; - if (options.urgent == 0) { - *uuid_data(uuid).packet_last = packet; - uuid_data(uuid).packet_last = &packet->next; - } else { - fio_packet_s **pos = &uuid_data(uuid).packet; - if (*pos) - pos = &(*pos)->next; - packet->next = *pos; - *pos = packet; - if (!packet->next) { - uuid_data(uuid).packet_last = &packet->next; - } - } - fio_atomic_add(&uuid_data(uuid).packet_count, 1); - fio_unlock(&uuid_data(uuid).sock_lock); - - if (was_empty) { - touchfd(fio_uuid2fd(uuid)); - deferred_on_ready((void *)uuid, (void *)1); - } - return 0; -locked_error: - fio_unlock(&uuid_data(uuid).sock_lock); - fio_packet_free(packet); - /** fallthrough and free buffer */ -error: - if (options.after.dealloc) { - options.after.dealloc((void *)options.data.buffer); - } - errno = EBADF; - return -1; -} - -/** A noop function for fio_write2 in cases not deallocation is required. */ -void FIO_DEALLOC_NOOP(void *arg) { (void)arg; } - -/** - * Returns the number of `fio_write` calls that are waiting in the socket's - * queue and haven't been processed. - */ -size_t fio_pending(intptr_t uuid) { - if (!uuid_is_valid(uuid)) - return 0; - return uuid_data(uuid).packet_count; -} - -/** - * `fio_close` marks the connection for disconnection once all the data was - * sent. The actual disconnection will be managed by the `fio_flush` function. - * - * `fio_flash` will be automatically scheduled. - */ -void fio_close(intptr_t uuid) { - if (!uuid_is_valid(uuid)) { - errno = EBADF; - return; - } - if (uuid_data(uuid).packet || uuid_data(uuid).sock_lock) { - uuid_data(uuid).close = 1; - fio_force_event(uuid, FIO_EVENT_ON_READY); - return; - } - fio_force_close(uuid); -} - -/** - * `fio_force_close` closes the connection immediately, without adhering to any - * protocol restrictions and without sending any remaining data in the - * connection buffer. - */ -void fio_force_close(intptr_t uuid) { - if (!uuid_is_valid(uuid)) { - errno = EBADF; - return; - } - // FIO_LOG_DEBUG("fio_force_close called for uuid %p", (void *)uuid); - /* make sure the close marker is set */ - if (!uuid_data(uuid).close) - uuid_data(uuid).close = 1; - /* clear away any packets in case we want to cut the connection short. */ - fio_packet_s *packet = NULL; - fio_lock(&uuid_data(uuid).sock_lock); - packet = uuid_data(uuid).packet; - uuid_data(uuid).packet = NULL; - uuid_data(uuid).packet_last = &uuid_data(uuid).packet; - uuid_data(uuid).sent = 0; - fio_unlock(&uuid_data(uuid).sock_lock); - while (packet) { - fio_packet_s *tmp = packet; - packet = packet->next; - fio_packet_free(tmp); - } - /* check for rw-hooks termination packet */ - if (uuid_data(uuid).open && (uuid_data(uuid).close & 1) && - uuid_data(uuid).rw_hooks->before_close(uuid, uuid_data(uuid).rw_udata)) { - uuid_data(uuid).close = 2; /* don't repeat the before_close callback */ - fio_touch(uuid); - fio_poll_add_write(fio_uuid2fd(uuid)); - return; - } - fio_lock(&uuid_data(uuid).protocol_lock); - fio_clear_fd(fio_uuid2fd(uuid), 0); - fio_unlock(&uuid_data(uuid).protocol_lock); -#ifdef __MINGW32__ - fio_sock_perform_close_fd(fio_uuid2fd(uuid)); -#else - close(fio_uuid2fd(uuid)); -#endif -#if FIO_ENGINE_POLL || FIO_ENGINE_WSAPOLL - fio_poll_remove_fd(fio_uuid2fd(uuid)); -#endif - if (fio_data->connection_count) - fio_atomic_sub(&fio_data->connection_count, 1); -} - -/** - * `fio_flush` attempts to write any remaining data in the internal buffer to - * the underlying file descriptor and closes the underlying file descriptor once - * if it's marked for closure (and all the data was sent). - * - * Return values: 1 will be returned if data remains in the buffer. 0 - * will be returned if the buffer was fully drained. -1 will be returned on an - * error or when the connection is closed. - */ -ssize_t fio_flush(intptr_t uuid) { - if (!uuid_is_valid(uuid)) - goto invalid; - errno = 0; - ssize_t flushed = 0; - int tmp; - /* start critical section */ - if (fio_trylock(&uuid_data(uuid).sock_lock)) - goto would_block; - - if (!uuid_data(uuid).packet) - goto flush_rw_hook; - - const fio_packet_s *old_packet = uuid_data(uuid).packet; - const size_t old_sent = uuid_data(uuid).sent; - - tmp = uuid_data(uuid).packet->write_func(fio_uuid2fd(uuid), - uuid_data(uuid).packet); - if (tmp <= 0) { - goto test_errno; - } - - if (uuid_data(uuid).packet_count >= FIO_SLOWLORIS_LIMIT && - uuid_data(uuid).packet == old_packet && - uuid_data(uuid).sent >= old_sent && - (uuid_data(uuid).sent - old_sent) < 32768) { - /* Slowloris attack assumed */ - goto attacked; - } - - /* end critical section */ - fio_unlock(&uuid_data(uuid).sock_lock); - - /* test for fio_close marker */ - if (!uuid_data(uuid).packet && uuid_data(uuid).close) - goto closed; - - /* return state */ - return uuid_data(uuid).open && uuid_data(uuid).packet != NULL; - -would_block: - errno = EWOULDBLOCK; - return -1; - -closed: - fio_force_close(uuid); - return -1; - -flush_rw_hook: - if(uuid_data(uuid).rw_hooks) - flushed = uuid_data(uuid).rw_hooks->flush(uuid, uuid_data(uuid).rw_udata); - fio_unlock(&uuid_data(uuid).sock_lock); - if (!flushed) - return 0; - if (flushed < 0) { - goto test_errno; - } - touchfd(fio_uuid2fd(uuid)); - return 1; - -test_errno: - fio_unlock(&uuid_data(uuid).sock_lock); - switch (errno) { - case EWOULDBLOCK: /* fallthrough */ -#if EWOULDBLOCK != EAGAIN - case EAGAIN: /* fallthrough */ -#endif - case ENOTCONN: /* fallthrough */ - case EINPROGRESS: /* fallthrough */ - case ENOSPC: /* fallthrough */ - case EADDRNOTAVAIL: /* fallthrough */ - case EINTR: - case 0: - return 1; - case EFAULT: - FIO_LOG_ERROR("fio_flush EFAULT - possible memory address error sent to " - "Unix socket."); - /* fallthrough */ - case EPIPE: /* fallthrough */ - case EIO: /* fallthrough */ - case EINVAL: /* fallthrough */ - case EBADF: - uuid_data(uuid).close = 1; - fio_force_close(uuid); - return -1; - } - FIO_LOG_DEBUG("UUID error: %p (%d): %s\n", (void *)uuid, errno, - strerror(errno)); - return 0; - -invalid: - /* bad UUID */ - errno = EBADF; - return -1; - -attacked: - /* don't close, just detach from facil.io and mark uuid as invalid */ - FIO_LOG_WARNING("(facil.io) possible Slowloris attack from %.*s", - (int)fio_peer_addr(uuid).len, fio_peer_addr(uuid).data); - fio_unlock(&uuid_data(uuid).sock_lock); - fio_clear_fd(fio_uuid2fd(uuid), 0); - return -1; -} - -/** `fio_flush_all` attempts flush all the open connections. */ -size_t fio_flush_all(void) { - if (!fio_data) - return 0; - size_t count = 0; - for (uintptr_t i = 0; i <= fio_data->max_protocol_fd; ++i) { - if ((fd_data(i).open || fd_data(i).packet) && - fd_data(i).rw_hooks && fio_flush(fd2uuid(i)) > 0) - ++count; - } - return count; -} - -/* ***************************************************************************** -Connection Read / Write Hooks, for overriding the system calls -***************************************************************************** */ - -static ssize_t fio_hooks_default_read(intptr_t uuid, void *udata, void *buf, - size_t count) { -#ifdef __MINGW32__ - int len = recv_ptr(fd_data(fio_uuid2fd(uuid)).socket_handle, buf, count, 0); - if (len != SOCKET_ERROR) - return len; - int error = WSAGetLastError(); - switch (error) { - case WSAEWOULDBLOCK: - errno = EWOULDBLOCK; - break; - case WSAENOTCONN: - errno = ENOTCONN; - break; - case WSAEINTR: - errno = EINTR; - break; - default: - errno = error; - } - return -1; -#else - return read(fio_uuid2fd(uuid), buf, count); -#endif - (void)(udata); -} -static ssize_t fio_hooks_default_write(intptr_t uuid, void *udata, - const void *buf, size_t count) { -#ifdef __MINGW32__ - return send_ptr(fd_data(fio_uuid2fd(uuid)).socket_handle, buf, count, 0); -#else - return write(fio_uuid2fd(uuid), buf, count); -#endif - (void)(udata); -} - -static ssize_t fio_hooks_default_before_close(intptr_t uuid, void *udata) { - return 0; - (void)udata; - (void)uuid; -} - -static ssize_t fio_hooks_default_flush(intptr_t uuid, void *udata) { - return 0; - (void)(uuid); - (void)(udata); -} - -static void fio_hooks_default_cleanup(void *udata) { (void)(udata); } - -const fio_rw_hook_s FIO_DEFAULT_RW_HOOKS = { - .read = fio_hooks_default_read, - .write = fio_hooks_default_write, - .flush = fio_hooks_default_flush, - .before_close = fio_hooks_default_before_close, - .cleanup = fio_hooks_default_cleanup, -}; - -static inline void fio_rw_hook_validate(fio_rw_hook_s *rw_hooks) { - if (!rw_hooks->read) - rw_hooks->read = fio_hooks_default_read; - if (!rw_hooks->write) - rw_hooks->write = fio_hooks_default_write; - if (!rw_hooks->flush) - rw_hooks->flush = fio_hooks_default_flush; - if (!rw_hooks->before_close) - rw_hooks->before_close = fio_hooks_default_before_close; - if (!rw_hooks->cleanup) - rw_hooks->cleanup = fio_hooks_default_cleanup; -} - -/** - * Replaces an existing read/write hook with another from within a read/write - * hook callback. - * - * Does NOT call any cleanup callbacks. - * - * Returns -1 on error, 0 on success. - */ -int fio_rw_hook_replace_unsafe(intptr_t uuid, fio_rw_hook_s *rw_hooks, - void *udata) { - int replaced = -1; - uint8_t was_locked; - intptr_t fd = fio_uuid2fd(uuid); - fio_rw_hook_validate(rw_hooks); - /* protect against some fulishness... but not all of it. */ - was_locked = fio_trylock(&fd_data(fd).sock_lock); - if (uuid_is_valid(uuid)) { - fd_data(fd).rw_hooks = rw_hooks; - fd_data(fd).rw_udata = udata; - replaced = 0; - } - if (!was_locked) - fio_unlock(&fd_data(fd).sock_lock); - return replaced; -} - -/** Sets a socket hook state (a pointer to the struct). */ -int fio_rw_hook_set(intptr_t uuid, fio_rw_hook_s *rw_hooks, void *udata) { - if (fio_is_closed(uuid)) - goto invalid_uuid; - fio_rw_hook_validate(rw_hooks); - intptr_t fd = fio_uuid2fd(uuid); - fio_rw_hook_s *old_rw_hooks; - void *old_udata; - fio_lock(&fd_data(fd).sock_lock); - if (fd2uuid(fd) != uuid) { - fio_unlock(&fd_data(fd).sock_lock); - goto invalid_uuid; - } - old_rw_hooks = fd_data(fd).rw_hooks; - old_udata = fd_data(fd).rw_udata; - fd_data(fd).rw_hooks = rw_hooks; - fd_data(fd).rw_udata = udata; - fio_unlock(&fd_data(fd).sock_lock); - if (old_rw_hooks && old_rw_hooks->cleanup) - old_rw_hooks->cleanup(old_udata); - return 0; -invalid_uuid: - if (!rw_hooks->cleanup) - rw_hooks->cleanup(udata); - return -1; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - IO Protocols and Attachment - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Setting the protocol -***************************************************************************** */ - -/* managing the protocol pointer array and the `on_close` callback */ -static int fio_attach__internal(void *uuid_, void *protocol_) { - intptr_t uuid = (intptr_t)uuid_; - fio_protocol_s *protocol = (fio_protocol_s *)protocol_; - if (protocol) { - if (!protocol->on_close) { - protocol->on_close = mock_on_ev; - } - if (!protocol->on_data) { - protocol->on_data = mock_on_data; - } - if (!protocol->on_ready) { - protocol->on_ready = mock_on_ev; - } - if (!protocol->ping) { - protocol->ping = mock_ping; - } - if (!protocol->on_shutdown) { - protocol->on_shutdown = mock_on_shutdown; - } - prt_meta(protocol) = (protocol_metadata_s){.rsv = 0}; - } - if (!uuid_is_valid(uuid)) - goto invalid_uuid_unlocked; - fio_lock(&uuid_data(uuid).protocol_lock); - if (!uuid_is_valid(uuid)) { - goto invalid_uuid; - } - fio_protocol_s *old_pr = uuid_data(uuid).protocol; - uuid_data(uuid).open = 1; - uuid_data(uuid).protocol = protocol; - touchfd(fio_uuid2fd(uuid)); - fio_unlock(&uuid_data(uuid).protocol_lock); - if (old_pr) { - /* protocol replacement */ - fio_defer_push_task(deferred_on_close, (void *)uuid, old_pr); - if (!protocol) { - /* hijacking */ - fio_poll_remove_fd(fio_uuid2fd(uuid)); - fio_poll_add_write(fio_uuid2fd(uuid)); - } - } else if (protocol) { - /* adding a new uuid to the reactor */ - fio_poll_add(fio_uuid2fd(uuid)); - } - return 0; - -invalid_uuid: - fio_unlock(&uuid_data(uuid).protocol_lock); -invalid_uuid_unlocked: - // FIO_LOG_DEBUG("fio_attach failed for invalid uuid %p", (void *)uuid); - if (protocol) - fio_defer_push_task(deferred_on_close, (void *)uuid, protocol); - if (uuid == -1) - errno = EBADF; - else - errno = ENOTCONN; - return -1; -} - -/** - * Attaches (or updates) a protocol object to a socket UUID. - * Returns -1 on error and 0 on success. - */ -void fio_attach(intptr_t uuid, fio_protocol_s *protocol) { - fio_attach__internal((void *)uuid, protocol); -} -/** Attaches (or updates) a protocol object to a socket UUID. - * Returns -1 on error and 0 on success. - */ -void fio_attach_fd(int fd, fio_protocol_s *protocol) { - fio_attach__internal((void *)fio_fd2uuid(fd), protocol); -} - -/** Sets a timeout for a specific connection (only when running and valid). */ -void fio_timeout_set(intptr_t uuid, uint8_t timeout) { - if (uuid_is_valid(uuid)) { - touchfd(fio_uuid2fd(uuid)); - uuid_data(uuid).timeout = timeout; - } else { - FIO_LOG_DEBUG("Called fio_timeout_set for invalid uuid %p", (void *)uuid); - } -} -/** Gets a timeout for a specific connection. Returns 0 if there's no set - * timeout or the connection is inactive. */ -uint8_t fio_timeout_get(intptr_t uuid) { return uuid_data(uuid).timeout; } - -/* ***************************************************************************** -Core Callbacks for forking / starting up / cleaning up -***************************************************************************** */ - -typedef struct { - fio_ls_embd_s node; - void (*func)(void *); - void *arg; -} callback_data_s; - -typedef struct { - fio_lock_i lock; - fio_ls_embd_s callbacks; -} callback_collection_s; - -static callback_collection_s callback_collection[FIO_CALL_NEVER + 1]; - -static void fio_state_on_idle_perform(void *task, void *arg) { - ((void (*)(void *))(uintptr_t)task)(arg); -} - -static inline void fio_state_callback_ensure(callback_collection_s *c) { - if (c->callbacks.next) - return; - c->callbacks = (fio_ls_embd_s)FIO_LS_INIT(c->callbacks); -} - -/** Adds a callback to the list of callbacks to be called for the event. */ -void fio_state_callback_add(callback_type_e c_type, void (*func)(void *), - void *arg) { - if (c_type == FIO_CALL_ON_INITIALIZE && fio_data) { - func(arg); - return; - } - if (!func || (int)c_type < 0 || c_type > FIO_CALL_NEVER) - return; - fio_lock(&callback_collection[c_type].lock); - fio_state_callback_ensure(&callback_collection[c_type]); - callback_data_s *tmp = malloc(sizeof(*tmp)); - FIO_ASSERT_ALLOC(tmp); - *tmp = (callback_data_s){.func = func, .arg = arg}; - fio_ls_embd_push(&callback_collection[c_type].callbacks, &tmp->node); - fio_unlock(&callback_collection[c_type].lock); -} - -/** Removes a callback from the list of callbacks to be called for the event. */ -int fio_state_callback_remove(callback_type_e c_type, void (*func)(void *), - void *arg) { - if ((int)c_type < 0 || c_type > FIO_CALL_NEVER) - return -1; - fio_lock(&callback_collection[c_type].lock); - FIO_LS_EMBD_FOR(&callback_collection[c_type].callbacks, pos) { - callback_data_s *tmp = (FIO_LS_EMBD_OBJ(callback_data_s, node, pos)); - if (tmp->func == func && tmp->arg == arg) { - fio_ls_embd_remove(&tmp->node); - free(tmp); - goto success; - } - } - fio_unlock(&callback_collection[c_type].lock); - return -1; -success: - fio_unlock(&callback_collection[c_type].lock); - return -0; -} - -/** Forces all the existing callbacks to run, as if the event occurred. */ -void fio_state_callback_force(callback_type_e c_type) { - if ((int)c_type < 0 || c_type > FIO_CALL_NEVER) - return; - /* copy collection */ - fio_ls_embd_s copy = FIO_LS_INIT(copy); - fio_lock(&callback_collection[c_type].lock); - fio_state_callback_ensure(&callback_collection[c_type]); - switch (c_type) { /* the difference between `unshift` and `push` */ - case FIO_CALL_ON_INITIALIZE: /* fallthrough */ - case FIO_CALL_PRE_START: /* fallthrough */ - case FIO_CALL_BEFORE_FORK: /* fallthrough */ - case FIO_CALL_AFTER_FORK: /* fallthrough */ - case FIO_CALL_IN_CHILD: /* fallthrough */ - case FIO_CALL_IN_MASTER: /* fallthrough */ - case FIO_CALL_ON_START: /* fallthrough */ - FIO_LS_EMBD_FOR(&callback_collection[c_type].callbacks, pos) { - callback_data_s *tmp = fio_malloc(sizeof(*tmp)); - FIO_ASSERT_ALLOC(tmp); - *tmp = *(FIO_LS_EMBD_OBJ(callback_data_s, node, pos)); - fio_ls_embd_unshift(©, &tmp->node); - } - break; - - case FIO_CALL_ON_IDLE: /* idle callbacks are orderless and evented */ - FIO_LS_EMBD_FOR(&callback_collection[c_type].callbacks, pos) { - callback_data_s *tmp = FIO_LS_EMBD_OBJ(callback_data_s, node, pos); - fio_defer_push_task(fio_state_on_idle_perform, - (void *)(uintptr_t)tmp->func, tmp->arg); - } - break; - - case FIO_CALL_ON_SHUTDOWN: /* fallthrough */ - case FIO_CALL_ON_FINISH: /* fallthrough */ - case FIO_CALL_ON_PARENT_CRUSH: /* fallthrough */ - case FIO_CALL_ON_CHILD_CRUSH: /* fallthrough */ - case FIO_CALL_AT_EXIT: /* fallthrough */ - case FIO_CALL_NEVER: /* fallthrough */ - default: - FIO_LS_EMBD_FOR(&callback_collection[c_type].callbacks, pos) { - callback_data_s *tmp = fio_malloc(sizeof(*tmp)); - FIO_ASSERT_ALLOC(tmp); - *tmp = *(FIO_LS_EMBD_OBJ(callback_data_s, node, pos)); - fio_ls_embd_push(©, &tmp->node); - } - break; - } - - fio_unlock(&callback_collection[c_type].lock); - /* run callbacks + free data */ - while (fio_ls_embd_any(©)) { - callback_data_s *tmp = - FIO_LS_EMBD_OBJ(callback_data_s, node, fio_ls_embd_pop(©)); - if (tmp->func) { - tmp->func(tmp->arg); - } - fio_free(tmp); - } -} - -/** Clears all the existing callbacks for the event. */ -void fio_state_callback_clear(callback_type_e c_type) { - if ((int)c_type < 0 || c_type > FIO_CALL_NEVER) - return; - fio_lock(&callback_collection[c_type].lock); - fio_state_callback_ensure(&callback_collection[c_type]); - while (fio_ls_embd_any(&callback_collection[c_type].callbacks)) { - callback_data_s *tmp = FIO_LS_EMBD_OBJ( - callback_data_s, node, - fio_ls_embd_shift(&callback_collection[c_type].callbacks)); - free(tmp); - } - fio_unlock(&callback_collection[c_type].lock); -} - -void fio_state_callback_on_fork(void) { - for (size_t i = 0; i < (FIO_CALL_NEVER + 1); ++i) { - callback_collection[i].lock = FIO_LOCK_INIT; - } -} -void fio_state_callback_clear_all(void) { - for (size_t i = 0; i < (FIO_CALL_NEVER + 1); ++i) { - fio_state_callback_clear((callback_type_e)i); - } -} - -/* ***************************************************************************** -IO bound tasks -***************************************************************************** */ - -// typedef struct { -// enum fio_protocol_lock_e type; -// void (*task)(intptr_t uuid, fio_protocol_s *, void *udata); -// void *udata; -// void (*fallback)(intptr_t uuid, void *udata); -// } fio_defer_iotask_args_s; - -static void fio_io_task_perform(void *uuid_, void *args_) { - fio_defer_iotask_args_s *args = args_; - intptr_t uuid = (intptr_t)uuid_; - fio_protocol_s *pr = fio_protocol_try_lock(uuid, args->type); - if (!pr) - goto postpone; - args->task(uuid, pr, args->udata); - fio_protocol_unlock(pr, args->type); - fio_free(args); - return; -postpone: - if (errno == EBADF) { - if (args->fallback) - args->fallback(uuid, args->udata); - fio_free(args); - return; - } - fio_defer_push_task(fio_io_task_perform, uuid_, args_); -} -/** - * Schedules a protected connection task. The task will run within the - * connection's lock. - * - * If an error ocuurs or the connection is closed before the task can run, the - * `fallback` task wil be called instead, allowing for resource cleanup. - */ -void fio_defer_io_task FIO_IGNORE_MACRO(intptr_t uuid, - fio_defer_iotask_args_s args) { - if (!args.task) { - if (args.fallback) - fio_defer_push_task((void (*)(void *, void *))args.fallback, (void *)uuid, - args.udata); - return; - } - fio_defer_iotask_args_s *cpy = fio_malloc(sizeof(*cpy)); - FIO_ASSERT_ALLOC(cpy); - *cpy = args; - fio_defer_push_task(fio_io_task_perform, (void *)uuid, cpy); -} - -/* ***************************************************************************** -Initialize the library -***************************************************************************** */ - -static void fio_pubsub_on_fork(void); - -/* Called within a child process after it starts. */ -static void fio_on_fork(void) { - fio_timer_lock = FIO_LOCK_INIT; - fio_data->lock = FIO_LOCK_INIT; - fio_defer_on_fork(); - fio_malloc_after_fork(); - fio_poll_init(); - fio_state_callback_on_fork(); - - /* don't pass open connections belonging to the parent onto the child. */ - const size_t limit = fio_data->capa; - for (size_t i = 0; i < limit; ++i) { - fd_data(i).sock_lock = FIO_LOCK_INIT; - fd_data(i).protocol_lock = FIO_LOCK_INIT; - if (fd_data(i).protocol && fd_data(i).open) { - /* open without protocol might be waiting for the child (listening) */ - fd_data(i).protocol->rsv = 0; - fio_force_close(fd2uuid(i)); - } - } - - fio_pubsub_on_fork(); - uint16_t old_active = fio_data->active; - fio_data->active = 0; - fio_defer_perform(); - fio_data->active = old_active; - fio_data->is_worker = 1; -} - -static void fio_mem_destroy(void); -static void __attribute__((destructor)) fio_lib_destroy(void) { - uint8_t add_eol = fio_is_master(); - fio_data->active = 0; - fio_on_fork(); - fio_defer_perform(); - fio_timer_clear_all(); - fio_defer_perform(); - fio_state_callback_force(FIO_CALL_AT_EXIT); - fio_state_callback_clear_all(); - fio_defer_perform(); - fio_poll_close(); - fio_free(fio_data); - /* memory library destruction must be last */ - fio_mem_destroy(); - FIO_LOG_DEBUG("(%d) facil.io resources released, exit complete.", - (int)getpid()); - if (add_eol) - fprintf(stderr, "\n"); /* add EOL to logs (logging adds EOL before text */ -} - -static void fio_mem_init(void); -#ifndef __MINGW32__ -static void fio_cluster_init(void); -#endif -static void fio_pubsub_initialize(void); -static void __attribute__((constructor)) fio_lib_init(void) { - /* detect socket capacity - MUST be first...*/ - ssize_t capa = 0; - { -#ifdef _SC_OPEN_MAX - capa = sysconf(_SC_OPEN_MAX); -#elif defined(__MINGW32__) - /** iodine/ruby specific */ - capa = 1024; - HMODULE mh = GetModuleHandleA("ws2_32.dll"); - accept_ptr = GetProcAddress(mh, "accept"); - bind_ptr = GetProcAddress(mh, "bind"); - closesocket_ptr = GetProcAddress(mh, "closesocket"); - connect_ptr = GetProcAddress(mh, "connect"); - getsockopt_ptr = GetProcAddress(mh, "getsockopt"); - ioctlsocket_ptr = GetProcAddress(mh, "ioctlsocket"); - listen_ptr = GetProcAddress(mh, "listen"); - recv_ptr = GetProcAddress(mh, "recv"); - send_ptr = GetProcAddress(mh, "send"); - setsockopt_ptr = GetProcAddress(mh, "setsockopt"); - socket_ptr = GetProcAddress(mh, "socket"); -#elif defined(FOPEN_MAX) - capa = FOPEN_MAX; -#endif -#ifndef __MINGW32__ - // try to maximize limits - collect max and set to max - struct rlimit rlim = {.rlim_max = 0}; - if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) { - FIO_LOG_WARNING("`getrlimit` failed in `fio_lib_init`."); - perror("\terrno:"); - } else { - rlim_t original = rlim.rlim_cur; - rlim.rlim_cur = rlim.rlim_max; - if (rlim.rlim_cur > FIO_MAX_SOCK_CAPACITY) { - rlim.rlim_cur = rlim.rlim_max = FIO_MAX_SOCK_CAPACITY; - } - while (setrlimit(RLIMIT_NOFILE, &rlim) == -1 && rlim.rlim_cur > original) - --rlim.rlim_cur; - getrlimit(RLIMIT_NOFILE, &rlim); - capa = rlim.rlim_cur; - if (capa > 1024) /* leave a slice of room */ - capa -= 16; - } -#endif - - /* initialize memory allocator */ - fio_mem_init(); - /* initialize polling engine */ - fio_poll_init(); - /* initialize the cluster engine */ - fio_pubsub_initialize(); -#if DEBUG -#if FIO_ENGINE_POLL - FIO_LOG_INFO("facil.io " FIO_VERSION_STRING " capacity initialization:\n" - "* Meximum open files %zu out of %zu\n" - "* Allocating %zu bytes for state handling.\n" - "* %zu bytes per connection + %zu for state handling.", - capa, (size_t)rlim.rlim_max, - (sizeof(*fio_data) + (capa * (sizeof(*fio_data->poll))) + - (capa * (sizeof(*fio_data->info)))), - (sizeof(*fio_data->poll) + sizeof(*fio_data->info)), - sizeof(*fio_data)); -#elif FIO_ENGINE_WSAPOLL - FIO_LOG_INFO("facil.io " FIO_VERSION_STRING " capacity initialization:\n" - "* Meximum open files %zu out of %zu\n" - "* Allocating %zu bytes for state handling.\n" - "* %zu bytes per connection + %zu for state handling.", - capa, FOPEN_MAX, - (sizeof(*fio_data) + (capa * (sizeof(*fio_data->poll))) + - (capa * (sizeof(*fio_data->info)))), - (sizeof(*fio_data->poll) + sizeof(*fio_data->info)), - sizeof(*fio_data)); -#else - FIO_LOG_INFO("facil.io " FIO_VERSION_STRING " capacity initialization:\n" - "* Meximum open files %zu out of %zu\n" - "* Allocating %zu bytes for state handling.\n" - "* %zu bytes per connection + %zu for state handling.", - capa, (size_t)rlim.rlim_max, - (sizeof(*fio_data) + (capa * (sizeof(*fio_data->info)))), - (sizeof(*fio_data->info)), sizeof(*fio_data)); -#endif -#endif - } - -#if FIO_ENGINE_POLL || FIO_ENGINE_WSAPOLL - /* allocate and initialize main data structures by detected capacity */ - fio_data = fio_mmap(sizeof(*fio_data) + (capa * (sizeof(*fio_data->poll))) + - (capa * (sizeof(*fio_data->info)))); - FIO_ASSERT_ALLOC(fio_data); - fio_data->capa = capa; - fio_data->poll = - (void *)((uintptr_t)(fio_data + 1) + (sizeof(fio_data->info[0]) * capa)); -#else - /* allocate and initialize main data structures by detected capacity */ - fio_data = fio_mmap(sizeof(*fio_data) + (capa * (sizeof(*fio_data->info)))); - FIO_ASSERT_ALLOC(fio_data); - fio_data->capa = capa; -#endif - fio_data->parent = getpid(); - fio_data->connection_count = 0; - fio_mark_time(); - - for (ssize_t i = 0; i < capa; ++i) { - fio_clear_fd(i, 0); -#ifdef __MINGW32__ - fio_clear_handle(i); -#endif -#if FIO_ENGINE_POLL || FIO_ENGINE_WSAPOLL - fio_data->poll[i].fd = -1; -#endif - } - - /* call initialization callbacks */ - fio_state_callback_force(FIO_CALL_ON_INITIALIZE); - fio_state_callback_clear(FIO_CALL_ON_INITIALIZE); -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - Running the IO Reactor - - - - - - - - - - - - - -***************************************************************************** */ - -static void fio_cluster_signal_children(void); - -static void fio_review_timeout(void *arg, void *ignr) { - // TODO: Fix review for connections with no protocol? - (void)ignr; - fio_protocol_s *tmp; - time_t review = fio_data->last_cycle.tv_sec; - intptr_t fd = (intptr_t)arg; - - uint16_t timeout = fd_data(fd).timeout; - if (!timeout) - timeout = 300; /* enforced timout settings */ - if (!fd_data(fd).open || fd_data(fd).active + timeout >= review) - goto finish; - if (fd_data(fd).protocol) { - tmp = protocol_try_lock(fd, FIO_PR_LOCK_STATE); - if (!tmp) { - if (errno == EBADF) - goto finish; - goto reschedule; - } - if (prt_meta(tmp).locks[FIO_PR_LOCK_TASK] || - prt_meta(tmp).locks[FIO_PR_LOCK_WRITE]) - goto unlock; - fio_defer_push_task(deferred_ping, (void *)fio_fd2uuid((int)fd), NULL); - unlock: - protocol_unlock(tmp, FIO_PR_LOCK_STATE); - } else { - /* open FD but no protocol? RW hook thing or listening sockets? */ - if (fd_data(fd).rw_hooks != &FIO_DEFAULT_RW_HOOKS) - fio_close(fd2uuid(fd)); - } -finish: - do { - fd++; - } while (!fd_data(fd).open && (fd <= fio_data->max_protocol_fd)); - - if (fio_data->max_protocol_fd < fd) { - fio_data->need_review = 1; - return; - } -reschedule: - fio_defer_push_task(fio_review_timeout, (void *)fd, NULL); -} - -/* reactor pattern cycling - common actions */ -static void fio_cycle_schedule_events(void) { - static int idle = 0; - static time_t last_to_review = 0; - fio_mark_time(); - fio_timer_schedule(); - if (fio_signal_children_flag) { - /* hot restart support */ - fio_signal_children_flag = 0; - fio_cluster_signal_children(); - } - int events = fio_poll(); - if (events < 0) { - return; - } - if (events > 0) { - idle = 1; - } else { - /* events == 0 */ - if (idle) { - fio_state_callback_force(FIO_CALL_ON_IDLE); - idle = 0; - } - } - if (fio_data->need_review && fio_data->last_cycle.tv_sec != last_to_review) { - last_to_review = fio_data->last_cycle.tv_sec; - fio_data->need_review = 0; - fio_defer_push_task(fio_review_timeout, (void *)0, NULL); - } -} - -/* reactor pattern cycling during cleanup */ -static void fio_cycle_unwind(void *ignr, void *ignr2) { - if (fio_data->connection_count) { - fio_cycle_schedule_events(); - fio_defer_push_task(fio_cycle_unwind, ignr, ignr2); - return; - } - fio_stop(); - return; -} - -/* reactor pattern cycling */ -static void fio_cycle(void *ignr, void *ignr2) { - fio_cycle_schedule_events(); - if (fio_data->active) { - fio_defer_push_task(fio_cycle, ignr, ignr2); - return; - } - return; -} - -/* TODO: fixme */ -static void fio_worker_startup(void) { - /* Call the on_start callbacks for worker processes. */ - if (fio_data->workers == 1 || fio_data->is_worker) { - fio_state_callback_force(FIO_CALL_ON_START); - fio_state_callback_clear(FIO_CALL_ON_START); - } - - if (fio_data->workers == 1) { - /* Single Process - the root is also a worker */ - fio_data->is_worker = 1; - } else if (fio_data->is_worker) { - /* Worker Process */ - FIO_LOG_INFO("%d is running.", (int)getpid()); - } else { - /* Root Process should run in single thread mode */ - fio_data->threads = 1; - } - - /* require timeout review */ - fio_data->need_review = 1; - - /* the cycle task will loop by re-scheduling until it's time to finish */ - fio_defer_push_task(fio_cycle, NULL, NULL); - - /* A single thread doesn't need a pool. */ - if (fio_data->threads > 1) { - fio_defer_thread_pool_join(fio_defer_thread_pool_new(fio_data->threads)); - } else { - fio_defer_perform(); - } -} - -/* performs all clean-up / shutdown requirements except for the exit sequence */ -static void fio_worker_cleanup(void) { - /* switch to winding down */ - if (fio_data->is_worker) - FIO_LOG_INFO("(%d) detected exit signal.", (int)getpid()); - else - FIO_LOG_INFO("Server Detected exit signal."); - fio_state_callback_force(FIO_CALL_ON_SHUTDOWN); - for (size_t i = 0; i <= fio_data->max_protocol_fd; ++i) { - if (fd_data(i).protocol) { - fio_defer_push_task(deferred_on_shutdown, (void *)fd2uuid(i), NULL); - } - } - fio_defer_push_task(fio_cycle_unwind, NULL, NULL); - fio_defer_perform(); -#ifdef __MINGW32__ - size_t end = fio_data->capa; - while (0 < end && fio_data->poll[end-1].fd == (SOCKET)-1) - --end; - for (size_t i = 0; i <= fio_data->capa; ++i) { - // ensure _all_ socket handles and fds are closed for good - fio_force_close(fd2uuid(i)); - } -#else - for (size_t i = 0; i <= fio_data->max_protocol_fd; ++i) { - if (fd_data(i).protocol || fd_data(i).open) { - fio_force_close(fd2uuid(i)); - } - } -#endif - fio_timer_clear_all(); - fio_defer_perform(); - if (!fio_data->is_worker) { - fio_cluster_signal_children(); - fio_defer_perform(); - while (wait(NULL) != -1) - ; - } - fio_defer_perform(); - fio_state_callback_force(FIO_CALL_ON_FINISH); - fio_defer_perform(); - fio_signal_handler_reset(); - if (fio_data->parent == getpid()) { - FIO_LOG_INFO(" --- Shutdown Complete ---\n"); - } else { - FIO_LOG_INFO("(%d) cleanup complete.", (int)getpid()); - } -} - -static void fio_sentinel_task(void *arg1, void *arg2); -static void *fio_sentinel_worker_thread(void *arg) { - errno = 0; - pid_t child = fio_fork(); - /* release fork lock. */ - fio_unlock(&fio_fork_lock); - if (child == -1) { - FIO_LOG_FATAL("couldn't spawn worker."); - perror("\n errno"); - kill(fio_parent_pid(), SIGINT); - fio_stop(); - return NULL; - } else if (child) { - int status; - waitpid(child, &status, 0); -#if DEBUG - if (fio_data->active) { /* !WIFEXITED(status) || WEXITSTATUS(status) */ - if (!WIFEXITED(status) || WEXITSTATUS(status)) { - FIO_LOG_FATAL("Child worker (%d) crashed. Stopping services.", child); - fio_state_callback_force(FIO_CALL_ON_CHILD_CRUSH); - } else { - FIO_LOG_FATAL("Child worker (%d) shutdown. Stopping services.", child); - } - kill(0, SIGINT); - } -#else - if (fio_data->active) { - /* don't call any functions while forking. */ - fio_lock(&fio_fork_lock); - if (!WIFEXITED(status) || WEXITSTATUS(status)) { - FIO_LOG_ERROR("Child worker (%d) crashed. Respawning worker.", - (int)child); - fio_state_callback_force(FIO_CALL_ON_CHILD_CRUSH); - } else { - FIO_LOG_WARNING("Child worker (%d) shutdown. Respawning worker.", - (int)child); - } - fio_defer_push_task(fio_sentinel_task, NULL, NULL); - fio_unlock(&fio_fork_lock); - } -#endif - } else { - fio_on_fork(); - fio_state_callback_force(FIO_CALL_AFTER_FORK); - fio_state_callback_force(FIO_CALL_IN_CHILD); - fio_worker_startup(); - fio_worker_cleanup(); - exit(0); - } - return NULL; - (void)arg; -} - -static void fio_sentinel_task(void *arg1, void *arg2) { - if (!fio_data->active) - return; - fio_state_callback_force(FIO_CALL_BEFORE_FORK); - fio_lock(&fio_fork_lock); /* will wait for worker thread to release lock. */ - void *thrd = - fio_thread_new(fio_sentinel_worker_thread, (void *)&fio_fork_lock); - fio_thread_free(thrd); - fio_lock(&fio_fork_lock); /* will wait for worker thread to release lock. */ - fio_unlock(&fio_fork_lock); /* release lock for next fork. */ - fio_state_callback_force(FIO_CALL_AFTER_FORK); - fio_state_callback_force(FIO_CALL_IN_MASTER); - (void)arg1; - (void)arg2; -} - -FIO_FUNC void fio_start_(void) {} /* marker for SublimeText3 jump feature */ - -/** - * Starts the facil.io event loop. This function will return after facil.io is - * done (after shutdown). - * - * See the `struct fio_start_args` details for any possible named arguments. - * - * This method blocks the current thread until the server is stopped (when a - * SIGINT/SIGTERM is received). - */ -void fio_start FIO_IGNORE_MACRO(struct fio_start_args args) { - fio_expected_concurrency(&args.threads, &args.workers); - fio_signal_handler_setup(); - - fio_data->workers = (uint16_t)args.workers; - fio_data->threads = (uint16_t)args.threads; - fio_data->active = 1; - fio_data->is_worker = 0; - - fio_state_callback_force(FIO_CALL_PRE_START); -#if HAVE_OPENSSL - FIO_LOG_INFO( - "Server is running %u %s X %u %s with facil.io " FIO_VERSION_STRING - " (%s)\n" - "* Linked to %s\n" - "* Detected capacity: %d open file limit\n" - "* Root pid: %d\n" - "* Press ^C to stop\n", - fio_data->workers, fio_data->workers > 1 ? "workers" : "worker", - fio_data->threads, fio_data->threads > 1 ? "threads" : "thread", - fio_engine(), - OpenSSL_version(0), - fio_data->capa, (int)fio_data->parent); -#else - FIO_LOG_INFO( - "Server is running %u %s X %u %s with facil.io " FIO_VERSION_STRING - " (%s)\n" - "* Detected capacity: %d open file limit\n" - "* Root pid: %d\n" - "* Press ^C to stop\n", - fio_data->workers, fio_data->workers > 1 ? "workers" : "worker", - fio_data->threads, fio_data->threads > 1 ? "threads" : "thread", - fio_engine(), - fio_data->capa, (int)fio_data->parent); -#endif - if (args.workers > 1) { - for (int i = 0; i < args.workers && fio_data->active; ++i) { - fio_sentinel_task(NULL, NULL); - } - } - fio_worker_startup(); - fio_worker_cleanup(); -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - Converting Numbers to Strings (and back) - - - - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Strings to Numbers -***************************************************************************** */ - -FIO_FUNC inline size_t fio_atol_skip_zero(char **pstr) { - char *const start = *pstr; - while (**pstr == '0') { - ++(*pstr); - } - return (size_t)(*pstr - *start); -} - -/* consumes any digits in the string (base 2-10), returning their value */ -FIO_FUNC inline uint64_t fio_atol_consume(char **pstr, uint8_t base) { - uint64_t result = 0; - const uint64_t limit = UINT64_MAX - (base * base); - while (**pstr >= '0' && **pstr < ('0' + base) && result <= (limit)) { - result = (result * base) + (**pstr - '0'); - ++(*pstr); - } - return result; -} - -/* returns true if there's data to be skipped */ -FIO_FUNC inline uint8_t fio_atol_skip_test(char **pstr, uint8_t base) { - return (**pstr >= '0' && **pstr < ('0' + base)); -} - -/* consumes any digits in the string (base 2-10), returning the count skipped */ -FIO_FUNC inline uint64_t fio_atol_skip(char **pstr, uint8_t base) { - uint64_t result = 0; - while (fio_atol_skip_test(pstr, base)) { - ++result; - ++(*pstr); - } - return result; -} - -/* consumes any hex data in the string, returning their value */ -FIO_FUNC inline uint64_t fio_atol_consume_hex(char **pstr) { - uint64_t result = 0; - const uint64_t limit = UINT64_MAX - (16 * 16); - for (; result <= limit;) { - uint8_t tmp; - if (**pstr >= '0' && **pstr <= '9') - tmp = **pstr - '0'; - else if (**pstr >= 'A' && **pstr <= 'F') - tmp = **pstr - ('A' - 10); - else if (**pstr >= 'a' && **pstr <= 'f') - tmp = **pstr - ('a' - 10); - else - return result; - result = (result << 4) | tmp; - ++(*pstr); - } - return result; -} - -/* returns true if there's data to be skipped */ -FIO_FUNC inline uint8_t fio_atol_skip_hex_test(char **pstr) { - return (**pstr >= '0' && **pstr <= '9') || (**pstr >= 'A' && **pstr <= 'F') || - (**pstr >= 'a' && **pstr <= 'f'); -} - -/* consumes any digits in the string (base 2-10), returning the count skipped */ -FIO_FUNC inline uint64_t fio_atol_skip_hex(char **pstr) { - uint64_t result = 0; - while (fio_atol_skip_hex_test(pstr)) { - ++result; - ++(*pstr); - } - return result; -} - -/* caches a up to 8*8 */ -// static inline fio_atol_pow_10_cache(size_t ex) {} - -/** - * A helper function that converts between String data to a signed int64_t. - * - * Numbers are assumed to be in base 10. Octal (`0###`), Hex (`0x##`/`x##`) and - * binary (`0b##`/ `b##`) are recognized as well. For binary Most Significant - * Bit must come first. - * - * The most significant difference between this function and `strtol` (aside of - * API design), is the added support for binary representations. - */ -int64_t fio_atol(char **pstr) { - /* No binary representation in strtol */ - char *str = *pstr; - uint64_t result = 0; - uint8_t invert = 0; - while (isspace(*str)) - ++(str); - if (str[0] == '-') { - invert ^= 1; - ++str; - } else if (*str == '+') { - ++(str); - } - - if (str[0] == 'B' || str[0] == 'b' || - (str[0] == '0' && (str[1] == 'b' || str[1] == 'B'))) { - /* base 2 */ - if (str[0] == '0') - str++; - str++; - fio_atol_skip_zero(&str); - while (str[0] == '0' || str[0] == '1') { - result = (result << 1) | (str[0] - '0'); - str++; - } - goto sign; /* no overlow protection, since sign might be embedded */ - - } else if (str[0] == 'x' || str[0] == 'X' || - (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))) { - /* base 16 */ - if (str[0] == '0') - str++; - str++; - fio_atol_skip_zero(&str); - result = fio_atol_consume_hex(&str); - if (fio_atol_skip_hex_test(&str)) /* too large for a number */ - return 0; - goto sign; /* no overlow protection, since sign might be embedded */ - } else if (str[0] == '0') { - fio_atol_skip_zero(&str); - /* base 8 */ - result = fio_atol_consume(&str, 8); - if (fio_atol_skip_test(&str, 8)) /* too large for a number */ - return 0; - } else { - /* base 10 */ - result = fio_atol_consume(&str, 10); - if (fio_atol_skip_test(&str, 10)) /* too large for a number */ - return 0; - } - if (result & ((uint64_t)1 << 63)) - result = INT64_MAX; /* signed overflow protection */ -sign: - if (invert) - result = 0 - result; - *pstr = str; - return (int64_t)result; -} - -/** A helper function that converts between String data to a signed double. */ -double fio_atof(char **pstr) { return strtold(*pstr, pstr); } - -/* ***************************************************************************** -Numbers to Strings -***************************************************************************** */ - -/** - * A helper function that writes a signed int64_t to a string. - * - * No overflow guard is provided, make sure there's at least 68 bytes - * available (for base 2). - * - * Offers special support for base 2 (binary), base 8 (octal), base 10 and base - * 16 (hex). An unsupported base will silently default to base 10. Prefixes - * are automatically added (i.e., "0x" for hex and "0b" for base 2). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -size_t fio_ltoa(char *dest, int64_t num, uint8_t base) { - const char notation[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - size_t len = 0; - char buf[48]; /* we only need up to 20 for base 10, but base 3 needs 41... */ - - if (!num) - goto zero; - - switch (base) { - case 1: /* fallthrough */ - case 2: - /* Base 2 */ - { - uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */ - uint8_t i = 0; /* counting bits */ - dest[len++] = '0'; - dest[len++] = 'b'; - - while ((i < 64) && (n & 0x8000000000000000) == 0) { - n = n << 1; - i++; - } - /* make sure the Binary representation doesn't appear signed. */ - if (i) { - dest[len++] = '0'; - } - /* write to dest. */ - while (i < 64) { - dest[len++] = ((n & 0x8000000000000000) ? '1' : '0'); - n = n << 1; - i++; - } - dest[len] = 0; - return len; - } - case 8: - /* Base 8 */ - { - uint64_t l = 0; - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - dest[len++] = '0'; - - while (num) { - buf[l++] = '0' + (num & 7); - num = num >> 3; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - } - - case 16: - /* Base 16 */ - { - uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */ - uint8_t i = 0; /* counting bits */ - dest[len++] = '0'; - dest[len++] = 'x'; - while (i < 8 && (n & 0xFF00000000000000) == 0) { - n = n << 8; - i++; - } - /* make sure the Hex representation doesn't appear misleadingly signed. */ - if (i && (n & 0x8000000000000000)) { - dest[len++] = '0'; - dest[len++] = '0'; - } - /* write the damn thing, high to low */ - while (i < 8) { - uint8_t tmp = (n & 0xF000000000000000) >> 60; - dest[len++] = notation[tmp]; - tmp = (n & 0x0F00000000000000) >> 56; - dest[len++] = notation[tmp]; - i++; - n = n << 8; - } - dest[len] = 0; - return len; - } - case 3: /* fallthrough */ - case 4: /* fallthrough */ - case 5: /* fallthrough */ - case 6: /* fallthrough */ - case 7: /* fallthrough */ - case 9: /* fallthrough */ - /* rare bases */ - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - uint64_t l = 0; - while (num) { - uint64_t t = num / base; - buf[l++] = '0' + (num - (t * base)); - num = t; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - - default: - break; - } - /* Base 10, the default base */ - - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - uint64_t l = 0; - while (num) { - uint64_t t = num / 10; - buf[l++] = '0' + (num - (t * 10)); - num = t; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - -zero: - switch (base) { - case 1: - case 2: - dest[len++] = '0'; - dest[len++] = 'b'; - break; - case 8: - dest[len++] = '0'; - break; - case 16: - dest[len++] = '0'; - dest[len++] = 'x'; - dest[len++] = '0'; - break; - } - dest[len++] = '0'; - dest[len] = 0; - return len; -} - -/** - * A helper function that converts between a double to a string. - * - * No overflow guard is provided, make sure there's at least 130 bytes - * available (for base 2). - * - * Supports base 2, base 10 and base 16. An unsupported base will silently - * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the - * beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -size_t fio_ftoa(char *dest, double num, uint8_t base) { - if (base == 2 || base == 16) { - /* handle the binary / Hex representation the same as if it were an - * int64_t - */ - int64_t *i = (void *)# - return fio_ltoa(dest, *i, base); - } - - size_t written = sprintf(dest, "%g", num); - uint8_t need_zero = 1; - char *start = dest; - while (*start) { - if (*start == ',') // locale issues? - *start = '.'; - if (*start == '.' || *start == 'e') { - need_zero = 0; - break; - } - start++; - } - if (need_zero) { - dest[written++] = '.'; - dest[written++] = '0'; - } - return written; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - SSL/TLS Weak Symbols for TLS Support - - - - - - - - -***************************************************************************** */ - #ifndef __MINGW32__ -/** - * Returns the number of registered ALPN protocol names. - * - * This could be used when deciding if protocol selection should be delegated to - * the ALPN mechanism, or whether a protocol should be immediately assigned. - * - * If no ALPN protocols are registered, zero (0) is returned. - */ -uintptr_t FIO_TLS_WEAK fio_tls_alpn_count(void *tls) { - return 0; - (void)tls; -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * the result of `fio_accept`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, void *tls, void *udata) { - FIO_LOG_FATAL("No supported SSL/TLS library available."); - exit(-1); - return; - (void)uuid; - (void)tls; - (void)udata; -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * one received by a `fio_connect` specified callback `on_connect`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, void *tls, void *udata) { - FIO_LOG_FATAL("No supported SSL/TLS library available."); - exit(-1); - return; - (void)uuid; - (void)tls; - (void)udata; -} - -/** - * Increase the reference count for the TLS object. - * - * Decrease with `fio_tls_destroy`. - */ -void FIO_TLS_WEAK fio_tls_dup(void *tls) { - FIO_LOG_FATAL("No supported SSL/TLS library available."); - exit(-1); - return; - (void)tls; -} - -/** - * Destroys the SSL/TLS context / settings object and frees any related - * resources / memory. - */ -void FIO_TLS_WEAK fio_tls_destroy(void *tls) { - FIO_LOG_FATAL("No supported SSL/TLS library available."); - exit(-1); - return; - (void)tls; -} -#endif - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - Listening to Incoming Connections - - - - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -The listening protocol (use the facil.io API to make a socket and attach it) -***************************************************************************** */ - -typedef struct { - fio_protocol_s pr; - intptr_t uuid; - void *udata; - void (*on_open)(intptr_t uuid, void *udata); - void (*on_start)(intptr_t uuid, void *udata); - void (*on_finish)(intptr_t uuid, void *udata); - char *port; - char *addr; - size_t port_len; - size_t addr_len; - void *tls; -} fio_listen_protocol_s; - -static void fio_listen_cleanup_task(void *pr_) { - fio_listen_protocol_s *pr = pr_; -#ifndef __MINGW32__ - if (pr->tls) - fio_tls_destroy(pr->tls); -#endif - if (pr->on_finish) { - pr->on_finish(pr->uuid, pr->udata); - } - fio_force_close(pr->uuid); - if (pr->addr && - (!pr->port || *pr->port == 0 || - (pr->port[0] == '0' && pr->port[1] == 0)) && - fio_is_master()) { - /* delete Unix sockets */ - unlink(pr->addr); - } - free(pr_); -} - -static void fio_listen_on_startup(void *pr_) { - fio_state_callback_remove(FIO_CALL_ON_SHUTDOWN, fio_listen_cleanup_task, pr_); - fio_listen_protocol_s *pr = pr_; - fio_attach(pr->uuid, &pr->pr); - if (pr->port_len) - FIO_LOG_DEBUG("(%d) started listening on port %s", (int)getpid(), pr->port); - else - FIO_LOG_DEBUG("(%d) started listening on Unix Socket at %s", (int)getpid(), - pr->addr); -} - -static void fio_listen_on_close(intptr_t uuid, fio_protocol_s *pr_) { - fio_listen_cleanup_task(pr_); - (void)uuid; -} - -static void fio_listen_on_data(intptr_t uuid, fio_protocol_s *pr_) { - fio_listen_protocol_s *pr = (fio_listen_protocol_s *)pr_; - for (int i = 0; i < 4; ++i) { - intptr_t client = fio_accept(uuid); - if (client == -1) - return; - pr->on_open(client, pr->udata); - } -} - -#ifndef __MINGW32__ -static void fio_listen_on_data_tls(intptr_t uuid, fio_protocol_s *pr_) { - fio_listen_protocol_s *pr = (fio_listen_protocol_s *)pr_; - for (int i = 0; i < 4; ++i) { - intptr_t client = fio_accept(uuid); - if (client == -1) - return; - fio_tls_accept(client, pr->tls, pr->udata); - pr->on_open(client, pr->udata); - } -} - -static void fio_listen_on_data_tls_alpn(intptr_t uuid, fio_protocol_s *pr_) { - fio_listen_protocol_s *pr = (fio_listen_protocol_s *)pr_; - for (int i = 0; i < 4; ++i) { - intptr_t client = fio_accept(uuid); - if (client == -1) - return; - fio_tls_accept(client, pr->tls, pr->udata); - } -} -#endif - -/* stub for editor - unused */ -void fio_listen____(void); -/** - * Schedule a network service on a listening socket. - * - * Returns the listening socket or -1 (on error). - */ -intptr_t fio_listen FIO_IGNORE_MACRO(struct fio_listen_args args) { - // ... -#ifdef __MINGW32__ - if ((!args.on_open) || - (!args.address && !args.port)) { - errno = EINVAL; - goto error; - } -#else - if ((!args.on_open && (!args.tls || !fio_tls_alpn_count(args.tls))) || - (!args.address && !args.port)) { - errno = EINVAL; - goto error; - } -#endif - - size_t addr_len = 0; - size_t port_len = 0; - if (args.address) - addr_len = strlen(args.address); - if (args.port) { - port_len = strlen(args.port); - char *tmp = (char *)args.port; - if (!fio_atol(&tmp)) { - port_len = 0; - args.port = NULL; - } - if (*tmp) { - /* port format was invalid, should be only numerals */ - errno = EINVAL; - goto error; - } - } - const intptr_t uuid = fio_socket(args.address, args.port, 1); - if (uuid == -1) - goto error; - - fio_listen_protocol_s *pr = malloc(sizeof(*pr) + addr_len + port_len + - ((addr_len + port_len) ? 2 : 0)); - FIO_ASSERT_ALLOC(pr); -#ifndef __MINGW32__ - if (args.tls) - fio_tls_dup(args.tls); -#endif - *pr = (fio_listen_protocol_s){ - .pr = - { - .on_close = fio_listen_on_close, - .ping = mock_ping_eternal, -#ifdef __MINGW32__ - .on_data = fio_listen_on_data, -#else - .on_data = (args.tls ? (fio_tls_alpn_count(args.tls) - ? fio_listen_on_data_tls_alpn - : fio_listen_on_data_tls) - : fio_listen_on_data), -#endif - }, - .uuid = uuid, - .udata = args.udata, - .on_open = args.on_open, - .on_start = args.on_start, - .on_finish = args.on_finish, - .tls = args.tls, - .addr_len = addr_len, - .port_len = port_len, - .addr = (char *)(pr + 1), - .port = ((char *)(pr + 1) + addr_len + 1), - }; - - if (addr_len) - memcpy(pr->addr, args.address, addr_len + 1); - if (port_len) - memcpy(pr->port, args.port, port_len + 1); - - if (fio_is_running()) { - fio_attach(pr->uuid, &pr->pr); - } else { - fio_state_callback_add(FIO_CALL_ON_START, fio_listen_on_startup, pr); - fio_state_callback_add(FIO_CALL_ON_SHUTDOWN, fio_listen_cleanup_task, pr); - } - - if (args.port) - FIO_LOG_INFO("Listening on port %s", args.port); - else - FIO_LOG_INFO("Listening on Unix Socket at %s", args.address); - - return uuid; -error: - if (args.on_finish) { - args.on_finish(-1, args.udata); - } - return -1; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - Connecting to remote servers as a client - - - - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -The connection protocol (use the facil.io API to make a socket and attach it) -***************************************************************************** */ - -typedef struct { - fio_protocol_s pr; - intptr_t uuid; - void *udata; - void *tls; - void (*on_connect)(intptr_t uuid, void *udata); - void (*on_fail)(intptr_t uuid, void *udata); -} fio_connect_protocol_s; - -static void fio_connect_on_close(intptr_t uuid, fio_protocol_s *pr_) { - fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_; - if (pr->on_fail) - pr->on_fail(uuid, pr->udata); -#ifndef __MINGW32__ - if (pr->tls) - fio_tls_destroy(pr->tls); -#endif - fio_free(pr); - (void)uuid; -} - -static void fio_connect_on_ready(intptr_t uuid, fio_protocol_s *pr_) { - fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_; - if (pr->pr.on_ready == mock_on_ev) - return; /* Don't call on_connect more than once */ - pr->pr.on_ready = mock_on_ev; - pr->on_fail = NULL; - pr->on_connect(uuid, pr->udata); - fio_poll_add(fio_uuid2fd(uuid)); - (void)uuid; -} - -#ifndef __MINGW32__ -static void fio_connect_on_ready_tls(intptr_t uuid, fio_protocol_s *pr_) { - fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_; - if (pr->pr.on_ready == mock_on_ev) - return; /* Don't call on_connect more than once */ - pr->pr.on_ready = mock_on_ev; - pr->on_fail = NULL; - fio_tls_connect(uuid, pr->tls, pr->udata); - pr->on_connect(uuid, pr->udata); - fio_poll_add(fio_uuid2fd(uuid)); - (void)uuid; -} - -static void fio_connect_on_ready_tls_alpn(intptr_t uuid, fio_protocol_s *pr_) { - fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_; - if (pr->pr.on_ready == mock_on_ev) - return; /* Don't call on_connect more than once */ - pr->pr.on_ready = mock_on_ev; - pr->on_fail = NULL; - fio_tls_connect(uuid, pr->tls, pr->udata); - fio_poll_add(fio_uuid2fd(uuid)); - (void)uuid; -} -#endif - -/* stub for sublime text function navigation */ -intptr_t fio_connect___(struct fio_connect_args args); - -intptr_t fio_connect FIO_IGNORE_MACRO(struct fio_connect_args args) { -#ifdef __MINGW32__ - if ((!args.on_connect) || - (!args.address && !args.port)) { - errno = EINVAL; - goto error; - } -#else - if ((!args.on_connect && (!args.tls || !fio_tls_alpn_count(args.tls))) || - (!args.address && !args.port)) { - errno = EINVAL; - goto error; - } -#endif - const intptr_t uuid = fio_socket(args.address, args.port, 0); - if (uuid == -1) - goto error; - fio_timeout_set(uuid, args.timeout); - - fio_connect_protocol_s *pr = fio_malloc(sizeof(*pr)); - FIO_ASSERT_ALLOC(pr); -#ifndef __MINGW32__ - if (args.tls) - fio_tls_dup(args.tls); -#endif - *pr = (fio_connect_protocol_s){ - .pr = - { -#ifdef __MINGW32__ - .on_ready = fio_connect_on_ready, -#else - .on_ready = (args.tls ? (fio_tls_alpn_count(args.tls) - ? fio_connect_on_ready_tls_alpn - : fio_connect_on_ready_tls) - : fio_connect_on_ready), -#endif - .on_close = fio_connect_on_close, - }, - .uuid = uuid, - .tls = args.tls, - .udata = args.udata, - .on_connect = args.on_connect, - .on_fail = args.on_fail, - }; - fio_attach(uuid, &pr->pr); - return uuid; -error: - if (args.on_fail) - args.on_fail(-1, args.udata); - return -1; -} - -/* ***************************************************************************** -URL address parsing -***************************************************************************** */ - -/** - * Parses the URI returning it's components and their lengths (no decoding - * performed, doesn't accept decoded URIs). - * - * The returned string are NOT NUL terminated, they are merely locations within - * the original string. - * - * This function expects any of the following formats: - * - * * `/complete_path?query#target` - * - * i.e.: /index.html?page=1#list - * - * * `host:port/complete_path?query#target` - * - * i.e.: - * example.com/index.html - * example.com:8080/index.html - * - * * `schema://user:password@host:port/path?query#target` - * - * i.e.: http://example.com/index.html?page=1#list - * - * Invalid formats might produce unexpected results. No error testing performed. - */ -fio_url_s fio_url_parse(const char *url, size_t length) { - /* - Intention: - [schema://][user[:]][password[@]][host.com[:/]][:port/][/path][?quary][#target] - */ - const char *end = url + length; - const char *pos = url; - fio_url_s r = {.scheme = {.data = (char *)url}}; - if (length == 0) { - goto finish; - } - - if (pos[0] == '/') { - /* start at path */ - goto start_path; - } - - while (pos < end && pos[0] != ':' && pos[0] != '/' && pos[0] != '@' && - pos[0] != '#' && pos[0] != '?') - ++pos; - - if (pos == end) { - /* was only host (path starts with '/') */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - goto finish; - } - switch (pos[0]) { - case '@': - /* username@[host] */ - r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - goto start_host; - case '/': - /* host[/path] */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - goto start_path; - case '?': - /* host?[query] */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - goto start_query; - case '#': - /* host#[target] */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - goto start_target; - case ':': - if (pos + 2 <= end && pos[1] == '/' && pos[2] == '/') { - /* scheme:// */ - r.scheme.len = pos - url; - pos += 3; - } else { - /* username:[password] OR */ - /* host:[port] */ - r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - goto start_password; - } - break; - } - - // start_username: - url = pos; - while (pos < end && pos[0] != ':' && pos[0] != '/' && pos[0] != '@' - /* && pos[0] != '#' && pos[0] != '?' */) - ++pos; - - if (pos >= end) { /* scheme://host */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - goto finish; - } - - switch (pos[0]) { - case '/': - /* scheme://host[/path] */ - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - goto start_path; - case '@': - /* scheme://username@[host]... */ - r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - goto start_host; - case ':': - /* scheme://username:[password]@[host]... OR */ - /* scheme://host:[port][/...] */ - r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - break; - } - -start_password: - url = pos; - while (pos < end && pos[0] != '/' && pos[0] != '@') - ++pos; - - if (pos >= end) { - /* was host:port */ - r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - r.host = r.user; - r.user.len = 0; - goto finish; - ; - } - - switch (pos[0]) { - case '/': - r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - r.host = r.user; - r.user.len = 0; - goto start_path; - case '@': - r.password = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - break; - } - -start_host: - url = pos; - while (pos < end && pos[0] != '/' && pos[0] != ':' && pos[0] != '#' && - pos[0] != '?') - ++pos; - - r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - if (pos >= end) { - goto finish; - } - switch (pos[0]) { - case '/': - /* scheme://[...@]host[/path] */ - goto start_path; - case '?': - /* scheme://[...@]host?[query] (bad)*/ - ++pos; - goto start_query; - case '#': - /* scheme://[...@]host#[target] (bad)*/ - ++pos; - goto start_target; - // case ':': - /* scheme://[...@]host:[port] */ - } - ++pos; - - // start_port: - url = pos; - while (pos < end && pos[0] != '/' && pos[0] != '#' && pos[0] != '?') - ++pos; - - r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - - if (pos >= end) { - /* scheme://[...@]host:port */ - goto finish; - } - switch (pos[0]) { - case '?': - /* scheme://[...@]host:port?[query] (bad)*/ - ++pos; - goto start_query; - case '#': - /* scheme://[...@]host:port#[target] (bad)*/ - ++pos; - goto start_target; - // case '/': - /* scheme://[...@]host:port[/path] */ - } - -start_path: - url = pos; - while (pos < end && pos[0] != '#' && pos[0] != '?') - ++pos; - - r.path = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - - if (pos >= end) { - goto finish; - } - ++pos; - if (pos[-1] == '#') - goto start_target; - -start_query: - url = pos; - while (pos < end && pos[0] != '#') - ++pos; - - r.query = (fio_str_info_s){.data = (char *)url, .len = pos - url}; - ++pos; - - if (pos >= end) - goto finish; - -start_target: - r.target = (fio_str_info_s){.data = (char *)pos, .len = end - pos}; - -finish: - - /* set any empty values to NULL */ - if (!r.scheme.len) - r.scheme.data = NULL; - if (!r.user.len) - r.user.data = NULL; - if (!r.password.len) - r.password.data = NULL; - if (!r.host.len) - r.host.data = NULL; - if (!r.port.len) - r.port.data = NULL; - if (!r.path.len) - r.path.data = NULL; - if (!r.query.len) - r.query.data = NULL; - if (!r.target.len) - r.target.data = NULL; - - return r; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - - - - - - - - - - - - Cluster Messaging Implementation - - - - - - - - - - - - - - - - - - - - - - - - - - - -***************************************************************************** */ - -#if FIO_PUBSUB_SUPPORT - -/* ***************************************************************************** - * Data Structures - Channel / Subscriptions data - **************************************************************************** */ - -typedef enum fio_cluster_message_type_e { - FIO_CLUSTER_MSG_FORWARD, - FIO_CLUSTER_MSG_JSON, - FIO_CLUSTER_MSG_ROOT, - FIO_CLUSTER_MSG_ROOT_JSON, - FIO_CLUSTER_MSG_PUBSUB_SUB, - FIO_CLUSTER_MSG_PUBSUB_UNSUB, - FIO_CLUSTER_MSG_PATTERN_SUB, - FIO_CLUSTER_MSG_PATTERN_UNSUB, - FIO_CLUSTER_MSG_SHUTDOWN, - FIO_CLUSTER_MSG_ERROR, - FIO_CLUSTER_MSG_PING, -} fio_cluster_message_type_e; - -typedef struct fio_collection_s fio_collection_s; - -#ifndef __clang__ /* clang might misbehave by assumming non-alignment */ -#pragma pack(1) /* https://gitter.im/halide/Halide/archives/2018/07/24 */ -#endif -typedef struct { - size_t name_len; - char *name; - volatile size_t ref; - fio_ls_embd_s subscriptions; - fio_collection_s *parent; - fio_match_fn match; - fio_lock_i lock; -} channel_s; -#ifndef __clang__ -#pragma pack() -#endif - -struct subscription_s { - fio_ls_embd_s node; - channel_s *parent; - void (*on_message)(fio_msg_s *msg); - void (*on_unsubscribe)(void *udata1, void *udata2); - void *udata1; - void *udata2; - /** reference counter. */ - volatile uintptr_t ref; - /** prevents the callback from running concurrently for multiple messages. */ - fio_lock_i lock; - fio_lock_i unsubscribed; -}; - -/* Use `malloc` / `free`, because channels might have a long life. */ - -/** Used internally by the Set object to create a new channel. */ -static channel_s *fio_channel_copy(channel_s *src) { - channel_s *dest = malloc(sizeof(*dest) + src->name_len + 1); - FIO_ASSERT_ALLOC(dest); - dest->name_len = src->name_len; - dest->match = src->match; - dest->parent = src->parent; - dest->name = (char *)(dest + 1); - if (src->name_len) - memcpy(dest->name, src->name, src->name_len); - dest->name[src->name_len] = 0; - dest->subscriptions = (fio_ls_embd_s)FIO_LS_INIT(dest->subscriptions); - dest->ref = 1; - dest->lock = FIO_LOCK_INIT; - return dest; -} -/** Frees a channel (reference counting). */ -static void fio_channel_free(channel_s *ch) { - if (!ch) - return; - if (fio_atomic_sub(&ch->ref, 1)) - return; - free(ch); -} -/** Increases a channel's reference count. */ -static void fio_channel_dup(channel_s *ch) { - if (!ch) - return; - fio_atomic_add(&ch->ref, 1); -} -/** Tests if two channels are equal. */ -static int fio_channel_cmp(channel_s *ch1, channel_s *ch2) { - return ch1->name_len == ch2->name_len && ch1->match == ch2->match && - !memcmp(ch1->name, ch2->name, ch1->name_len); -} -/* pub/sub channels and core data sets have a long life, so avoid fio_malloc */ -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_SET_NAME fio_ch_set -#define FIO_SET_OBJ_TYPE channel_s * -#define FIO_SET_OBJ_COMPARE(o1, o2) fio_channel_cmp((o1), (o2)) -#define FIO_SET_OBJ_DESTROY(obj) fio_channel_free((obj)) -#define FIO_SET_OBJ_COPY(dest, src) ((dest) = fio_channel_copy((src))) -#include - -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_ARY_NAME fio_meta_ary -#define FIO_ARY_TYPE fio_msg_metadata_fn -#include - -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_SET_NAME fio_engine_set -#define FIO_SET_OBJ_TYPE fio_pubsub_engine_s * -#define FIO_SET_OBJ_COMPARE(k1, k2) ((k1) == (k2)) -#include - -struct fio_collection_s { - fio_ch_set_s channels; - fio_lock_i lock; -}; - -#define COLLECTION_INIT \ - { .channels = FIO_SET_INIT, .lock = FIO_LOCK_INIT } - -static struct { - fio_collection_s filters; - fio_collection_s pubsub; - fio_collection_s patterns; - struct { - fio_engine_set_s set; - fio_lock_i lock; - } engines; - struct { - fio_meta_ary_s ary; - fio_lock_i lock; - } meta; -} fio_postoffice = { - .filters = COLLECTION_INIT, - .pubsub = COLLECTION_INIT, - .patterns = COLLECTION_INIT, - .engines.lock = FIO_LOCK_INIT, - .meta.lock = FIO_LOCK_INIT, -}; - -/** used to contain the message before it's passed to the handler */ -typedef struct { - fio_msg_s msg; - size_t marker; - size_t meta_len; - fio_msg_metadata_s *meta; -} fio_msg_client_s; - -/** used to contain the message internally while publishing */ -typedef struct { - fio_str_info_s channel; - fio_str_info_s data; - uintptr_t ref; /* internal reference counter */ - int32_t filter; - int8_t is_json; - size_t meta_len; - fio_msg_metadata_s meta[]; -} fio_msg_internal_s; - -/** The default engine (settable). */ -fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; - -/* ***************************************************************************** -Internal message object creation -***************************************************************************** */ - -/** returns a temporary fio_meta_ary_s with a copy of the metadata array */ -static fio_meta_ary_s fio_postoffice_meta_copy_new(void) { - fio_meta_ary_s t = FIO_ARY_INIT; - if (!fio_meta_ary_count(&fio_postoffice.meta.ary)) { - return t; - } - fio_lock(&fio_postoffice.meta.lock); - fio_meta_ary_concat(&t, &fio_postoffice.meta.ary); - fio_unlock(&fio_postoffice.meta.lock); - return t; -} - -/** frees a temporary copy created by postoffice_meta_copy_new */ -static inline void fio_postoffice_meta_copy_free(fio_meta_ary_s *cpy) { - fio_meta_ary_free(cpy); -} - -static void fio_postoffice_meta_update(fio_msg_internal_s *m) { - if (m->filter || !m->meta_len) - return; - fio_meta_ary_s t = fio_postoffice_meta_copy_new(); - if (t.end > m->meta_len) - t.end = m->meta_len; - m->meta_len = t.end; - while (t.end) { - --t.end; - m->meta[t.end] = t.arry[t.end](m->channel, m->data, m->is_json); - } - fio_postoffice_meta_copy_free(&t); -} - -static fio_msg_internal_s * -fio_msg_internal_create(int32_t filter, uint32_t type, fio_str_info_s ch, - fio_str_info_s data, int8_t is_json, int8_t cpy) { - fio_meta_ary_s t = FIO_ARY_INIT; - if (!filter) - t = fio_postoffice_meta_copy_new(); - fio_msg_internal_s *m = fio_malloc(sizeof(*m) + (sizeof(*m->meta) * t.end) + - (ch.len) + (data.len) + 16 + 2); - FIO_ASSERT_ALLOC(m); - *m = (fio_msg_internal_s){ - .filter = filter, - .channel = (fio_str_info_s){.data = (char *)(m->meta + t.end) + 16, - .len = ch.len}, - .data = (fio_str_info_s){.data = ((char *)(m->meta + t.end) + ch.len + - 16 + 1), - .len = data.len}, - .is_json = is_json, - .ref = 1, - .meta_len = t.end, - }; - fio_u2str32((uint8_t *)(m + 1) + (sizeof(*m->meta) * t.end), ch.len); - fio_u2str32((uint8_t *)(m + 1) + (sizeof(*m->meta) * t.end) + 4, data.len); - fio_u2str32((uint8_t *)(m + 1) + (sizeof(*m->meta) * t.end) + 8, type); - fio_u2str32((uint8_t *)(m + 1) + (sizeof(*m->meta) * t.end) + 12, - (uint32_t)filter); - // m->channel.data[ch.len] = 0; /* redundant, fio_malloc is all zero */ - // m->data.data[data.len] = 0; /* redundant, fio_malloc is all zero */ - if (cpy) { - memcpy(m->channel.data, ch.data, ch.len); - memcpy(m->data.data, data.data, data.len); - while (t.end) { - --t.end; - m->meta[t.end] = t.arry[t.end](m->channel, m->data, is_json); - } - } - fio_postoffice_meta_copy_free(&t); - return m; -} - -/** frees the internal message data */ -static inline void fio_msg_internal_finalize(fio_msg_internal_s *m) { - if (!m->channel.len) - m->channel.data = NULL; - if (!m->data.len) - m->data.data = NULL; -} - -/** frees the internal message data */ -static inline void fio_msg_internal_free(fio_msg_internal_s *m) { - if (fio_atomic_sub(&m->ref, 1)) - return; - while (m->meta_len) { - --m->meta_len; - if (m->meta[m->meta_len].on_finish) { - fio_msg_s tmp_msg = { - .channel = m->channel, - .msg = m->data, - }; - m->meta[m->meta_len].on_finish(&tmp_msg, m->meta[m->meta_len].metadata); - } - } - fio_free(m); -} - -static void fio_msg_internal_free2(void *m) { fio_msg_internal_free(m); } - -/* add reference count to fio_msg_internal_s */ -static inline fio_msg_internal_s *fio_msg_internal_dup(fio_msg_internal_s *m) { - fio_atomic_add(&m->ref, 1); - return m; -} - -/** internal helper */ - -static inline ssize_t fio_msg_internal_send_dup(intptr_t uuid, - fio_msg_internal_s *m) { - return fio_write2(uuid, .data.buffer = fio_msg_internal_dup(m), - .offset = (sizeof(*m) + (m->meta_len * sizeof(*m->meta))), - .length = 16 + m->data.len + m->channel.len + 2, - .after.dealloc = fio_msg_internal_free2); -} - -/** - * A mock pub/sub callback for external subscriptions. - */ -static void fio_mock_on_message(fio_msg_s *msg) { (void)msg; } - -/* ***************************************************************************** -Channel Subscription Management -***************************************************************************** */ - -static void fio_pubsub_on_channel_create(channel_s *ch); -static void fio_pubsub_on_channel_destroy(channel_s *ch); - -/* some comon tasks extracted */ -static inline channel_s *fio_filter_dup_lock_internal(channel_s *ch, - uint64_t hashed, - fio_collection_s *c) { - fio_lock(&c->lock); - ch = fio_ch_set_insert(&c->channels, hashed, ch); - fio_unlock(&c->lock); - /* respect locking order to prevent deadlock with fio_unsubscribe */ - fio_lock(&ch->lock); - fio_lock(&c->lock); - /* check again if channels is still in collection */ - channel_s *found_ch = fio_ch_set_find(&c->channels, hashed, ch); - if (found_ch == ch) { - /* channel is still in collection: - * unlock the collection - * increase reference counter - * leave the channel locked and return it */ - fio_unlock(&c->lock); - fio_channel_dup(ch); - return ch; - } else { - /* channel could not be found, it has been removed from the collection */ - /* insert it again */ - fio_unlock(&c->lock); - fio_unlock(&ch->lock); - return fio_filter_dup_lock_internal(ch, hashed, c); - } -} - -/** Creates / finds a filter channel, adds a reference count and locks it. */ -static channel_s *fio_filter_dup_lock(uint32_t filter) { - channel_s ch = (channel_s){ - .name = (char *)&filter, - .name_len = (sizeof(filter)), - .parent = &fio_postoffice.filters, - .ref = 8, /* avoid freeing stack memory */ - .lock = FIO_LOCK_INIT, - }; - return fio_filter_dup_lock_internal(&ch, filter, &fio_postoffice.filters); -} - -/** Creates / finds a pubsub channel, adds a reference count and locks it. */ -static channel_s *fio_channel_dup_lock(fio_str_info_s name) { - channel_s ch = (channel_s){ - .name = name.data, - .name_len = name.len, - .parent = &fio_postoffice.pubsub, - .ref = 8, /* avoid freeing stack memory */ - .lock = FIO_LOCK_INIT, - }; - uint64_t hashed_name = FIO_HASH_FN( - name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub); - channel_s *ch_p = - fio_filter_dup_lock_internal(&ch, hashed_name, &fio_postoffice.pubsub); - if (fio_ls_embd_is_empty(&ch_p->subscriptions)) { - fio_pubsub_on_channel_create(ch_p); - } - return ch_p; -} - -/** Creates / finds a pattern channel, adds a reference count and locks it. */ -static channel_s *fio_channel_match_dup_lock(fio_str_info_s name, - fio_match_fn match) { - channel_s ch = (channel_s){ - .name = name.data, - .name_len = name.len, - .parent = &fio_postoffice.patterns, - .match = match, - .ref = 8, /* avoid freeing stack memory */ - .lock = FIO_LOCK_INIT, - }; - uint64_t hashed_name = FIO_HASH_FN( - name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub); - channel_s *ch_p = - fio_filter_dup_lock_internal(&ch, hashed_name, &fio_postoffice.patterns); - if (fio_ls_embd_is_empty(&ch_p->subscriptions)) { - fio_pubsub_on_channel_create(ch_p); - } - return ch_p; -} - -/* to be used for reference counting (subtructing) */ -static inline void fio_subscription_free(subscription_s *s) { - if (fio_atomic_sub(&s->ref, 1)) { - return; - } - if (s->on_unsubscribe) { - s->on_unsubscribe(s->udata1, s->udata2); - } - fio_channel_free(s->parent); - fio_free(s); -} - -/** SublimeText 3 marker */ -subscription_s *fio_subscribe___(subscribe_args_s args); - -/** Subscribes to a filter, pub/sub channel or pattern */ -subscription_s *fio_subscribe FIO_IGNORE_MACRO(subscribe_args_s args) { - if (!args.on_message) - goto error; - channel_s *ch; - subscription_s *s = fio_malloc(sizeof(*s)); - FIO_ASSERT_ALLOC(s); - *s = (subscription_s){ - .on_message = args.on_message, - .on_unsubscribe = args.on_unsubscribe, - .udata1 = args.udata1, - .udata2 = args.udata2, - .ref = 1, - .lock = FIO_LOCK_INIT, - }; - if (args.filter) { - ch = fio_filter_dup_lock(args.filter); - } else if (args.match) { - ch = fio_channel_match_dup_lock(args.channel, args.match); - } else { - ch = fio_channel_dup_lock(args.channel); - } - s->parent = ch; - fio_ls_embd_push(&ch->subscriptions, &s->node); - fio_unlock((&ch->lock)); - return s; -error: - if (args.on_unsubscribe) - args.on_unsubscribe(args.udata1, args.udata2); - return NULL; -} - -/** Unsubscribes from a filter, pub/sub channel or pattern */ -void fio_unsubscribe(subscription_s *s) { - if (!s) - return; - if (fio_trylock(&s->unsubscribed)) - goto finish; - fio_lock(&s->lock); - channel_s *ch = s->parent; - uint8_t removed = 0; - fio_lock(&ch->lock); - fio_ls_embd_remove(&s->node); - /* check if channel is done for */ - if (fio_ls_embd_is_empty(&ch->subscriptions)) { - fio_collection_s *c = ch->parent; - uint64_t hashed = FIO_HASH_FN( - ch->name, ch->name_len, &fio_postoffice.pubsub, &fio_postoffice.pubsub); - /* lock collection */ - fio_lock(&c->lock); - /* test again within lock */ - if (fio_ls_embd_is_empty(&ch->subscriptions)) { - fio_ch_set_remove(&c->channels, hashed, ch, NULL); - removed = (c != &fio_postoffice.filters); - } - fio_unlock(&c->lock); - } - fio_unlock(&ch->lock); - if (removed) { - fio_pubsub_on_channel_destroy(ch); - } - - /* promise the subscription will be inactive */ - s->on_message = NULL; - fio_unlock(&s->lock); -finish: - fio_subscription_free(s); -} - -/** - * This helper returns a temporary String with the subscription's channel (or a - * string representing the filter). - * - * To keep the string beyond the lifetime of the subscription, copy the string. - */ -fio_str_info_s fio_subscription_channel(subscription_s *subscription) { - return (fio_str_info_s){.data = subscription->parent->name, - .len = subscription->parent->name_len}; -} - -/* ***************************************************************************** -Engine handling and Management -***************************************************************************** */ -#ifndef __MINGW32__ -/* implemented later, informs root process about pub/sub subscriptions */ -static inline void fio_cluster_inform_root_about_channel(channel_s *ch, - int add); -#endif -/* runs in lock(!) let'm all know */ -static void fio_pubsub_on_channel_create(channel_s *ch) { - fio_lock(&fio_postoffice.engines.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.engines.set, pos) { - if (!pos->hash) - continue; - pos->obj->subscribe(pos->obj, - (fio_str_info_s){.data = ch->name, .len = ch->name_len}, - ch->match); - } - fio_unlock(&fio_postoffice.engines.lock); -#ifndef __MINGW32__ - fio_cluster_inform_root_about_channel(ch, 1); -#endif -} - -/* runs in lock(!) let'm all know */ -static void fio_pubsub_on_channel_destroy(channel_s *ch) { - fio_lock(&fio_postoffice.engines.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.engines.set, pos) { - if (!pos->hash) - continue; - pos->obj->unsubscribe( - pos->obj, (fio_str_info_s){.data = ch->name, .len = ch->name_len}, - ch->match); - } - fio_unlock(&fio_postoffice.engines.lock); -#ifndef __MINGW32__ - fio_cluster_inform_root_about_channel(ch, 0); -#endif -} - -/** - * Attaches an engine, so it's callback can be called by facil.io. - * - * The `subscribe` callback will be called for every existing channel. - * - * NOTE: the root (master) process will call `subscribe` for any channel in any - * process, while all the other processes will call `subscribe` only for their - * own channels. This allows engines to use the root (master) process as an - * exclusive subscription process. - */ -void fio_pubsub_attach(fio_pubsub_engine_s *engine) { - fio_lock(&fio_postoffice.engines.lock); - fio_engine_set_insert(&fio_postoffice.engines.set, (uintptr_t)engine, engine); - fio_unlock(&fio_postoffice.engines.lock); - fio_pubsub_reattach(engine); -} - -/** Detaches an engine, so it could be safely destroyed. */ -void fio_pubsub_detach(fio_pubsub_engine_s *engine) { - fio_lock(&fio_postoffice.engines.lock); - fio_engine_set_remove(&fio_postoffice.engines.set, (uintptr_t)engine, engine, - NULL); - fio_unlock(&fio_postoffice.engines.lock); -} - -/** Returns true (1) if the engine is attached to the system. */ -int fio_pubsub_is_attached(fio_pubsub_engine_s *engine) { - fio_pubsub_engine_s *addr; - fio_lock(&fio_postoffice.engines.lock); - addr = fio_engine_set_find(&fio_postoffice.engines.set, (uintptr_t)engine, - engine); - fio_unlock(&fio_postoffice.engines.lock); - return addr != NULL; -} - -/** - * Engines can ask facil.io to call the `subscribe` callback for all active - * channels. - * - * This allows engines that lost their connection to their Pub/Sub service to - * resubscribe all the currently active channels with the new connection. - * - * CAUTION: This is an evented task... try not to free the engine's memory while - * resubscriptions are under way... - * - * NOTE: the root (master) process will call `subscribe` for any channel in any - * process, while all the other processes will call `subscribe` only for their - * own channels. This allows engines to use the root (master) process as an - * exclusive subscription process. - */ -void fio_pubsub_reattach(fio_pubsub_engine_s *eng) { - fio_lock(&fio_postoffice.pubsub.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.pubsub.channels, pos) { - if (!pos->hash) - continue; - eng->subscribe( - eng, - (fio_str_info_s){.data = pos->obj->name, .len = pos->obj->name_len}, - NULL); - } - fio_unlock(&fio_postoffice.pubsub.lock); - fio_lock(&fio_postoffice.patterns.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.patterns.channels, pos) { - if (!pos->hash) - continue; - eng->subscribe( - eng, - (fio_str_info_s){.data = pos->obj->name, .len = pos->obj->name_len}, - pos->obj->match); - } - fio_unlock(&fio_postoffice.patterns.lock); -} - -/* ***************************************************************************** - * Message Metadata handling - **************************************************************************** */ - -void fio_message_metadata_callback_set(fio_msg_metadata_fn callback, - int enable) { - if (!callback) - return; - fio_lock(&fio_postoffice.meta.lock); - fio_meta_ary_remove2(&fio_postoffice.meta.ary, callback, NULL); - if (enable) - fio_meta_ary_push(&fio_postoffice.meta.ary, callback); - fio_unlock(&fio_postoffice.meta.lock); -} - -/** Finds the message's metadata by it's type ID. */ -void *fio_message_metadata(fio_msg_s *msg, intptr_t type_id) { - fio_msg_metadata_s *meta = ((fio_msg_client_s *)msg)->meta; - size_t len = ((fio_msg_client_s *)msg)->meta_len; - while (len) { - --len; - if (meta[len].type_id == type_id) - return meta[len].metadata; - } - return NULL; -} - -/* ***************************************************************************** - * Publishing to the subsriptions - **************************************************************************** */ - -/* common internal tasks */ -static channel_s *fio_channel_find_dup_internal(channel_s *ch_tmp, - uint64_t hashed, - fio_collection_s *c) { - fio_lock(&c->lock); - channel_s *ch = fio_ch_set_find(&c->channels, hashed, ch_tmp); - if (!ch) { - fio_unlock(&c->lock); - return NULL; - } - fio_channel_dup(ch); - fio_unlock(&c->lock); - return ch; -} - -/** Finds a filter channel, increasing it's reference count if it exists. */ -static channel_s *fio_filter_find_dup(uint32_t filter) { - channel_s tmp = {.name = (char *)(&filter), .name_len = sizeof(filter)}; - channel_s *ch = - fio_channel_find_dup_internal(&tmp, filter, &fio_postoffice.filters); - return ch; -} - -/** Finds a pubsub channel, increasing it's reference count if it exists. */ -static channel_s *fio_channel_find_dup(fio_str_info_s name) { - channel_s tmp = {.name = name.data, .name_len = name.len}; - uint64_t hashed_name = FIO_HASH_FN( - name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub); - channel_s *ch = - fio_channel_find_dup_internal(&tmp, hashed_name, &fio_postoffice.pubsub); - return ch; -} - -/* defers the callback (mark only) */ -void fio_message_defer(fio_msg_s *msg_) { - fio_msg_client_s *cl = (fio_msg_client_s *)msg_; - cl->marker = 1; -} - -/* performs the actual callback */ -static void fio_perform_subscription_callback(void *s_, void *msg_) { - subscription_s *s = s_; - if (fio_trylock(&s->lock)) { - fio_defer_push_task(fio_perform_subscription_callback, s_, msg_); - return; - } - fio_msg_internal_s *msg = (fio_msg_internal_s *)msg_; - fio_msg_client_s m = { - .msg = - { - .channel = msg->channel, - .msg = msg->data, - .filter = msg->filter, - .udata1 = s->udata1, - .udata2 = s->udata2, - }, - .meta_len = msg->meta_len, - .meta = msg->meta, - .marker = 0, - }; - if (s->on_message) { - /* the on_message callback is removed when a subscription is canceled. */ - s->on_message(&m.msg); - } - fio_unlock(&s->lock); - if (m.marker) { - fio_defer_push_task(fio_perform_subscription_callback, s_, msg_); - return; - } - fio_msg_internal_free(msg); - fio_subscription_free(s); -} - -/** UNSAFE! publishes a message to a channel, managing the reference counts */ -static void fio_publish2channel(channel_s *ch, fio_msg_internal_s *msg) { - FIO_LS_EMBD_FOR(&ch->subscriptions, pos) { - subscription_s *s = FIO_LS_EMBD_OBJ(subscription_s, node, pos); - if (!s || s->on_message == fio_mock_on_message) { - continue; - } - fio_atomic_add(&s->ref, 1); - fio_atomic_add(&msg->ref, 1); - fio_defer_push_task(fio_perform_subscription_callback, s, msg); - } - fio_msg_internal_free(msg); -} -static void fio_publish2channel_task(void *ch_, void *msg) { - channel_s *ch = ch_; - if (!ch_) - return; - if (!msg) - goto finish; - if (fio_trylock(&ch->lock)) { - fio_defer_push_urgent(fio_publish2channel_task, ch, msg); - return; - } - fio_publish2channel(ch, msg); - fio_unlock(&ch->lock); -finish: - fio_channel_free(ch); -} - -/** Publishes the message to the current process and frees the strings. */ -static void fio_publish2process(fio_msg_internal_s *m) { - fio_msg_internal_finalize(m); - channel_s *ch; - if (m->filter) { - ch = fio_filter_find_dup(m->filter); - if (!ch) { - goto finish; - } - } else { - ch = fio_channel_find_dup(m->channel); - } - /* exact match */ - if (ch) { - fio_defer_push_urgent(fio_publish2channel_task, ch, - fio_msg_internal_dup(m)); - } - if (m->filter == 0) { - /* pattern matching match */ - fio_lock(&fio_postoffice.patterns.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.patterns.channels, p) { - if (!p->hash) { - continue; - } - - if (p->obj->match( - (fio_str_info_s){.data = p->obj->name, .len = p->obj->name_len}, - m->channel)) { - fio_channel_dup(p->obj); - fio_defer_push_urgent(fio_publish2channel_task, p->obj, - fio_msg_internal_dup(m)); - } - } - fio_unlock(&fio_postoffice.patterns.lock); - } -finish: - fio_msg_internal_free(m); -} - -/* ***************************************************************************** - * Data Structures - Core Structures - **************************************************************************** */ - -#define CLUSTER_READ_BUFFER 16384 - -#define FIO_SET_NAME fio_sub_hash -#define FIO_SET_OBJ_TYPE subscription_s * -#define FIO_SET_KEY_TYPE fio_str_s -#define FIO_SET_KEY_COPY(k1, k2) \ - (k1) = FIO_STR_INIT; \ - fio_str_concat(&(k1), &(k2)) -#define FIO_SET_KEY_COMPARE(k1, k2) fio_str_iseq(&(k1), &(k2)) -#define FIO_SET_KEY_DESTROY(key) fio_str_free(&(key)) -#define FIO_SET_OBJ_DESTROY(obj) fio_unsubscribe(obj) -#include - -#define FIO_CLUSTER_NAME_LIMIT 255 - -typedef struct cluster_pr_s { - fio_protocol_s protocol; - fio_msg_internal_s *msg; - void (*handler)(struct cluster_pr_s *pr); - void (*sender)(void *data, intptr_t avoid_uuid); - fio_sub_hash_s pubsub; - fio_sub_hash_s patterns; - intptr_t uuid; - uint32_t exp_channel; - uint32_t exp_msg; - uint32_t type; - int32_t filter; - uint32_t length; - fio_lock_i lock; - uint8_t buffer[CLUSTER_READ_BUFFER]; -} cluster_pr_s; - -static struct cluster_data_s { - intptr_t uuid; - fio_ls_s clients; - fio_lock_i lock; - char name[FIO_CLUSTER_NAME_LIMIT + 1]; -} cluster_data = {.clients = FIO_LS_INIT(cluster_data.clients), - .lock = FIO_LOCK_INIT}; - -#ifndef __MINGW32__ -static void fio_cluster_data_cleanup(int delete_file) { - if (delete_file && cluster_data.name[0]) { -#if DEBUG - FIO_LOG_DEBUG("(%d) unlinking cluster's Unix socket.", (int)getpid()); -#endif - unlink(cluster_data.name); - } - while (fio_ls_any(&cluster_data.clients)) { - intptr_t uuid = (intptr_t)fio_ls_pop(&cluster_data.clients); - if (uuid > 0) { - fio_close(uuid); - } - } - cluster_data.uuid = 0; - cluster_data.lock = FIO_LOCK_INIT; - cluster_data.clients = (fio_ls_s)FIO_LS_INIT(cluster_data.clients); -} - -static void fio_cluster_cleanup(void *ignore) { - /* cleanup the cluster data */ - fio_cluster_data_cleanup(fio_parent_pid() == getpid()); - (void)ignore; -} - -static void fio_cluster_init(void) { - fio_cluster_data_cleanup(0); - /* create a unique socket name */ - char *tmp_folder = getenv("TMPDIR"); - uint32_t tmp_folder_len = 0; - if (!tmp_folder || ((tmp_folder_len = (uint32_t)strlen(tmp_folder)) > - (FIO_CLUSTER_NAME_LIMIT - 28))) { -#ifdef P_tmpdir - tmp_folder = (char *)P_tmpdir; - if (tmp_folder) - tmp_folder_len = (uint32_t)strlen(tmp_folder); -#else - tmp_folder = "/tmp/"; - tmp_folder_len = 5; -#endif - } - if (tmp_folder_len >= (FIO_CLUSTER_NAME_LIMIT - 28)) { - tmp_folder_len = 0; - } - if (tmp_folder_len) { - memcpy(cluster_data.name, tmp_folder, tmp_folder_len); - if (cluster_data.name[tmp_folder_len - 1] != '/') - cluster_data.name[tmp_folder_len++] = '/'; - } - memcpy(cluster_data.name + tmp_folder_len, "fio-usock-", 10); - tmp_folder_len += 10; - tmp_folder_len += - snprintf(cluster_data.name + tmp_folder_len, - FIO_CLUSTER_NAME_LIMIT - tmp_folder_len, "%08x", (int)getpid() + (int)(fio_rand64() & 0xAFFFFFFF)); - cluster_data.name[tmp_folder_len] = 0; - - /* remove if existing */ - unlink(cluster_data.name); - /* add cleanup callback */ - fio_state_callback_add(FIO_CALL_AT_EXIT, fio_cluster_cleanup, NULL); -} -#endif -/* ***************************************************************************** - * Cluster Protocol callbacks - **************************************************************************** */ - -static inline void fio_cluster_protocol_free(void *pr) { fio_free(pr); } - -static uint8_t fio_cluster_on_shutdown(intptr_t uuid, fio_protocol_s *pr_) { - cluster_pr_s *p = (cluster_pr_s *)pr_; - p->sender(fio_msg_internal_create(0, FIO_CLUSTER_MSG_SHUTDOWN, - (fio_str_info_s){.len = 0}, - (fio_str_info_s){.len = 0}, 0, 1), - -1); - return 255; - (void)pr_; - (void)uuid; -} - -static void fio_cluster_on_data(intptr_t uuid, fio_protocol_s *pr_) { - cluster_pr_s *c = (cluster_pr_s *)pr_; - ssize_t i = - fio_read(uuid, c->buffer + c->length, CLUSTER_READ_BUFFER - c->length); - if (i <= 0) - return; - c->length += i; - i = 0; - do { - if (!c->exp_channel && !c->exp_msg) { - if (c->length - i < 16) - break; - c->exp_channel = fio_str2u32(c->buffer + i) + 1; - c->exp_msg = fio_str2u32(c->buffer + i + 4) + 1; - c->type = fio_str2u32(c->buffer + i + 8); - c->filter = (int32_t)fio_str2u32(c->buffer + i + 12); - if (c->exp_channel) { - if (c->exp_channel >= (1024 * 1024 * 16) + 1) { - FIO_LOG_FATAL("(%d) cluster message name too long (16Mb limit): %u\n", - (int)getpid(), (unsigned int)c->exp_channel); - exit(1); - return; - } - } - if (c->exp_msg) { - if (c->exp_msg >= (1024 * 1024 * 64) + 1) { - FIO_LOG_FATAL("(%d) cluster message data too long (64Mb limit): %u\n", - (int)getpid(), (unsigned int)c->exp_msg); - exit(1); - return; - } - } - c->msg = fio_msg_internal_create( - c->filter, c->type, - (fio_str_info_s){.data = (char *)(c->msg + 1), - .len = c->exp_channel - 1}, - (fio_str_info_s){.data = ((char *)(c->msg + 1) + c->exp_channel + 1), - .len = c->exp_msg - 1}, - (int8_t)(c->type == FIO_CLUSTER_MSG_JSON || - c->type == FIO_CLUSTER_MSG_ROOT_JSON), - 0); - i += 16; - } - if (c->exp_channel) { - if (c->exp_channel + i > c->length) { - memcpy(c->msg->channel.data + - ((c->msg->channel.len + 1) - c->exp_channel), - (char *)c->buffer + i, (size_t)(c->length - i)); - c->exp_channel -= (c->length - i); - i = c->length; - break; - } else { - memcpy(c->msg->channel.data + - ((c->msg->channel.len + 1) - c->exp_channel), - (char *)c->buffer + i, (size_t)(c->exp_channel)); - i += c->exp_channel; - c->exp_channel = 0; - } - } - if (c->exp_msg) { - if (c->exp_msg + i > c->length) { - memcpy(c->msg->data.data + ((c->msg->data.len + 1) - c->exp_msg), - (char *)c->buffer + i, (size_t)(c->length - i)); - c->exp_msg -= (c->length - i); - i = c->length; - break; - } else { - memcpy(c->msg->data.data + ((c->msg->data.len + 1) - c->exp_msg), - (char *)c->buffer + i, (size_t)(c->exp_msg)); - i += c->exp_msg; - c->exp_msg = 0; - } - } - fio_postoffice_meta_update(c->msg); - c->handler(c); - fio_msg_internal_free(c->msg); - c->msg = NULL; - } while (c->length > i); - c->length -= i; - if (c->length && i) { - memmove(c->buffer, c->buffer + i, c->length); - } - (void)pr_; -} - -static void fio_cluster_ping(intptr_t uuid, fio_protocol_s *pr_) { - fio_msg_internal_s *m = fio_msg_internal_create( - 0, FIO_CLUSTER_MSG_PING, (fio_str_info_s){.len = 0}, - (fio_str_info_s){.len = 0}, 0, 1); - fio_msg_internal_send_dup(uuid, m); - fio_msg_internal_free(m); - (void)pr_; -} - -static void fio_cluster_on_close(intptr_t uuid, fio_protocol_s *pr_) { - cluster_pr_s *c = (cluster_pr_s *)pr_; - if (!fio_data->is_worker) { - /* a child was lost, respawning is handled elsewhere. */ - fio_lock(&cluster_data.lock); - FIO_LS_FOR(&cluster_data.clients, pos) { - if (pos->obj == (void *)uuid) { - fio_ls_remove(pos); - break; - } - } - fio_unlock(&cluster_data.lock); - } else if (fio_data->active) { - /* no shutdown message received - parent crashed. */ - if (c->type != FIO_CLUSTER_MSG_SHUTDOWN && fio_is_running()) { - FIO_LOG_FATAL("(%d) Parent Process crash detected!", (int)getpid()); - fio_state_callback_force(FIO_CALL_ON_PARENT_CRUSH); - fio_state_callback_clear(FIO_CALL_ON_PARENT_CRUSH); -#ifndef __MINGW32__ - fio_cluster_data_cleanup(1); -#endif - kill(getpid(), SIGINT); - } - } - if (c->msg) - fio_msg_internal_free(c->msg); - c->msg = NULL; - fio_sub_hash_free(&c->pubsub); - fio_cluster_protocol_free(c); - (void)uuid; -} - -static inline fio_protocol_s * -fio_cluster_protocol_alloc(intptr_t uuid, - void (*handler)(struct cluster_pr_s *pr), - void (*sender)(void *data, intptr_t auuid)) { - cluster_pr_s *p = fio_mmap(sizeof(*p)); - if (!p) { - FIO_LOG_FATAL("Cluster protocol allocation failed."); - exit(errno); - } - p->protocol = (fio_protocol_s){ - .ping = fio_cluster_ping, - .on_close = fio_cluster_on_close, - .on_shutdown = fio_cluster_on_shutdown, - .on_data = fio_cluster_on_data, - }; - p->uuid = uuid; - p->handler = handler; - p->sender = sender; - p->pubsub = (fio_sub_hash_s)FIO_SET_INIT; - p->patterns = (fio_sub_hash_s)FIO_SET_INIT; - p->lock = FIO_LOCK_INIT; - return &p->protocol; -} - -/* ***************************************************************************** - * Master (server) IPC Connections - **************************************************************************** */ -#ifndef __MINGW32__ -static void fio_cluster_server_sender(void *m_, intptr_t avoid_uuid) { - fio_msg_internal_s *m = m_; - fio_lock(&cluster_data.lock); - FIO_LS_FOR(&cluster_data.clients, pos) { - if ((intptr_t)pos->obj != -1) { - if ((intptr_t)pos->obj != avoid_uuid) { - fio_msg_internal_send_dup((intptr_t)pos->obj, m); - } - } - } - fio_unlock(&cluster_data.lock); - fio_msg_internal_free(m); -} - -static void fio_cluster_server_handler(struct cluster_pr_s *pr) { - /* what to do? */ - // fprintf(stderr, "-"); - switch ((fio_cluster_message_type_e)pr->type) { - - case FIO_CLUSTER_MSG_FORWARD: /* fallthrough */ - case FIO_CLUSTER_MSG_JSON: { - fio_cluster_server_sender(fio_msg_internal_dup(pr->msg), pr->uuid); - fio_publish2process(fio_msg_internal_dup(pr->msg)); - break; - } - - case FIO_CLUSTER_MSG_PUBSUB_SUB: { - subscription_s *s = - fio_subscribe(.on_message = fio_mock_on_message, .match = NULL, - .channel = pr->msg->channel); - fio_str_s tmp = FIO_STR_INIT_EXISTING( - pr->msg->channel.data, pr->msg->channel.len, 0); // don't free - fio_lock(&pr->lock); - fio_sub_hash_insert(&pr->pubsub, - FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len, - &fio_postoffice.pubsub, - &fio_postoffice.pubsub), - tmp, s, NULL); - fio_unlock(&pr->lock); - break; - } - case FIO_CLUSTER_MSG_PUBSUB_UNSUB: { - fio_str_s tmp = FIO_STR_INIT_EXISTING( - pr->msg->channel.data, pr->msg->channel.len, 0); // don't free - fio_lock(&pr->lock); - fio_sub_hash_remove(&pr->pubsub, - FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len, - &fio_postoffice.pubsub, - &fio_postoffice.pubsub), - tmp, NULL); - fio_unlock(&pr->lock); - break; - } - - case FIO_CLUSTER_MSG_PATTERN_SUB: { - uintptr_t match = fio_str2u64(pr->msg->data.data); - subscription_s *s = fio_subscribe(.on_message = fio_mock_on_message, - .match = (fio_match_fn)match, - .channel = pr->msg->channel); - fio_str_s tmp = FIO_STR_INIT_EXISTING( - pr->msg->channel.data, pr->msg->channel.len, 0); // don't free - fio_lock(&pr->lock); - fio_sub_hash_insert(&pr->patterns, - FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len, - &fio_postoffice.pubsub, - &fio_postoffice.pubsub), - tmp, s, NULL); - fio_unlock(&pr->lock); - break; - } - - case FIO_CLUSTER_MSG_PATTERN_UNSUB: { - fio_str_s tmp = FIO_STR_INIT_EXISTING( - pr->msg->channel.data, pr->msg->channel.len, 0); // don't free - fio_lock(&pr->lock); - fio_sub_hash_remove(&pr->patterns, - FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len, - &fio_postoffice.pubsub, - &fio_postoffice.pubsub), - tmp, NULL); - fio_unlock(&pr->lock); - break; - } - - case FIO_CLUSTER_MSG_ROOT_JSON: - pr->type = FIO_CLUSTER_MSG_JSON; /* fallthrough */ - case FIO_CLUSTER_MSG_ROOT: - fio_publish2process(fio_msg_internal_dup(pr->msg)); - break; - - case FIO_CLUSTER_MSG_SHUTDOWN: /* fallthrough */ - case FIO_CLUSTER_MSG_ERROR: /* fallthrough */ - case FIO_CLUSTER_MSG_PING: /* fallthrough */ - default: - break; - } -} - -/** Called when a ne client is available */ -static void fio_cluster_listen_accept(intptr_t uuid, fio_protocol_s *protocol) { - (void)protocol; - /* prevent `accept` backlog in parent */ - intptr_t client; - while ((client = fio_accept(uuid)) != -1) { - fio_attach(client, - fio_cluster_protocol_alloc(client, fio_cluster_server_handler, - fio_cluster_server_sender)); - fio_lock(&cluster_data.lock); - fio_ls_push(&cluster_data.clients, (void *)client); - fio_unlock(&cluster_data.lock); - } -} - -/** Called when the connection was closed, but will not run concurrently */ -static void fio_cluster_listen_on_close(intptr_t uuid, - fio_protocol_s *protocol) { - free(protocol); - cluster_data.uuid = -1; - if (fio_parent_pid() == getpid()) { -#if DEBUG - FIO_LOG_DEBUG("(%d) stopped listening for cluster connections", - (int)getpid()); -#endif - if (fio_data->active) - fio_stop(); - } - (void)uuid; -} - -static void fio_listen2cluster(void *ignore) { - /* this is called for each `fork`, but we only need this to run once. */ - fio_lock(&cluster_data.lock); - cluster_data.uuid = fio_socket(cluster_data.name, NULL, 1); - fio_unlock(&cluster_data.lock); - if (cluster_data.uuid < 0) { - FIO_LOG_FATAL("(facil.io cluster) failed to open cluster socket."); - perror(" check file permissions. errno:"); - exit(errno); - } - fio_protocol_s *p = malloc(sizeof(*p)); - FIO_ASSERT_ALLOC(p); - *p = (fio_protocol_s){ - .on_data = fio_cluster_listen_accept, - .on_shutdown = mock_on_shutdown_eternal, - .ping = mock_ping_eternal, - .on_close = fio_cluster_listen_on_close, - }; -#ifdef __MINGW32__ - FIO_LOG_DEBUG("(%d) Listening to cluster on above tcp socket.", (int)getpid()); -#else - FIO_LOG_DEBUG("(%d) Listening to cluster: %s", (int)getpid(), - cluster_data.name); -#endif - fio_attach(cluster_data.uuid, p); - (void)ignore; -} -#endif -/* ***************************************************************************** - * Worker (client) IPC connections - **************************************************************************** */ -#ifndef __MINGW32__ -static void fio_cluster_client_handler(struct cluster_pr_s *pr) { - /* what to do? */ - switch ((fio_cluster_message_type_e)pr->type) { - case FIO_CLUSTER_MSG_FORWARD: /* fallthrough */ - case FIO_CLUSTER_MSG_JSON: - fio_publish2process(fio_msg_internal_dup(pr->msg)); - break; - case FIO_CLUSTER_MSG_SHUTDOWN: - fio_stop(); - case FIO_CLUSTER_MSG_ERROR: /* fallthrough */ - case FIO_CLUSTER_MSG_PING: /* fallthrough */ - case FIO_CLUSTER_MSG_ROOT: /* fallthrough */ - case FIO_CLUSTER_MSG_ROOT_JSON: /* fallthrough */ - case FIO_CLUSTER_MSG_PUBSUB_SUB: /* fallthrough */ - case FIO_CLUSTER_MSG_PUBSUB_UNSUB: /* fallthrough */ - case FIO_CLUSTER_MSG_PATTERN_SUB: /* fallthrough */ - case FIO_CLUSTER_MSG_PATTERN_UNSUB: /* fallthrough */ - - default: - break; - } -} -static void fio_cluster_client_sender(void *m_, intptr_t ignr_) { - fio_msg_internal_s *m = m_; - if (!uuid_is_valid(cluster_data.uuid) && fio_data->active) { - /* delay message delivery until we have a vaild uuid */ - fio_defer_push_task((void (*)(void *, void *))fio_cluster_client_sender, m_, - (void *)ignr_); - return; - } - fio_msg_internal_send_dup(cluster_data.uuid, m); - fio_msg_internal_free(m); -} - -/** The address of the server we are connecting to. */ -// char *address; -/** The port on the server we are connecting to. */ -// char *port; -/** - * The `on_connect` callback should return a pointer to a protocol object - * that will handle any connection related events. - * - * Should either call `facil_attach` or close the connection. - */ -static void fio_cluster_on_connect(intptr_t uuid, void *udata) { - cluster_data.uuid = uuid; - - /* inform root about all existing channels */ - fio_lock(&fio_postoffice.pubsub.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.pubsub.channels, pos) { - if (!pos->hash) { - continue; - } - fio_cluster_inform_root_about_channel(pos->obj, 1); - } - fio_unlock(&fio_postoffice.pubsub.lock); - fio_lock(&fio_postoffice.patterns.lock); - FIO_SET_FOR_LOOP(&fio_postoffice.patterns.channels, pos) { - if (!pos->hash) { - continue; - } - fio_cluster_inform_root_about_channel(pos->obj, 1); - } - fio_unlock(&fio_postoffice.patterns.lock); - - fio_attach(uuid, fio_cluster_protocol_alloc(uuid, fio_cluster_client_handler, - fio_cluster_client_sender)); - (void)udata; -} -/** - * The `on_fail` is called when a socket fails to connect. The old sock UUID - * is passed along. - */ -static void fio_cluster_on_fail(intptr_t uuid, void *udata) { - FIO_LOG_FATAL("(facil.io) unknown cluster connection error"); - perror(" errno"); - kill(fio_parent_pid(), SIGINT); - fio_stop(); - // exit(errno ? errno : 1); - (void)udata; - (void)uuid; -} - -static void fio_connect2cluster(void *ignore) { - if (cluster_data.uuid) - fio_force_close(cluster_data.uuid); - cluster_data.uuid = 0; - /* this is called for each child, but not for single a process worker. */ - fio_connect(.address = cluster_data.name, .port = NULL, - .on_connect = fio_cluster_on_connect, - .on_fail = fio_cluster_on_fail); - (void)ignore; -} - -static void fio_send2cluster(fio_msg_internal_s *m) { - if (!fio_is_running()) { - FIO_LOG_ERROR("facio.io cluster inactive, can't send message."); - return; - } - if (fio_data->workers == 1) { - /* nowhere to send to */ - return; - } - if (fio_is_master()) { - fio_cluster_server_sender(fio_msg_internal_dup(m), -1); - } else { - fio_cluster_client_sender(fio_msg_internal_dup(m), -1); - } -} -#endif -/* ***************************************************************************** - * Propagation - **************************************************************************** */ -#ifndef __MINGW32__ -static inline void fio_cluster_inform_root_about_channel(channel_s *ch, - int add) { - if (!fio_data->is_worker || fio_data->workers == 1 || !cluster_data.uuid || - !ch) - return; - fio_str_info_s ch_name = {.data = ch->name, .len = ch->name_len}; - fio_str_info_s msg = {.data = NULL, .len = 0}; -#if DEBUG - FIO_LOG_DEBUG("(%d) informing root about: %s (%zu) msg type %d", - (int)getpid(), ch_name.data, ch_name.len, - (ch->match ? (add ? FIO_CLUSTER_MSG_PATTERN_SUB - : FIO_CLUSTER_MSG_PATTERN_UNSUB) - : (add ? FIO_CLUSTER_MSG_PUBSUB_SUB - : FIO_CLUSTER_MSG_PUBSUB_UNSUB))); -#endif - char buf[8] = {0}; - if (ch->match) { - fio_u2str64(buf, (uintptr_t)ch->match); - msg.data = buf; - msg.len = sizeof(ch->match); - } - - fio_cluster_client_sender( - fio_msg_internal_create(0, - (ch->match - ? (add ? FIO_CLUSTER_MSG_PATTERN_SUB - : FIO_CLUSTER_MSG_PATTERN_UNSUB) - : (add ? FIO_CLUSTER_MSG_PUBSUB_SUB - : FIO_CLUSTER_MSG_PUBSUB_UNSUB)), - ch_name, msg, 0, 1), - -1); -} -#endif -/* ***************************************************************************** - * Initialization - **************************************************************************** */ -#ifndef __MINGW32__ -static void fio_accept_after_fork(void *ignore) { - /* prevent `accept` backlog in parent */ -#ifndef __MINGW32__ - fio_cluster_listen_accept(cluster_data.uuid, NULL); -#endif - (void)ignore; -} - -static void fio_cluster_at_exit(void *ignore) { - /* unlock all */ - fio_pubsub_on_fork(); - /* clear subscriptions of all types */ - while (fio_ch_set_count(&fio_postoffice.patterns.channels)) { - channel_s *ch = fio_ch_set_last(&fio_postoffice.patterns.channels); - while (fio_ls_embd_any(&ch->subscriptions)) { - subscription_s *sub = - FIO_LS_EMBD_OBJ(subscription_s, node, ch->subscriptions.next); - fio_unsubscribe(sub); - } - fio_ch_set_pop(&fio_postoffice.patterns.channels); - } - - while (fio_ch_set_count(&fio_postoffice.pubsub.channels)) { - channel_s *ch = fio_ch_set_last(&fio_postoffice.pubsub.channels); - while (fio_ls_embd_any(&ch->subscriptions)) { - subscription_s *sub = - FIO_LS_EMBD_OBJ(subscription_s, node, ch->subscriptions.next); - fio_unsubscribe(sub); - } - fio_ch_set_pop(&fio_postoffice.pubsub.channels); - } - - while (fio_ch_set_count(&fio_postoffice.filters.channels)) { - channel_s *ch = fio_ch_set_last(&fio_postoffice.filters.channels); - while (fio_ls_embd_any(&ch->subscriptions)) { - subscription_s *sub = - FIO_LS_EMBD_OBJ(subscription_s, node, ch->subscriptions.next); - fio_unsubscribe(sub); - } - fio_ch_set_pop(&fio_postoffice.filters.channels); - } - fio_ch_set_free(&fio_postoffice.filters.channels); - fio_ch_set_free(&fio_postoffice.patterns.channels); - fio_ch_set_free(&fio_postoffice.pubsub.channels); - - /* clear engines */ - FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; - while (fio_engine_set_count(&fio_postoffice.engines.set)) { - fio_pubsub_detach(fio_engine_set_last(&fio_postoffice.engines.set)); - fio_engine_set_last(&fio_postoffice.engines.set); - } - fio_engine_set_free(&fio_postoffice.engines.set); - - /* clear meta hooks */ - fio_meta_ary_free(&fio_postoffice.meta.ary); - /* perform newly created tasks */ - fio_defer_perform(); - (void)ignore; -} -#endif - -static void fio_pubsub_initialize(void) { -#ifndef __MINGW32__ - fio_cluster_init(); - fio_state_callback_add(FIO_CALL_PRE_START, fio_listen2cluster, NULL); - fio_state_callback_add(FIO_CALL_IN_MASTER, fio_accept_after_fork, NULL); - fio_state_callback_add(FIO_CALL_IN_CHILD, fio_connect2cluster, NULL); - fio_state_callback_add(FIO_CALL_ON_FINISH, fio_cluster_cleanup, NULL); - fio_state_callback_add(FIO_CALL_AT_EXIT, fio_cluster_at_exit, NULL); -#endif -} - -/* ***************************************************************************** -Cluster forking handler -***************************************************************************** */ - -static void fio_pubsub_on_fork(void) { - fio_postoffice.filters.lock = FIO_LOCK_INIT; - fio_postoffice.pubsub.lock = FIO_LOCK_INIT; - fio_postoffice.patterns.lock = FIO_LOCK_INIT; - fio_postoffice.engines.lock = FIO_LOCK_INIT; - fio_postoffice.meta.lock = FIO_LOCK_INIT; - cluster_data.lock = FIO_LOCK_INIT; - cluster_data.uuid = 0; - FIO_SET_FOR_LOOP(&fio_postoffice.filters.channels, pos) { - if (!pos->hash) - continue; - pos->obj->lock = FIO_LOCK_INIT; - FIO_LS_EMBD_FOR(&pos->obj->subscriptions, n) { - FIO_LS_EMBD_OBJ(subscription_s, node, n)->lock = FIO_LOCK_INIT; - } - } - FIO_SET_FOR_LOOP(&fio_postoffice.pubsub.channels, pos) { - if (!pos->hash) - continue; - pos->obj->lock = FIO_LOCK_INIT; - FIO_LS_EMBD_FOR(&pos->obj->subscriptions, n) { - FIO_LS_EMBD_OBJ(subscription_s, node, n)->lock = FIO_LOCK_INIT; - } - } - FIO_SET_FOR_LOOP(&fio_postoffice.patterns.channels, pos) { - if (!pos->hash) - continue; - pos->obj->lock = FIO_LOCK_INIT; - FIO_LS_EMBD_FOR(&pos->obj->subscriptions, n) { - FIO_LS_EMBD_OBJ(subscription_s, node, n)->lock = FIO_LOCK_INIT; - } - } -} - -/* ***************************************************************************** - * External API - **************************************************************************** */ - -/** Signals children (or self) to shutdown) - NOT signal safe. */ -static void fio_cluster_signal_children(void) { - if (fio_parent_pid() != getpid()) { - fio_stop(); - return; - } -#ifndef __MINGW32__ - fio_cluster_server_sender(fio_msg_internal_create(0, FIO_CLUSTER_MSG_SHUTDOWN, - (fio_str_info_s){.len = 0}, - (fio_str_info_s){.len = 0}, - 0, 1), - -1); -#endif -} - -/* Sublime Text marker */ -void fio_publish___(fio_publish_args_s args); -/** - * Publishes a message to the relevant subscribers (if any). - * - * See `facil_publish_args_s` for details. - * - * By default the message is sent using the FIO_PUBSUB_CLUSTER engine (all - * processes, including the calling process). - * - * To limit the message only to other processes (exclude the calling process), - * use the FIO_PUBSUB_SIBLINGS engine. - * - * To limit the message only to the calling process, use the - * FIO_PUBSUB_PROCESS engine. - * - * To publish messages to the pub/sub layer, the `.filter` argument MUST be - * equal to 0 or missing. - */ -void fio_publish FIO_IGNORE_MACRO(fio_publish_args_s args) { - if (args.filter && !args.engine) { - args.engine = FIO_PUBSUB_CLUSTER; - } else if (!args.engine) { - args.engine = FIO_PUBSUB_DEFAULT; - } - fio_msg_internal_s *m = NULL; - switch ((uintptr_t)args.engine) { - case 0UL: /* fallthrough (missing default) */ - case 1UL: // ((uintptr_t)FIO_PUBSUB_CLUSTER): - m = fio_msg_internal_create( - args.filter, - (args.is_json ? FIO_CLUSTER_MSG_JSON : FIO_CLUSTER_MSG_FORWARD), - args.channel, args.message, args.is_json, 1); -#ifndef __MINGW32__ - fio_send2cluster(m); -#endif - fio_publish2process(m); - break; - case 2UL: // ((uintptr_t)FIO_PUBSUB_PROCESS): - m = fio_msg_internal_create(args.filter, 0, args.channel, args.message, - args.is_json, 1); - fio_publish2process(m); - break; - case 3UL: // ((uintptr_t)FIO_PUBSUB_SIBLINGS): - m = fio_msg_internal_create( - args.filter, - (args.is_json ? FIO_CLUSTER_MSG_JSON : FIO_CLUSTER_MSG_FORWARD), - args.channel, args.message, args.is_json, 1); -#ifndef __MINGW32__ - fio_send2cluster(m); -#endif - fio_msg_internal_free(m); - m = NULL; - break; - case 4UL: // ((uintptr_t)FIO_PUBSUB_ROOT): - m = fio_msg_internal_create( - args.filter, - (args.is_json ? FIO_CLUSTER_MSG_ROOT_JSON : FIO_CLUSTER_MSG_ROOT), - args.channel, args.message, args.is_json, 1); -#ifdef __MINGW32__ - fio_publish2process(m); -#else - if (fio_data->is_worker == 0 || fio_data->workers == 1) { - fio_publish2process(m); - } else { - fio_cluster_client_sender(m, -1); - } -#endif - break; - default: - if (args.filter != 0) { - FIO_LOG_ERROR("(pub/sub) pub/sub engines can only be used for " - "pub/sub messages (no filter)."); - return; - } - args.engine->publish(args.engine, args.channel, args.message, args.is_json); - } - return; -} - -/* ***************************************************************************** - * Glob Matching - **************************************************************************** */ - -/** A binary glob matching helper. Returns 1 on match, otherwise returns 0. */ -static int fio_glob_match(fio_str_info_s pat, fio_str_info_s ch) { - /* adapted and rewritten, with thankfulness, from the code at: - * https://github.com/opnfv/kvmfornfv/blob/master/kernel/lib/glob.c - * - * Original version's copyright: - * Copyright 2015 Open Platform for NFV Project, Inc. and its contributors - * Under the MIT license. - */ - - /* - * Backtrack to previous * on mismatch and retry starting one - * character later in the string. Because * matches all characters, - * there's never a need to backtrack multiple levels. - */ - uint8_t *back_pat = NULL, *back_str = (uint8_t *)ch.data; - size_t back_pat_len = 0, back_str_len = ch.len; - - /* - * Loop over each token (character or class) in pat, matching - * it against the remaining unmatched tail of str. Return false - * on mismatch, or true after matching the trailing nul bytes. - */ - while (ch.len) { - uint8_t c = *(uint8_t *)ch.data++; - uint8_t d = *(uint8_t *)pat.data++; - ch.len--; - pat.len--; - - switch (d) { - case '?': /* Wildcard: anything goes */ - break; - - case '*': /* Any-length wildcard */ - if (!pat.len) /* Optimize trailing * case */ - return 1; - back_pat = (uint8_t *)pat.data; - back_pat_len = pat.len; - back_str = (uint8_t *)--ch.data; /* Allow zero-length match */ - back_str_len = ++ch.len; - break; - - case '[': { /* Character class */ - uint8_t match = 0, inverted = (*(uint8_t *)pat.data == '^'); - uint8_t *cls = (uint8_t *)pat.data + inverted; - uint8_t a = *cls++; - - /* - * Iterate over each span in the character class. - * A span is either a single character a, or a - * range a-b. The first span may begin with ']'. - */ - do { - uint8_t b = a; - - if (cls[0] == '-' && cls[1] != ']') { - b = cls[1]; - - cls += 2; - if (a > b) { - uint8_t tmp = a; - a = b; - b = tmp; - } - } - match |= (a <= c && c <= b); - } while ((a = *cls++) != ']'); - - if (match == inverted) - goto backtrack; - pat.len -= cls - (uint8_t *)pat.data; - pat.data = (char *)cls; - - } break; - case '\\': - d = *(uint8_t *)pat.data++; - pat.len--; - /* fallthrough */ - default: /* Literal character */ - if (c == d) - break; - backtrack: - if (!back_pat) - return 0; /* No point continuing */ - /* Try again from last *, one character later in str. */ - pat.data = (char *)back_pat; - ch.data = (char *)++back_str; - ch.len = --back_str_len; - pat.len = back_pat_len; - } - } - return !ch.len && !pat.len; -} - -fio_match_fn FIO_MATCH_GLOB = fio_glob_match; - -#else /* FIO_PUBSUB_SUPPORT */ - -static void fio_pubsub_on_fork(void) {} -static void fio_cluster_init(void) {} -static void fio_cluster_signal_children(void) {} - -#endif /* FIO_PUBSUB_SUPPORT */ - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - - - - - - - - Memory Allocator Details & Implementation - - - - - - - - - - - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Allocator default settings -***************************************************************************** */ - -/* doun't change these */ -#undef FIO_MEMORY_BLOCK_SLICES -#undef FIO_MEMORY_BLOCK_HEADER_SIZE -#undef FIO_MEMORY_BLOCK_START_POS -#undef FIO_MEMORY_MAX_SLICES_PER_BLOCK -#undef FIO_MEMORY_BLOCK_MASK - -/* The number of blocks pre-allocated each system call, 256 ==8Mb */ -#ifndef FIO_MEMORY_BLOCKS_PER_ALLOCATION -#define FIO_MEMORY_BLOCKS_PER_ALLOCATION 256 -#endif - -#define FIO_MEMORY_BLOCK_MASK (FIO_MEMORY_BLOCK_SIZE - 1) /* 0b0...1... */ - -#define FIO_MEMORY_BLOCK_SLICES (FIO_MEMORY_BLOCK_SIZE >> 4) /* 16B slices */ - -/* must be divisable by 16 bytes, bigger than min(sizeof(block_s), 16) */ -#define FIO_MEMORY_BLOCK_HEADER_SIZE 32 - -/* allocation counter position (start) */ -#define FIO_MEMORY_BLOCK_START_POS (FIO_MEMORY_BLOCK_HEADER_SIZE >> 4) - -#define FIO_MEMORY_MAX_SLICES_PER_BLOCK \ - (FIO_MEMORY_BLOCK_SLICES - FIO_MEMORY_BLOCK_START_POS) - -/* ***************************************************************************** -FIO_FORCE_MALLOC handler -***************************************************************************** */ - -#if FIO_FORCE_MALLOC - -void *fio_malloc(size_t size) { return calloc(size, 1); } - -void *fio_calloc(size_t size_per_unit, size_t unit_count) { - return calloc(size_per_unit, unit_count); -} - -void fio_free(void *ptr) { free(ptr); } - -void *fio_realloc(void *ptr, size_t new_size) { - return realloc((ptr), (new_size)); -} - -void *fio_realloc2(void *ptr, size_t new_size, size_t copy_length) { - return realloc((ptr), (new_size)); - (void)copy_length; -} - -void *fio_mmap(size_t size) { return calloc(size, 1); } - -void fio_malloc_after_fork(void) {} -void fio_mem_destroy(void) {} -void fio_mem_init(void) {} - -#else - -/* ***************************************************************************** -Memory Copying by 16 byte units -***************************************************************************** */ - -/** used internally, only when memory addresses are known to be aligned */ -static inline void fio_memcpy(void *__restrict dest_, void *__restrict src_, - size_t units) { -#if __SIZEOF_INT128__ == 9 /* a 128bit type exists... but tests favor 64bit */ - register __uint128_t *dest = dest_; - register __uint128_t *src = src_; -#elif SIZE_MAX == 0xFFFFFFFFFFFFFFFF /* 64 bit size_t */ - register size_t *dest = dest_; - register size_t *src = src_; - units = units << 1; -#elif SIZE_MAX == 0xFFFFFFFF /* 32 bit size_t */ - register size_t *dest = dest_; - register size_t *src = src_; - units = units << 2; -#else /* unknow... assume 16 bit? */ - register size_t *dest = dest_; - register size_t *src = src_; - units = units << 3; -#endif - while (units >= 16) { /* unroll loop */ - dest[0] = src[0]; - dest[1] = src[1]; - dest[2] = src[2]; - dest[3] = src[3]; - dest[4] = src[4]; - dest[5] = src[5]; - dest[6] = src[6]; - dest[7] = src[7]; - dest[8] = src[8]; - dest[9] = src[9]; - dest[10] = src[10]; - dest[11] = src[11]; - dest[12] = src[12]; - dest[13] = src[13]; - dest[14] = src[14]; - dest[15] = src[15]; - dest += 16; - src += 16; - units -= 16; - } - switch (units) { - case 15: - *(dest++) = *(src++); /* fallthrough */ - case 14: - *(dest++) = *(src++); /* fallthrough */ - case 13: - *(dest++) = *(src++); /* fallthrough */ - case 12: - *(dest++) = *(src++); /* fallthrough */ - case 11: - *(dest++) = *(src++); /* fallthrough */ - case 10: - *(dest++) = *(src++); /* fallthrough */ - case 9: - *(dest++) = *(src++); /* fallthrough */ - case 8: - *(dest++) = *(src++); /* fallthrough */ - case 7: - *(dest++) = *(src++); /* fallthrough */ - case 6: - *(dest++) = *(src++); /* fallthrough */ - case 5: - *(dest++) = *(src++); /* fallthrough */ - case 4: - *(dest++) = *(src++); /* fallthrough */ - case 3: - *(dest++) = *(src++); /* fallthrough */ - case 2: - *(dest++) = *(src++); /* fallthrough */ - case 1: - *(dest++) = *(src++); - } -} - -/* ***************************************************************************** -System Memory wrappers -***************************************************************************** */ - -/* - * allocates memory using `mmap`, but enforces block size alignment. - * requires page aligned `len`. - * - * `align_shift` is used to move the memory page alignment to allow for a single - * page allocation header. align_shift MUST be either 0 (normal) or 1 (single - * page header). Other values might cause errors. - */ -static inline void *sys_alloc(size_t len, uint8_t is_indi) { - void *result; - static void *next_alloc = NULL; -/* hope for the best? */ -#ifdef MAP_ALIGNED - result = - mmap(next_alloc, len, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(FIO_MEMORY_BLOCK_SIZE_LOG), - -1, 0); -#else - result = mmap(next_alloc, len, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); -#endif - if (result == MAP_FAILED) - return NULL; - if (((uintptr_t)result & FIO_MEMORY_BLOCK_MASK)) { - munmap(result, len); - result = mmap(NULL, len + FIO_MEMORY_BLOCK_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (result == MAP_FAILED) { - return NULL; - } - const uintptr_t offset = - (FIO_MEMORY_BLOCK_SIZE - ((uintptr_t)result & FIO_MEMORY_BLOCK_MASK)); - if (offset) { - munmap(result, offset); - result = (void *)((uintptr_t)result + offset); - } - munmap((void *)((uintptr_t)result + len), FIO_MEMORY_BLOCK_SIZE - offset); - } - if (is_indi == - 0) /* advance by a block's allocation size for next allocation */ - next_alloc = - (void *)((uintptr_t)result + - (FIO_MEMORY_BLOCK_SIZE * (FIO_MEMORY_BLOCKS_PER_ALLOCATION))); - else /* add 1TB for realloc */ - next_alloc = (void *)((uintptr_t)result + (is_indi * ((uintptr_t)1 << 30))); - return result; -} - -/* frees memory using `munmap`. requires exact, page aligned, `len` */ -static inline void sys_free(void *mem, size_t len) { munmap(mem, len); } - -static void *sys_realloc(void *mem, size_t prev_len, size_t new_len) { - if (new_len > prev_len) { - void *result; -#if defined(__linux__) - result = mremap(mem, prev_len, new_len, 0); - if (result != MAP_FAILED) - return result; -#endif - result = mmap((void *)((uintptr_t)mem + prev_len), new_len - prev_len, - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (result == (void *)((uintptr_t)mem + prev_len)) { - result = mem; - } else { - /* copy and free */ - munmap(result, new_len - prev_len); /* free the failed attempt */ - result = sys_alloc(new_len, 1); /* allocate new memory */ - if (!result) { - return NULL; - } - fio_memcpy(result, mem, prev_len >> 4); /* copy data */ - // memcpy(result, mem, prev_len); - munmap(mem, prev_len); /* free original memory */ - } - return result; - } - if (new_len + 4096 < prev_len) /* more than a single dangling page */ - munmap((void *)((uintptr_t)mem + new_len), prev_len - new_len); - return mem; -} - -/** Rounds up any size to the nearest page alignment (assumes 4096 bytes per - * page) */ -static inline size_t sys_round_size(size_t size) { - return (size & (~4095)) + (4096 * (!!(size & 4095))); -} - -/* ***************************************************************************** -Data Types -***************************************************************************** */ - -/* The basic block header. Starts a 32Kib memory block */ -typedef struct block_s block_s; - -struct block_s { - block_s *parent; /* REQUIRED, root == point to self */ - uint16_t ref; /* reference count (per memory page) */ - uint16_t pos; /* position into the block */ - uint16_t max; /* available memory count */ - uint16_t root_ref; /* root reference memory padding */ -}; - -typedef struct block_node_s block_node_s; -struct block_node_s { - block_s dont_touch; /* prevent block internal data from being corrupted */ - fio_ls_embd_s node; /* next block */ -}; - -/* a per-CPU core "arena" for memory allocations */ -typedef struct { - block_s *block; - fio_lock_i lock; -} arena_s; - -/* The memory allocators persistent state */ -static struct { - fio_ls_embd_s available; /* free list for memory blocks */ - // intptr_t count; /* free list counter */ - size_t cores; /* the number of detected CPU cores*/ - fio_lock_i lock; /* a global lock */ - uint8_t forked; /* a forked collection indicator. */ -} memory = { - .cores = 1, - .lock = FIO_LOCK_INIT, - .available = FIO_LS_INIT(memory.available), -}; - -/* The per-CPU arena array. */ -static arena_s *arenas; - -/* The per-CPU arena array. */ -static long double on_malloc_zero; - -#if DEBUG -/* The per-CPU arena array. */ -static size_t fio_mem_block_count_max; -/* The per-CPU arena array. */ -static size_t fio_mem_block_count; -#define FIO_MEMORY_ON_BLOCK_ALLOC() \ - do { \ - fio_atomic_add(&fio_mem_block_count, 1); \ - if (fio_mem_block_count > fio_mem_block_count_max) \ - fio_mem_block_count_max = fio_mem_block_count; \ - } while (0) -#define FIO_MEMORY_ON_BLOCK_FREE() \ - do { \ - fio_atomic_sub(&fio_mem_block_count, 1); \ - } while (0) -#define FIO_MEMORY_PRINT_BLOCK_STAT() \ - FIO_LOG_INFO( \ - "(fio) Total memory blocks allocated before cleanup %zu\n" \ - " Maximum memory blocks allocated at a single time %zu\n", \ - fio_mem_block_count, fio_mem_block_count_max) -#define FIO_MEMORY_PRINT_BLOCK_STAT_END() \ - FIO_LOG_INFO("(fio) Total memory blocks allocated " \ - "after cleanup (possible leak) %zu\n", \ - fio_mem_block_count) -#else -#define FIO_MEMORY_ON_BLOCK_ALLOC() -#define FIO_MEMORY_ON_BLOCK_FREE() -#define FIO_MEMORY_PRINT_BLOCK_STAT() -#define FIO_MEMORY_PRINT_BLOCK_STAT_END() -#endif -/* ***************************************************************************** -Per-CPU Arena management -***************************************************************************** */ - -/* returned a locked arena. Attempts the preffered arena first. */ -static inline arena_s *arena_lock(arena_s *preffered) { - if (!preffered) - preffered = arenas; - if (!fio_trylock(&preffered->lock)) - return preffered; - do { - arena_s *arena = preffered; - for (size_t i = (size_t)(arena - arenas); i < memory.cores; ++i) { - if ((preffered == arenas || arena != preffered) && - !fio_trylock(&arena->lock)) - return arena; - ++arena; - } - if (preffered == arenas) - fio_reschedule_thread(); - preffered = arenas; - } while (1); -} - -static pthread_key_t arena_last_used_key; -static pthread_once_t arena_last_used_once; -static void init_arena_last_used_key(void) { - pthread_key_create(&arena_last_used_key, NULL); -} - -static void arena_enter(void) { - pthread_once(&arena_last_used_once, init_arena_last_used_key); - arena_s *arena_last_used = pthread_getspecific(arena_last_used_key); - arena_last_used = arena_lock(arena_last_used); - pthread_setspecific(arena_last_used_key, arena_last_used); -} - -static inline void arena_exit(void) { - pthread_once(&arena_last_used_once, init_arena_last_used_key); - arena_s *arena_last_used = pthread_getspecific(arena_last_used_key); - fio_unlock(&arena_last_used->lock); -} - -/** Clears any memory locks, in case of a system call to `fork`. */ -void fio_malloc_after_fork(void) { - pthread_once(&arena_last_used_once, init_arena_last_used_key); - pthread_setspecific(arena_last_used_key, NULL); - if (!arenas) { - return; - } - memory.lock = FIO_LOCK_INIT; - memory.forked = 1; - for (size_t i = 0; i < memory.cores; ++i) { - arenas[i].lock = FIO_LOCK_INIT; - } -} - -/* ***************************************************************************** -Block management / allocation -***************************************************************************** */ - -static inline void block_init_root(block_s *blk, block_s *parent) { - *blk = (block_s){ - .parent = parent, - .ref = 1, - .pos = FIO_MEMORY_BLOCK_START_POS, - .root_ref = 1, - }; -} - -/* intializes the block header for an available block of memory. */ -static inline void block_init(block_s *blk) { - /* initialization shouldn't effect `parent` or `root_ref`*/ - blk->ref = 1; - blk->pos = FIO_MEMORY_BLOCK_START_POS; - /* zero out linked list memory (everything else is already zero) */ - ((block_node_s *)blk)->node.next = NULL; - ((block_node_s *)blk)->node.prev = NULL; - /* bump parent reference count */ - fio_atomic_add(&blk->parent->root_ref, 1); -} - -/* intializes the block header for an available block of memory. */ -static inline void block_free(block_s *blk) { - if (fio_atomic_sub(&blk->ref, 1)) - return; - - memset(blk + 1, 0, (FIO_MEMORY_BLOCK_SIZE - sizeof(*blk))); - fio_lock(&memory.lock); - fio_ls_embd_push(&memory.available, &((block_node_s *)blk)->node); - - blk = blk->parent; - - if (fio_atomic_sub(&blk->root_ref, 1)) { - fio_unlock(&memory.lock); - return; - } - // fio_unlock(&memory.lock); - // return; - - /* remove all of the root block's children (slices) from the memory pool */ - for (size_t i = 0; i < FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++i) { - block_node_s *pos = - (block_node_s *)((uintptr_t)blk + (i * FIO_MEMORY_BLOCK_SIZE)); - fio_ls_embd_remove(&pos->node); - } - - fio_unlock(&memory.lock); - sys_free(blk, FIO_MEMORY_BLOCK_SIZE * FIO_MEMORY_BLOCKS_PER_ALLOCATION); - FIO_LOG_DEBUG("memory allocator returned %p to the system", (void *)blk); - FIO_MEMORY_ON_BLOCK_FREE(); -} - -/* intializes the block header for an available block of memory. */ -static inline block_s *block_new(void) { - block_s *blk = NULL; - - fio_lock(&memory.lock); - blk = (block_s *)fio_ls_embd_pop(&memory.available); - if (blk) { - blk = (block_s *)FIO_LS_EMBD_OBJ(block_node_s, node, blk); - FIO_ASSERT(((uintptr_t)blk & FIO_MEMORY_BLOCK_MASK) == 0, - "Memory allocator error! double `fio_free`?\n"); - block_init(blk); /* must be performed within lock */ - fio_unlock(&memory.lock); - return blk; - } - /* collect memory from the system */ - blk = sys_alloc(FIO_MEMORY_BLOCK_SIZE * FIO_MEMORY_BLOCKS_PER_ALLOCATION, 0); - if (!blk) { - fio_unlock(&memory.lock); - return NULL; - } - FIO_LOG_DEBUG("memory allocator allocated %p from the system", (void *)blk); - FIO_MEMORY_ON_BLOCK_ALLOC(); - block_init_root(blk, blk); - /* the extra memory goes into the memory pool. initialize + linke-list. */ - block_node_s *tmp = (block_node_s *)blk; - for (int i = 1; i < FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++i) { - tmp = (block_node_s *)((uintptr_t)tmp + FIO_MEMORY_BLOCK_SIZE); - block_init_root((block_s *)tmp, blk); - fio_ls_embd_push(&memory.available, &tmp->node); - } - fio_unlock(&memory.lock); - /* return the root block (which isn't in the memory pool). */ - return blk; -} - -/* allocates memory from within a block - called within an arena's lock */ -static inline void *block_slice(uint16_t units) { - pthread_once(&arena_last_used_once, init_arena_last_used_key); - arena_s *arena_last_used = pthread_getspecific(arena_last_used_key); - block_s *blk = arena_last_used->block; - if (!blk) { - /* arena is empty */ - blk = block_new(); - arena_last_used->block = blk; - } else if (blk->pos + units > FIO_MEMORY_MAX_SLICES_PER_BLOCK) { - /* not enough memory in the block - rotate */ - block_free(blk); - blk = block_new(); - arena_last_used->block = blk; - } - if (!blk) { - /* no system memory available? */ - errno = ENOMEM; - return NULL; - } - /* slice block starting at blk->pos and increase reference count */ - const void *mem = (void *)((uintptr_t)blk + ((uintptr_t)blk->pos << 4)); - fio_atomic_add(&blk->ref, 1); - blk->pos += units; - if (blk->pos >= FIO_MEMORY_MAX_SLICES_PER_BLOCK) { - /* ... the block was fully utilized, clear arena */ - block_free(blk); - arena_last_used->block = NULL; - } - return (void *)mem; -} - -/* handle's a bock's reference count - called without a lock */ -static inline void block_slice_free(void *mem) { - /* locate block boundary */ - block_s *blk = (block_s *)((uintptr_t)mem & (~FIO_MEMORY_BLOCK_MASK)); - block_free(blk); -} - -/* ***************************************************************************** -Non-Block allocations (direct from the system) -***************************************************************************** */ - -/* allocates directly from the system adding size header - no lock required. */ -static inline void *big_alloc(size_t size) { - size = sys_round_size(size + 16); - size_t *mem = sys_alloc(size, 1); - if (!mem) - goto error; - *mem = size; - return (void *)(((uintptr_t)mem) + 16); -error: - return NULL; -} - -/* reads size header and frees memory back to the system */ -static inline void big_free(void *ptr) { - size_t *mem = (void *)(((uintptr_t)ptr) - 16); - sys_free(mem, *mem); -} - -/* reallocates memory using the system, resetting the size header */ -static inline void *big_realloc(void *ptr, size_t new_size) { - size_t *mem = (void *)(((uintptr_t)ptr) - 16); - new_size = sys_round_size(new_size + 16); - mem = sys_realloc(mem, *mem, new_size); - if (!mem) - goto error; - *mem = new_size; - return (void *)(((uintptr_t)mem) + 16); -error: - return NULL; -} - -/* ***************************************************************************** -Allocator Initialization (initialize arenas and allocate a block for each CPU) -***************************************************************************** */ - -#if DEBUG -void fio_memory_dump_missing(void) { - fprintf(stderr, "\n ==== Attempting Memory Dump (will crash) ====\n"); - if (fio_ls_embd_is_empty(&memory.available)) { - fprintf(stderr, "- Memory dump attempt canceled\n"); - return; - } - block_node_s *smallest = - FIO_LS_EMBD_OBJ(block_node_s, node, memory.available.next); - FIO_LS_EMBD_FOR(&memory.available, node) { - block_node_s *tmp = FIO_LS_EMBD_OBJ(block_node_s, node, node); - if (smallest > tmp) - smallest = tmp; - } - - for (size_t i = 0; - i < FIO_MEMORY_BLOCK_SIZE * FIO_MEMORY_BLOCKS_PER_ALLOCATION; ++i) { - if ((((uintptr_t)smallest + i) & FIO_MEMORY_BLOCK_MASK) == 0) { - i += 32; - fprintf(stderr, "---block jump---\n"); - continue; - } - if (((char *)smallest)[i]) - fprintf(stderr, "%c", ((char *)smallest)[i]); - } -} -#else -#define fio_memory_dump_missing() -#endif - -static void fio_mem_init(void) { - if (arenas) - return; - - ssize_t cpu_count = 0; -#ifdef _SC_NPROCESSORS_ONLN - cpu_count = sysconf(_SC_NPROCESSORS_ONLN); -#elif defined(__MINGW32__) - SYSTEM_INFO sys_info; - GetSystemInfo(&sys_info); - cpu_count = sys_info.dwNumberOfProcessors; -#else -#warning Dynamic CPU core count is unavailable - assuming 8 cores for memory allocation pools. -#endif - if (cpu_count <= 0) - cpu_count = 8; - memory.cores = cpu_count; - arenas = big_alloc(sizeof(*arenas) * cpu_count); - FIO_ASSERT_ALLOC(arenas); - block_free(block_new()); -#ifndef __MINGW32__ - pthread_atfork(NULL, NULL, fio_malloc_after_fork); -#endif -} - -static void fio_mem_destroy(void) { - if (!arenas) - return; - - FIO_MEMORY_PRINT_BLOCK_STAT(); - - for (size_t i = 0; i < memory.cores; ++i) { - if (arenas[i].block) - block_free(arenas[i].block); - arenas[i].block = NULL; - } - if (!memory.forked && fio_ls_embd_any(&memory.available)) { - FIO_LOG_WARNING("facil.io detected memory traces remaining after cleanup" - " - memory leak?"); - FIO_MEMORY_PRINT_BLOCK_STAT_END(); - size_t count = 0; - FIO_LS_EMBD_FOR(&memory.available, node) { ++count; } - FIO_LOG_DEBUG("Memory blocks in pool: %zu (%zu blocks per allocation).", - count, (size_t)FIO_MEMORY_BLOCKS_PER_ALLOCATION); -#if FIO_MEM_DUMP - fio_memory_dump_missing(); -#endif - } - big_free(arenas); - arenas = NULL; -} -/* ***************************************************************************** -Memory allocation / deacclocation API -***************************************************************************** */ - -void *fio_malloc(size_t size) { -#if FIO_OVERRIDE_MALLOC - if (!arenas) - fio_mem_init(); -#endif - if (!size) { - /* changed behavior prevents "allocation failed" test for `malloc(0)` */ - return (void *)(&on_malloc_zero); - } - if (size >= FIO_MEMORY_BLOCK_ALLOC_LIMIT) { - /* system allocation - must be block aligned */ - // FIO_LOG_WARNING("fio_malloc re-routed to mmap - big allocation"); - return big_alloc(size); - } - /* ceiling for 16 byte alignement, translated to 16 byte units */ - size = (size >> 4) + (!!(size & 15)); - arena_enter(); - void *mem = block_slice(size); - arena_exit(); - return mem; -} - -void *fio_calloc(size_t size, size_t count) { - return fio_malloc(size * count); // memory is pre-initialized by mmap or pool. -} - -void fio_free(void *ptr) { - if (!ptr || ptr == (void *)&on_malloc_zero) - return; - if (((uintptr_t)ptr & FIO_MEMORY_BLOCK_MASK) == 16) { - /* big allocation - direct from the system */ - big_free(ptr); - return; - } - /* allocated within block */ - block_slice_free(ptr); -} - -/** - * Re-allocates memory. An attept to avoid copying the data is made only for big - * memory allocations. - * - * This variation is slightly faster as it might copy less data - */ -void *fio_realloc2(void *ptr, size_t new_size, size_t copy_length) { - if (!ptr || ptr == (void *)&on_malloc_zero) { - return fio_malloc(new_size); - } - if (!new_size) { - goto zero_size; - } - if (((uintptr_t)ptr & FIO_MEMORY_BLOCK_MASK) == 16) { - /* big reallocation - direct from the system */ - return big_realloc(ptr, new_size); - } - /* allocated within block - don't even try to expand the allocation */ - /* ceiling for 16 byte alignement, translated to 16 byte units */ - void *new_mem = fio_malloc(new_size); - if (!new_mem) - return NULL; - new_size = ((new_size >> 4) + (!!(new_size & 15))); - copy_length = ((copy_length >> 4) + (!!(copy_length & 15))); - fio_memcpy(new_mem, ptr, copy_length > new_size ? new_size : copy_length); - - block_slice_free(ptr); - return new_mem; -zero_size: - fio_free(ptr); - return fio_malloc(0); -} - -void *fio_realloc(void *ptr, size_t new_size) { - const size_t max_old = - FIO_MEMORY_BLOCK_SIZE - ((uintptr_t)ptr & FIO_MEMORY_BLOCK_MASK); - return fio_realloc2(ptr, new_size, max_old); -} - -/** - * Allocates memory directly using `mmap`, this is prefered for larger objects - * that have a long lifetime. - * - * `fio_free` can be used for deallocating the memory. - */ -void *fio_mmap(size_t size) { - if (!size) { - return NULL; - } - return big_alloc(size); -} - -/* ***************************************************************************** -FIO_OVERRIDE_MALLOC - override glibc / library malloc -***************************************************************************** */ -#if FIO_OVERRIDE_MALLOC -void *malloc(size_t size) { return fio_malloc(size); } -void *calloc(size_t size, size_t count) { return fio_calloc(size, count); } -void free(void *ptr) { fio_free(ptr); } -void *realloc(void *ptr, size_t new_size) { return fio_realloc(ptr, new_size); } -#endif - -#endif - -/* ***************************************************************************** - - - - - - - - Random Generator Functions - - - - - - - -***************************************************************************** */ - -static pthread_key_t s_key; -static pthread_key_t c_key; -static pthread_once_t s_c_once = PTHREAD_ONCE_INIT; -static void init_s_c_key(void) { - pthread_key_create(&s_key, free); - pthread_key_create(&c_key, free); -} -static void init_s_c_ptr(void) { - uint64_t *s = malloc(sizeof(uint64_t) * 2); - FIO_ASSERT_ALLOC(s); - memset(s, 0, sizeof(uint64_t) * 2); - uint16_t *c = malloc(sizeof(uint16_t)); - FIO_ASSERT_ALLOC(c); - memset(c, 0, sizeof(uint16_t)); - pthread_setspecific(s_key, s); - pthread_setspecific(c_key, c); -} -/* tested for randomness using code from: http://xoshiro.di.unimi.it/hwd.php */ -uint64_t fio_rand64(void) { - /* modeled after xoroshiro128+, by David Blackman and Sebastiano Vigna */ - pthread_once(&s_c_once, init_s_c_key); - uint64_t *s = (uint64_t *)pthread_getspecific(s_key); /* random state */ - if (!s) { - init_s_c_ptr(); - s = (uint64_t *)pthread_getspecific(s_key); - } - uint16_t *c = (uint16_t *)pthread_getspecific(c_key); /* seed counter */ - const uint64_t P[] = {0x37701261ED6C16C7ULL, 0x764DBBB75F3B3E0DULL}; - if (*c++ == 0) { - /* re-seed state every 65,536 requests */ -#ifdef RUSAGE_SELF - struct rusage rusage; - getrusage(RUSAGE_SELF, &rusage); - s[0] = fio_risky_hash(&rusage, sizeof(rusage), s[0]); - s[1] = fio_risky_hash(&rusage, sizeof(rusage), s[0]); -#else - struct timespec clk; - clock_gettime(CLOCK_REALTIME, &clk); - s[0] = fio_risky_hash(&clk, sizeof(clk), s[0]); - s[1] = fio_risky_hash(&clk, sizeof(clk), s[0]); -#endif - } - s[0] += fio_lrot64(s[0], 33) * P[0]; - s[1] += fio_lrot64(s[1], 33) * P[1]; - return fio_lrot64(s[0], 31) + fio_lrot64(s[1], 29); -} - -/* copies 64 bits of randomness (8 bytes) repeatedly... */ -void fio_rand_bytes(void *data_, size_t len) { - if (!data_ || !len) - return; - uint8_t *data = data_; - /* unroll 32 bytes / 256 bit writes */ - for (size_t i = (len >> 5); i; --i) { - const uint64_t t0 = fio_rand64(); - const uint64_t t1 = fio_rand64(); - const uint64_t t2 = fio_rand64(); - const uint64_t t3 = fio_rand64(); - fio_u2str64(data, t0); - fio_u2str64(data + 8, t1); - fio_u2str64(data + 16, t2); - fio_u2str64(data + 24, t3); - data += 32; - } - uint64_t tmp; - /* 64 bit steps */ - switch (len & 24) { - case 24: - tmp = fio_rand64(); - fio_u2str64(data + 16, tmp); - /* fallthrough */ - case 16: - tmp = fio_rand64(); - fio_u2str64(data + 8, tmp); - /* fallthrough */ - case 8: - tmp = fio_rand64(); - fio_u2str64(data, tmp); - data += len & 24; - } - if ((len & 7)) { - tmp = fio_rand64(); - /* leftover bytes */ - switch ((len & 7)) { - case 7: - data[6] = (tmp >> 8) & 0xFF; - /* fallthrough */ - case 6: - data[5] = (tmp >> 16) & 0xFF; - /* fallthrough */ - case 5: - data[4] = (tmp >> 24) & 0xFF; - /* fallthrough */ - case 4: - data[3] = (tmp >> 32) & 0xFF; - /* fallthrough */ - case 3: - data[2] = (tmp >> 40) & 0xFF; - /* fallthrough */ - case 2: - data[1] = (tmp >> 48) & 0xFF; - /* fallthrough */ - case 1: - data[0] = (tmp >> 56) & 0xFF; - } - } -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - Hash Functions and Base64 - - SipHash / SHA-1 / SHA-2 / Base64 / Hex encoding - - - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -SipHash -***************************************************************************** */ - -#if __BIG_ENDIAN__ /* SipHash is Little Endian */ -#define sip_local64(i) fio_bswap64((i)) -#else -#define sip_local64(i) (i) -#endif - -static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x, - size_t y, uint64_t key1, uint64_t key2) { - /* initialize the 4 words */ - uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL) ^ key1; - uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL) ^ key2; - uint64_t v2 = (0x0706050403020100ULL ^ 0x6c7967656e657261ULL) ^ key1; - uint64_t v3 = (0x0f0e0d0c0b0a0908ULL ^ 0x7465646279746573ULL) ^ key2; - const uint8_t *w8 = data; - uint8_t len_mod = len & 255; - union { - uint64_t i; - uint8_t str[8]; - } word; - -#define hash_map_SipRound \ - do { \ - v2 += v3; \ - v3 = fio_lrot64(v3, 16) ^ v2; \ - v0 += v1; \ - v1 = fio_lrot64(v1, 13) ^ v0; \ - v0 = fio_lrot64(v0, 32); \ - v2 += v1; \ - v0 += v3; \ - v1 = fio_lrot64(v1, 17) ^ v2; \ - v3 = fio_lrot64(v3, 21) ^ v0; \ - v2 = fio_lrot64(v2, 32); \ - } while (0); - - while (len >= 8) { - word.i = sip_local64(fio_str2u64(w8)); - v3 ^= word.i; - /* Sip Rounds */ - for (size_t i = 0; i < x; ++i) { - hash_map_SipRound; - } - v0 ^= word.i; - w8 += 8; - len -= 8; - } - word.i = 0; - uint8_t *pos = word.str; - switch (len) { /* fallthrough is intentional */ - case 7: - pos[6] = w8[6]; - /* fallthrough */ - case 6: - pos[5] = w8[5]; - /* fallthrough */ - case 5: - pos[4] = w8[4]; - /* fallthrough */ - case 4: - pos[3] = w8[3]; - /* fallthrough */ - case 3: - pos[2] = w8[2]; - /* fallthrough */ - case 2: - pos[1] = w8[1]; - /* fallthrough */ - case 1: - pos[0] = w8[0]; - } - word.str[7] = len_mod; - - /* last round */ - v3 ^= word.i; - hash_map_SipRound; - hash_map_SipRound; - v0 ^= word.i; - /* Finalization */ - v2 ^= 0xff; - /* d iterations of SipRound */ - for (size_t i = 0; i < y; ++i) { - hash_map_SipRound; - } - hash_map_SipRound; - hash_map_SipRound; - hash_map_SipRound; - hash_map_SipRound; - /* XOR it all together */ - v0 ^= v1 ^ v2 ^ v3; -#undef hash_map_SipRound - return v0; -} - -uint64_t fio_siphash24(const void *data, size_t len, uint64_t key1, - uint64_t key2) { - return fio_siphash_xy(data, len, 2, 4, key1, key2); -} - -uint64_t fio_siphash13(const void *data, size_t len, uint64_t key1, - uint64_t key2) { - return fio_siphash_xy(data, len, 1, 3, key1, key2); -} - -/* ***************************************************************************** -SHA-1 -***************************************************************************** */ - -static const uint8_t sha1_padding[64] = {0x80, 0}; - -/** -Process the buffer once full. -*/ -static inline void fio_sha1_perform_all_rounds(fio_sha1_s *s, - const uint8_t *buffer) { - /* collect data */ - uint32_t a = s->digest.i[0]; - uint32_t b = s->digest.i[1]; - uint32_t c = s->digest.i[2]; - uint32_t d = s->digest.i[3]; - uint32_t e = s->digest.i[4]; - uint32_t t, w[16]; - /* copy data to words, performing byte swapping as needed */ - w[0] = fio_str2u32(buffer); - w[1] = fio_str2u32(buffer + 4); - w[2] = fio_str2u32(buffer + 8); - w[3] = fio_str2u32(buffer + 12); - w[4] = fio_str2u32(buffer + 16); - w[5] = fio_str2u32(buffer + 20); - w[6] = fio_str2u32(buffer + 24); - w[7] = fio_str2u32(buffer + 28); - w[8] = fio_str2u32(buffer + 32); - w[9] = fio_str2u32(buffer + 36); - w[10] = fio_str2u32(buffer + 40); - w[11] = fio_str2u32(buffer + 44); - w[12] = fio_str2u32(buffer + 48); - w[13] = fio_str2u32(buffer + 52); - w[14] = fio_str2u32(buffer + 56); - w[15] = fio_str2u32(buffer + 60); - /* perform rounds */ -#undef perform_single_round -#define perform_single_round(num) \ - t = fio_lrot32(a, 5) + e + w[num] + ((b & c) | ((~b) & d)) + 0x5A827999; \ - e = d; \ - d = c; \ - c = fio_lrot32(b, 30); \ - b = a; \ - a = t; - -#define perform_four_rounds(i) \ - perform_single_round(i); \ - perform_single_round(i + 1); \ - perform_single_round(i + 2); \ - perform_single_round(i + 3); - - perform_four_rounds(0); - perform_four_rounds(4); - perform_four_rounds(8); - perform_four_rounds(12); - -#undef perform_single_round -#define perform_single_round(i) \ - w[(i)&15] = fio_lrot32((w[(i - 3) & 15] ^ w[(i - 8) & 15] ^ \ - w[(i - 14) & 15] ^ w[(i - 16) & 15]), \ - 1); \ - t = fio_lrot32(a, 5) + e + w[(i)&15] + ((b & c) | ((~b) & d)) + 0x5A827999; \ - e = d; \ - d = c; \ - c = fio_lrot32(b, 30); \ - b = a; \ - a = t; - - perform_four_rounds(16); - -#undef perform_single_round -#define perform_single_round(i) \ - w[(i)&15] = fio_lrot32((w[(i - 3) & 15] ^ w[(i - 8) & 15] ^ \ - w[(i - 14) & 15] ^ w[(i - 16) & 15]), \ - 1); \ - t = fio_lrot32(a, 5) + e + w[(i)&15] + (b ^ c ^ d) + 0x6ED9EBA1; \ - e = d; \ - d = c; \ - c = fio_lrot32(b, 30); \ - b = a; \ - a = t; - - perform_four_rounds(20); - perform_four_rounds(24); - perform_four_rounds(28); - perform_four_rounds(32); - perform_four_rounds(36); - -#undef perform_single_round -#define perform_single_round(i) \ - w[(i)&15] = fio_lrot32((w[(i - 3) & 15] ^ w[(i - 8) & 15] ^ \ - w[(i - 14) & 15] ^ w[(i - 16) & 15]), \ - 1); \ - t = fio_lrot32(a, 5) + e + w[(i)&15] + ((b & (c | d)) | (c & d)) + \ - 0x8F1BBCDC; \ - e = d; \ - d = c; \ - c = fio_lrot32(b, 30); \ - b = a; \ - a = t; - - perform_four_rounds(40); - perform_four_rounds(44); - perform_four_rounds(48); - perform_four_rounds(52); - perform_four_rounds(56); -#undef perform_single_round -#define perform_single_round(i) \ - w[(i)&15] = fio_lrot32((w[(i - 3) & 15] ^ w[(i - 8) & 15] ^ \ - w[(i - 14) & 15] ^ w[(i - 16) & 15]), \ - 1); \ - t = fio_lrot32(a, 5) + e + w[(i)&15] + (b ^ c ^ d) + 0xCA62C1D6; \ - e = d; \ - d = c; \ - c = fio_lrot32(b, 30); \ - b = a; \ - a = t; - perform_four_rounds(60); - perform_four_rounds(64); - perform_four_rounds(68); - perform_four_rounds(72); - perform_four_rounds(76); - - /* store data */ - s->digest.i[4] += e; - s->digest.i[3] += d; - s->digest.i[2] += c; - s->digest.i[1] += b; - s->digest.i[0] += a; -} - -/** -Initialize or reset the `sha1` object. This must be performed before hashing -data using sha1. -*/ -fio_sha1_s fio_sha1_init(void) { - return (fio_sha1_s){.digest.i[0] = 0x67452301, - .digest.i[1] = 0xefcdab89, - .digest.i[2] = 0x98badcfe, - .digest.i[3] = 0x10325476, - .digest.i[4] = 0xc3d2e1f0}; -} - -/** -Writes data to the sha1 buffer. -*/ -void fio_sha1_write(fio_sha1_s *s, const void *data, size_t len) { - size_t in_buffer = s->length & 63; - size_t partial = 64 - in_buffer; - s->length += len; - if (partial > len) { - memcpy(s->buffer + in_buffer, data, len); - return; - } - if (in_buffer) { - memcpy(s->buffer + in_buffer, data, partial); - len -= partial; - data = (void *)((uintptr_t)data + partial); - fio_sha1_perform_all_rounds(s, s->buffer); - } - while (len >= 64) { - fio_sha1_perform_all_rounds(s, data); - data = (void *)((uintptr_t)data + 64); - len -= 64; - } - if (len) { - memcpy(s->buffer + in_buffer, data, len); - } - return; -} - -char *fio_sha1_result(fio_sha1_s *s) { - size_t in_buffer = s->length & 63; - if (in_buffer > 55) { - memcpy(s->buffer + in_buffer, sha1_padding, 64 - in_buffer); - fio_sha1_perform_all_rounds(s, s->buffer); - memcpy(s->buffer, sha1_padding + 1, 56); - } else if (in_buffer != 55) { - memcpy(s->buffer + in_buffer, sha1_padding, 56 - in_buffer); - } else { - s->buffer[55] = sha1_padding[0]; - } - /* store the length in BITS - alignment should be promised by struct */ - /* this must the number in BITS, encoded as a BIG ENDIAN 64 bit number */ - uint64_t *len = (uint64_t *)(s->buffer + 56); - *len = s->length << 3; - *len = fio_lton64(*len); - fio_sha1_perform_all_rounds(s, s->buffer); - - /* change back to little endian */ - s->digest.i[0] = fio_ntol32(s->digest.i[0]); - s->digest.i[1] = fio_ntol32(s->digest.i[1]); - s->digest.i[2] = fio_ntol32(s->digest.i[2]); - s->digest.i[3] = fio_ntol32(s->digest.i[3]); - s->digest.i[4] = fio_ntol32(s->digest.i[4]); - - return (char *)s->digest.str; -} - -#undef perform_single_round - -/* ***************************************************************************** -SHA-2 -***************************************************************************** */ - -static const uint8_t sha2_padding[128] = {0x80, 0}; - -/* SHA-224 and SHA-256 constants */ -static uint32_t sha2_256_words[] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, - 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, - 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, - 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, - 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, - 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; - -/* SHA-512 and friends constants */ -static uint64_t sha2_512_words[] = { - 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, - 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, - 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, - 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, - 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, - 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, - 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, - 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, - 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, - 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, - 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, - 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, - 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, - 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, - 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, - 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, - 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, - 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, - 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, - 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, - 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, - 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, - 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, - 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, - 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, - 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, - 0x5fcb6fab3ad6faec, 0x6c44198c4a475817}; - -/* Specific Macros for the SHA-2 processing */ - -#define Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & z)) -#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) - -#define Eps0_32(x) \ - (fio_rrot32((x), 2) ^ fio_rrot32((x), 13) ^ fio_rrot32((x), 22)) -#define Eps1_32(x) \ - (fio_rrot32((x), 6) ^ fio_rrot32((x), 11) ^ fio_rrot32((x), 25)) -#define Omg0_32(x) (fio_rrot32((x), 7) ^ fio_rrot32((x), 18) ^ (((x) >> 3))) -#define Omg1_32(x) (fio_rrot32((x), 17) ^ fio_rrot32((x), 19) ^ (((x) >> 10))) - -#define Eps0_64(x) \ - (fio_rrot64((x), 28) ^ fio_rrot64((x), 34) ^ fio_rrot64((x), 39)) -#define Eps1_64(x) \ - (fio_rrot64((x), 14) ^ fio_rrot64((x), 18) ^ fio_rrot64((x), 41)) -#define Omg0_64(x) (fio_rrot64((x), 1) ^ fio_rrot64((x), 8) ^ (((x) >> 7))) -#define Omg1_64(x) (fio_rrot64((x), 19) ^ fio_rrot64((x), 61) ^ (((x) >> 6))) - -/** -Process the buffer once full. -*/ -static inline void fio_sha2_perform_all_rounds(fio_sha2_s *s, - const uint8_t *data) { - if (s->type & 1) { /* 512 derived type */ - // process values for the 64bit words - uint64_t a = s->digest.i64[0]; - uint64_t b = s->digest.i64[1]; - uint64_t c = s->digest.i64[2]; - uint64_t d = s->digest.i64[3]; - uint64_t e = s->digest.i64[4]; - uint64_t f = s->digest.i64[5]; - uint64_t g = s->digest.i64[6]; - uint64_t h = s->digest.i64[7]; - uint64_t t1, t2, w[80]; - w[0] = fio_str2u64(data); - w[1] = fio_str2u64(data + 8); - w[2] = fio_str2u64(data + 16); - w[3] = fio_str2u64(data + 24); - w[4] = fio_str2u64(data + 32); - w[5] = fio_str2u64(data + 40); - w[6] = fio_str2u64(data + 48); - w[7] = fio_str2u64(data + 56); - w[8] = fio_str2u64(data + 64); - w[9] = fio_str2u64(data + 72); - w[10] = fio_str2u64(data + 80); - w[11] = fio_str2u64(data + 88); - w[12] = fio_str2u64(data + 96); - w[13] = fio_str2u64(data + 104); - w[14] = fio_str2u64(data + 112); - w[15] = fio_str2u64(data + 120); - -#undef perform_single_round -#define perform_single_round(i) \ - t1 = h + Eps1_64(e) + Ch(e, f, g) + sha2_512_words[i] + w[i]; \ - t2 = Eps0_64(a) + Maj(a, b, c); \ - h = g; \ - g = f; \ - f = e; \ - e = d + t1; \ - d = c; \ - c = b; \ - b = a; \ - a = t1 + t2; - -#define perform_4rounds(i) \ - perform_single_round(i); \ - perform_single_round(i + 1); \ - perform_single_round(i + 2); \ - perform_single_round(i + 3); - - perform_4rounds(0); - perform_4rounds(4); - perform_4rounds(8); - perform_4rounds(12); - -#undef perform_single_round -#define perform_single_round(i) \ - w[i] = Omg1_64(w[i - 2]) + w[i - 7] + Omg0_64(w[i - 15]) + w[i - 16]; \ - t1 = h + Eps1_64(e) + Ch(e, f, g) + sha2_512_words[i] + w[i]; \ - t2 = Eps0_64(a) + Maj(a, b, c); \ - h = g; \ - g = f; \ - f = e; \ - e = d + t1; \ - d = c; \ - c = b; \ - b = a; \ - a = t1 + t2; - - perform_4rounds(16); - perform_4rounds(20); - perform_4rounds(24); - perform_4rounds(28); - perform_4rounds(32); - perform_4rounds(36); - perform_4rounds(40); - perform_4rounds(44); - perform_4rounds(48); - perform_4rounds(52); - perform_4rounds(56); - perform_4rounds(60); - perform_4rounds(64); - perform_4rounds(68); - perform_4rounds(72); - perform_4rounds(76); - - s->digest.i64[0] += a; - s->digest.i64[1] += b; - s->digest.i64[2] += c; - s->digest.i64[3] += d; - s->digest.i64[4] += e; - s->digest.i64[5] += f; - s->digest.i64[6] += g; - s->digest.i64[7] += h; - return; - } else { - // process values for the 32bit words - uint32_t a = s->digest.i32[0]; - uint32_t b = s->digest.i32[1]; - uint32_t c = s->digest.i32[2]; - uint32_t d = s->digest.i32[3]; - uint32_t e = s->digest.i32[4]; - uint32_t f = s->digest.i32[5]; - uint32_t g = s->digest.i32[6]; - uint32_t h = s->digest.i32[7]; - uint32_t t1, t2, w[64]; - - w[0] = fio_str2u32(data); - w[1] = fio_str2u32(data + 4); - w[2] = fio_str2u32(data + 8); - w[3] = fio_str2u32(data + 12); - w[4] = fio_str2u32(data + 16); - w[5] = fio_str2u32(data + 20); - w[6] = fio_str2u32(data + 24); - w[7] = fio_str2u32(data + 28); - w[8] = fio_str2u32(data + 32); - w[9] = fio_str2u32(data + 36); - w[10] = fio_str2u32(data + 40); - w[11] = fio_str2u32(data + 44); - w[12] = fio_str2u32(data + 48); - w[13] = fio_str2u32(data + 52); - w[14] = fio_str2u32(data + 56); - w[15] = fio_str2u32(data + 60); - -#undef perform_single_round -#define perform_single_round(i) \ - t1 = h + Eps1_32(e) + Ch(e, f, g) + sha2_256_words[i] + w[i]; \ - t2 = Eps0_32(a) + Maj(a, b, c); \ - h = g; \ - g = f; \ - f = e; \ - e = d + t1; \ - d = c; \ - c = b; \ - b = a; \ - a = t1 + t2; - - perform_4rounds(0); - perform_4rounds(4); - perform_4rounds(8); - perform_4rounds(12); - -#undef perform_single_round -#define perform_single_round(i) \ - w[i] = Omg1_32(w[i - 2]) + w[i - 7] + Omg0_32(w[i - 15]) + w[i - 16]; \ - t1 = h + Eps1_32(e) + Ch(e, f, g) + sha2_256_words[i] + w[i]; \ - t2 = Eps0_32(a) + Maj(a, b, c); \ - h = g; \ - g = f; \ - f = e; \ - e = d + t1; \ - d = c; \ - c = b; \ - b = a; \ - a = t1 + t2; - - perform_4rounds(16); - perform_4rounds(20); - perform_4rounds(24); - perform_4rounds(28); - perform_4rounds(32); - perform_4rounds(36); - perform_4rounds(40); - perform_4rounds(44); - perform_4rounds(48); - perform_4rounds(52); - perform_4rounds(56); - perform_4rounds(60); - - s->digest.i32[0] += a; - s->digest.i32[1] += b; - s->digest.i32[2] += c; - s->digest.i32[3] += d; - s->digest.i32[4] += e; - s->digest.i32[5] += f; - s->digest.i32[6] += g; - s->digest.i32[7] += h; - } -} - -/** -Initialize/reset the SHA-2 object. - -SHA-2 is actually a family of functions with different variants. When -initializing the SHA-2 container, you must select the variant you intend to -apply. The following are valid options (see the fio_sha2_variant_e enum): - -- SHA_512 (== 0) -- SHA_384 -- SHA_512_224 -- SHA_512_256 -- SHA_256 -- SHA_224 - -*/ -fio_sha2_s fio_sha2_init(fio_sha2_variant_e variant) { - if (variant == SHA_256) { - return (fio_sha2_s){ - .type = SHA_256, - .digest.i32[0] = 0x6a09e667, - .digest.i32[1] = 0xbb67ae85, - .digest.i32[2] = 0x3c6ef372, - .digest.i32[3] = 0xa54ff53a, - .digest.i32[4] = 0x510e527f, - .digest.i32[5] = 0x9b05688c, - .digest.i32[6] = 0x1f83d9ab, - .digest.i32[7] = 0x5be0cd19, - }; - } else if (variant == SHA_384) { - return (fio_sha2_s){ - .type = SHA_384, - .digest.i64[0] = 0xcbbb9d5dc1059ed8, - .digest.i64[1] = 0x629a292a367cd507, - .digest.i64[2] = 0x9159015a3070dd17, - .digest.i64[3] = 0x152fecd8f70e5939, - .digest.i64[4] = 0x67332667ffc00b31, - .digest.i64[5] = 0x8eb44a8768581511, - .digest.i64[6] = 0xdb0c2e0d64f98fa7, - .digest.i64[7] = 0x47b5481dbefa4fa4, - }; - } else if (variant == SHA_512) { - return (fio_sha2_s){ - .type = SHA_512, - .digest.i64[0] = 0x6a09e667f3bcc908, - .digest.i64[1] = 0xbb67ae8584caa73b, - .digest.i64[2] = 0x3c6ef372fe94f82b, - .digest.i64[3] = 0xa54ff53a5f1d36f1, - .digest.i64[4] = 0x510e527fade682d1, - .digest.i64[5] = 0x9b05688c2b3e6c1f, - .digest.i64[6] = 0x1f83d9abfb41bd6b, - .digest.i64[7] = 0x5be0cd19137e2179, - }; - } else if (variant == SHA_224) { - return (fio_sha2_s){ - .type = SHA_224, - .digest.i32[0] = 0xc1059ed8, - .digest.i32[1] = 0x367cd507, - .digest.i32[2] = 0x3070dd17, - .digest.i32[3] = 0xf70e5939, - .digest.i32[4] = 0xffc00b31, - .digest.i32[5] = 0x68581511, - .digest.i32[6] = 0x64f98fa7, - .digest.i32[7] = 0xbefa4fa4, - }; - } else if (variant == SHA_512_224) { - return (fio_sha2_s){ - .type = SHA_512_224, - .digest.i64[0] = 0x8c3d37c819544da2, - .digest.i64[1] = 0x73e1996689dcd4d6, - .digest.i64[2] = 0x1dfab7ae32ff9c82, - .digest.i64[3] = 0x679dd514582f9fcf, - .digest.i64[4] = 0x0f6d2b697bd44da8, - .digest.i64[5] = 0x77e36f7304c48942, - .digest.i64[6] = 0x3f9d85a86a1d36c8, - .digest.i64[7] = 0x1112e6ad91d692a1, - }; - } else if (variant == SHA_512_256) { - return (fio_sha2_s){ - .type = SHA_512_256, - .digest.i64[0] = 0x22312194fc2bf72c, - .digest.i64[1] = 0x9f555fa3c84c64c2, - .digest.i64[2] = 0x2393b86b6f53b151, - .digest.i64[3] = 0x963877195940eabd, - .digest.i64[4] = 0x96283ee2a88effe3, - .digest.i64[5] = 0xbe5e1e2553863992, - .digest.i64[6] = 0x2b0199fc2c85b8aa, - .digest.i64[7] = 0x0eb72ddc81c52ca2, - }; - } - FIO_LOG_FATAL("SHA-2 ERROR - variant unknown"); - exit(2); -} - -/** -Writes data to the SHA-2 buffer. -*/ -void fio_sha2_write(fio_sha2_s *s, const void *data, size_t len) { - size_t in_buffer; - size_t partial; - if (s->type & 1) { /* 512 type derived */ - in_buffer = s->length.words[0] & 127; - if (s->length.words[0] + len < s->length.words[0]) { - /* we are at wraping around the 64bit limit */ - s->length.words[1] = (s->length.words[1] << 1) | 1; - } - s->length.words[0] += len; - partial = 128 - in_buffer; - - if (partial > len) { - memcpy(s->buffer + in_buffer, data, len); - return; - } - if (in_buffer) { - memcpy(s->buffer + in_buffer, data, partial); - len -= partial; - data = (void *)((uintptr_t)data + partial); - fio_sha2_perform_all_rounds(s, s->buffer); - } - while (len >= 128) { - fio_sha2_perform_all_rounds(s, data); - data = (void *)((uintptr_t)data + 128); - len -= 128; - } - if (len) { - memcpy(s->buffer + in_buffer, data, len); - } - return; - } - /* else... NOT 512 bits derived (64bit base) */ - - in_buffer = s->length.words[0] & 63; - partial = 64 - in_buffer; - - s->length.words[0] += len; - - if (partial > len) { - memcpy(s->buffer + in_buffer, data, len); - return; - } - if (in_buffer) { - memcpy(s->buffer + in_buffer, data, partial); - len -= partial; - data = (void *)((uintptr_t)data + partial); - fio_sha2_perform_all_rounds(s, s->buffer); - } - while (len >= 64) { - fio_sha2_perform_all_rounds(s, data); - data = (void *)((uintptr_t)data + 64); - len -= 64; - } - if (len) { - memcpy(s->buffer + in_buffer, data, len); - } - return; -} - -/** -Finalizes the SHA-2 hash, returning the Hashed data. - -`sha2_result` can be called for the same object multiple times, but the -finalization will only be performed the first time this function is called. -*/ -char *fio_sha2_result(fio_sha2_s *s) { - if (s->type & 1) { - /* 512 bits derived hashing */ - - size_t in_buffer = s->length.words[0] & 127; - - if (in_buffer > 111) { - memcpy(s->buffer + in_buffer, sha2_padding, 128 - in_buffer); - fio_sha2_perform_all_rounds(s, s->buffer); - memcpy(s->buffer, sha2_padding + 1, 112); - } else if (in_buffer != 111) { - memcpy(s->buffer + in_buffer, sha2_padding, 112 - in_buffer); - } else { - s->buffer[111] = sha2_padding[0]; - } - /* store the length in BITS - alignment should be promised by struct */ - /* this must the number in BITS, encoded as a BIG ENDIAN 64 bit number */ - - s->length.words[1] = (s->length.words[1] << 3) | (s->length.words[0] >> 61); - s->length.words[0] = s->length.words[0] << 3; - s->length.words[0] = fio_lton64(s->length.words[0]); - s->length.words[1] = fio_lton64(s->length.words[1]); - -#if !__BIG_ENDIAN__ - { - uint_fast64_t tmp = s->length.words[0]; - s->length.words[0] = s->length.words[1]; - s->length.words[1] = tmp; - } -#endif - - uint64_t *len = (uint64_t *)(s->buffer + 112); - len[0] = s->length.words[0]; - len[1] = s->length.words[1]; - fio_sha2_perform_all_rounds(s, s->buffer); - - /* change back to little endian */ - s->digest.i64[0] = fio_ntol64(s->digest.i64[0]); - s->digest.i64[1] = fio_ntol64(s->digest.i64[1]); - s->digest.i64[2] = fio_ntol64(s->digest.i64[2]); - s->digest.i64[3] = fio_ntol64(s->digest.i64[3]); - s->digest.i64[4] = fio_ntol64(s->digest.i64[4]); - s->digest.i64[5] = fio_ntol64(s->digest.i64[5]); - s->digest.i64[6] = fio_ntol64(s->digest.i64[6]); - s->digest.i64[7] = fio_ntol64(s->digest.i64[7]); - // set NULL bytes for SHA-2 Type - switch (s->type) { - case SHA_512_224: - s->digest.str[28] = 0; - break; - case SHA_512_256: - s->digest.str[32] = 0; - break; - case SHA_384: - s->digest.str[48] = 0; - break; - default: - s->digest.str[64] = - 0; /* sometimes the optimizer messes the NUL sequence */ - break; - } - // fprintf(stderr, "result requested, in hex, is:"); - // for (size_t i = 0; i < ((s->type & 1) ? 64 : 32); i++) - // fprintf(stderr, "%02x", (unsigned int)(s->digest.str[i] & 0xFF)); - // fprintf(stderr, "\r\n"); - return (char *)s->digest.str; - } - - size_t in_buffer = s->length.words[0] & 63; - if (in_buffer > 55) { - memcpy(s->buffer + in_buffer, sha2_padding, 64 - in_buffer); - fio_sha2_perform_all_rounds(s, s->buffer); - memcpy(s->buffer, sha2_padding + 1, 56); - } else if (in_buffer != 55) { - memcpy(s->buffer + in_buffer, sha2_padding, 56 - in_buffer); - } else { - s->buffer[55] = sha2_padding[0]; - } - /* store the length in BITS - alignment should be promised by struct */ - /* this must the number in BITS, encoded as a BIG ENDIAN 64 bit number */ - uint64_t *len = (uint64_t *)(s->buffer + 56); - *len = s->length.words[0] << 3; - *len = fio_lton64(*len); - fio_sha2_perform_all_rounds(s, s->buffer); - - /* change back to little endian, if required */ - - s->digest.i32[0] = fio_ntol32(s->digest.i32[0]); - s->digest.i32[1] = fio_ntol32(s->digest.i32[1]); - s->digest.i32[2] = fio_ntol32(s->digest.i32[2]); - s->digest.i32[3] = fio_ntol32(s->digest.i32[3]); - s->digest.i32[4] = fio_ntol32(s->digest.i32[4]); - s->digest.i32[5] = fio_ntol32(s->digest.i32[5]); - s->digest.i32[6] = fio_ntol32(s->digest.i32[6]); - s->digest.i32[7] = fio_ntol32(s->digest.i32[7]); - - // set NULL bytes for SHA_224 - if (s->type == SHA_224) - s->digest.str[28] = 0; - // fprintf(stderr, "SHA-2 result requested, in hex, is:"); - // for (size_t i = 0; i < ((s->type & 1) ? 64 : 32); i++) - // fprintf(stderr, "%02x", (unsigned int)(s->digest.str[i] & 0xFF)); - // fprintf(stderr, "\r\n"); - return (char *)s->digest.str; -} - -#undef perform_single_round - -/* **************************************************************************** -Base64 encoding -***************************************************************************** */ - -/** the base64 encoding array */ -static const char base64_encodes_original[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - -/** the base64 encoding array */ -static const char base64_encodes_url[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; - -/** -A base64 decoding array. - -Generation script (Ruby): - -a = []; a[255] = 0 -s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".bytes; -s.length.times {|i| a[s[i]] = i }; -s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,".bytes; -s.length.times {|i| a[s[i]] = i }; -s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".bytes; -s.length.times {|i| a[s[i]] = i }; a.map!{ |i| i.to_i }; a - -*/ -static unsigned base64_decodes[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 62, 63, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 0, 0, 0, 64, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, - 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; -#define BITVAL(x) (base64_decodes[(x)] & 63) - -/* - * The actual encoding logic. The map can be switched for encoding variations. - */ -static inline int fio_base64_encode_internal(char *target, const char *data, - int len, - const char *base64_encodes) { - /* walk backwards, allowing fo inplace decoding (target == data) */ - int groups = len / 3; - const int mod = len - (groups * 3); - const int target_size = (groups + (mod != 0)) * 4; - char *writer = target + target_size - 1; - const char *reader = data + len - 1; - writer[1] = 0; - switch (mod) { - case 2: { - char tmp2 = *(reader--); - char tmp1 = *(reader--); - *(writer--) = '='; - *(writer--) = base64_encodes[((tmp2 & 15) << 2)]; - *(writer--) = base64_encodes[((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15)]; - *(writer--) = base64_encodes[(tmp1 >> 2) & 63]; - } break; - case 1: { - char tmp1 = *(reader--); - *(writer--) = '='; - *(writer--) = '='; - *(writer--) = base64_encodes[(tmp1 & 3) << 4]; - *(writer--) = base64_encodes[(tmp1 >> 2) & 63]; - } break; - } - while (groups) { - groups--; - const char tmp3 = *(reader--); - const char tmp2 = *(reader--); - const char tmp1 = *(reader--); - *(writer--) = base64_encodes[tmp3 & 63]; - *(writer--) = base64_encodes[((tmp2 & 15) << 2) | ((tmp3 >> 6) & 3)]; - *(writer--) = base64_encodes[(((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15))]; - *(writer--) = base64_encodes[(tmp1 >> 2) & 63]; - } - return target_size; -} - -/** -This will encode a byte array (data) of a specified length (len) and -place the encoded data into the target byte buffer (target). The target buffer -MUST have enough room for the expected data. - -Base64 encoding always requires 4 bytes for each 3 bytes. Padding is added if -the raw data's length isn't devisable by 3. - -Always assume the target buffer should have room enough for (len*4/3 + 4) -bytes. - -Returns the number of bytes actually written to the target buffer -(including the Base64 required padding and excluding a NULL terminator). - -A NULL terminator char is NOT written to the target buffer. -*/ -int fio_base64_encode(char *target, const char *data, int len) { - return fio_base64_encode_internal(target, data, len, base64_encodes_original); -} - -/** -Same as fio_base64_encode, but using Base64URL encoding. -*/ -int fio_base64url_encode(char *target, const char *data, int len) { - return fio_base64_encode_internal(target, data, len, base64_encodes_url); -} - -/** -This will decode a Base64 encoded string of a specified length (len) and -place the decoded data into the target byte buffer (target). - -The target buffer MUST have enough room for the expected data. - -A NULL byte will be appended to the target buffer. The function will return -the number of bytes written to the target buffer. - -If the target buffer is NULL, the encoded string will be destructively edited -and the decoded data will be placed in the original string's buffer. - -Base64 encoding always requires 4 bytes for each 3 bytes. Padding is added if -the raw data's length isn't devisable by 3. Hence, the target buffer should -be, at least, `base64_len/4*3 + 3` long. - -Returns the number of bytes actually written to the target buffer (excluding -the NULL terminator byte). - -If an error occurred, returns the number of bytes written up to the error. Test -`errno` for error (will be set to ERANGE). -*/ -int fio_base64_decode(char *target, char *encoded, int base64_len) { - if (!target) - target = encoded; - if (base64_len <= 0) { - target[0] = 0; - return 0; - } - int written = 0; - uint8_t tmp1, tmp2, tmp3, tmp4; - // skip unknown data at end - while (base64_len && - !base64_decodes[*(uint8_t *)(encoded + (base64_len - 1))]) { - base64_len--; - } - // skip white space - while (base64_len && isspace((*(uint8_t *)encoded))) { - base64_len--; - encoded++; - } - while (base64_len >= 4) { - if (!base64_len) { - return written; - } - tmp1 = *(uint8_t *)(encoded++); - tmp2 = *(uint8_t *)(encoded++); - tmp3 = *(uint8_t *)(encoded++); - tmp4 = *(uint8_t *)(encoded++); - if (!base64_decodes[tmp1] || !base64_decodes[tmp2] || - !base64_decodes[tmp3] || !base64_decodes[tmp4]) { - errno = ERANGE; - goto finish; - } - *(target++) = (BITVAL(tmp1) << 2) | (BITVAL(tmp2) >> 4); - *(target++) = (BITVAL(tmp2) << 4) | (BITVAL(tmp3) >> 2); - *(target++) = (BITVAL(tmp3) << 6) | (BITVAL(tmp4)); - // make sure we don't loop forever. - base64_len -= 4; - // count written bytes - written += 3; - // skip white space - while (base64_len && isspace((*encoded))) { - base64_len--; - encoded++; - } - } - // deal with the "tail" of the mis-encoded stream - this shouldn't happen - tmp1 = 0; - tmp2 = 0; - tmp3 = 0; - tmp4 = 0; - switch (base64_len) { - case 1: - tmp1 = *(uint8_t *)(encoded++); - if (!base64_decodes[tmp1]) { - errno = ERANGE; - goto finish; - } - *(target++) = BITVAL(tmp1); - written += 1; - break; - case 2: - tmp1 = *(uint8_t *)(encoded++); - tmp2 = *(uint8_t *)(encoded++); - if (!base64_decodes[tmp1] || !base64_decodes[tmp2]) { - errno = ERANGE; - goto finish; - } - *(target++) = (BITVAL(tmp1) << 2) | (BITVAL(tmp2) >> 6); - *(target++) = (BITVAL(tmp2) << 4); - written += 2; - break; - case 3: - tmp1 = *(uint8_t *)(encoded++); - tmp2 = *(uint8_t *)(encoded++); - tmp3 = *(uint8_t *)(encoded++); - if (!base64_decodes[tmp1] || !base64_decodes[tmp2] || - !base64_decodes[tmp3]) { - errno = ERANGE; - goto finish; - } - *(target++) = (BITVAL(tmp1) << 2) | (BITVAL(tmp2) >> 6); - *(target++) = (BITVAL(tmp2) << 4) | (BITVAL(tmp3) >> 2); - *(target++) = BITVAL(tmp3) << 6; - written += 3; - break; - } -finish: - if (encoded[-1] == '=') { - target--; - written--; - if (encoded[-2] == '=') { - target--; - written--; - } - if (written < 0) - written = 0; - } - *target = 0; - return written; -} - -/* ***************************************************************************** -Section Start Marker - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Testing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -***************************************************************************** */ - -#if DEBUG - -// clang-format off -#if defined(HAVE_OPENSSL) -# include -#endif -// clang-format on - -/* ***************************************************************************** -Testing Linked Lists -***************************************************************************** */ - -#define FIO_LLIST_TEST_LIMIT 1016 - -/** - * Tests linked list functionality. - */ -#ifndef H_FIO_LINKED_LIST_H -#define fio_llist_test() -#else -FIO_FUNC inline void fio_llist_test(void) { - fio_ls_s list = FIO_LS_INIT(list); - size_t counter; - fprintf(stderr, "=== Testing Core Linked List features (fio_ls and " - "fio_ls_embs functions)\n"); - /* test push/shift */ - for (uintptr_t i = 0; i < FIO_LLIST_TEST_LIMIT; ++i) { - fio_ls_push(&list, (void *)i); - } - FIO_ASSERT(fio_ls_any(&list), "List should be populated after fio_ls_push"); - counter = 0; - FIO_LS_FOR(&list, pos) { - FIO_ASSERT((size_t)pos->obj == counter, - "`FIO_LS_FOR` value error (%zu != %zu)", (size_t)pos->obj, - counter); - ++counter; - } - counter = 0; - while (fio_ls_any(&list)) { - FIO_ASSERT(counter < FIO_LLIST_TEST_LIMIT, - "`fio_ls_any` didn't return false when expected %p<=%p=>%p", - (void *)list.prev, (void *)&list, (void *)list.next); - size_t tmp = (size_t)fio_ls_shift(&list); - FIO_ASSERT(tmp == counter, "`fio_ls_shift` value error (%zu != %zu)", tmp, - counter); - ++counter; - } - FIO_ASSERT(counter == FIO_LLIST_TEST_LIMIT, - "List item count error (%zu != %zu)", counter, - (size_t)FIO_LLIST_TEST_LIMIT); - /* test unshift/pop */ - for (uintptr_t i = 0; i < FIO_LLIST_TEST_LIMIT; ++i) { - fio_ls_unshift(&list, (void *)i); - } - FIO_ASSERT(fio_ls_any(&list), - "List should be populated after fio_ls_unshift"); - counter = 0; - while (!fio_ls_is_empty(&list)) { - FIO_ASSERT(counter < FIO_LLIST_TEST_LIMIT, - "`fio_ls_is_empty` didn't return true when expected %p<=%p=>%p", - (void *)list.prev, (void *)&list, (void *)list.next); - size_t tmp = (size_t)fio_ls_pop(&list); - FIO_ASSERT(tmp == counter, "`fio_ls_pop` value error (%zu != %zu)", tmp, - counter); - ++counter; - } - FIO_ASSERT(counter == FIO_LLIST_TEST_LIMIT, - "List item count error (%zu != %zu)", counter, - (size_t)FIO_LLIST_TEST_LIMIT); - - /* Re-test for embeded list */ - - struct fio_ls_test_s { - size_t i; - fio_ls_embd_s node; - }; - - fio_ls_embd_s emlist = FIO_LS_INIT(emlist); - - /* test push/shift */ - for (uintptr_t i = 0; i < FIO_LLIST_TEST_LIMIT; ++i) { - struct fio_ls_test_s *n = malloc(sizeof(*n)); - FIO_ASSERT_ALLOC(n); - n->i = i; - fio_ls_embd_push(&emlist, &n->node); - FIO_ASSERT(FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, emlist.next)->i == 0, - "fio_ls_embd_push should push to the end."); - } - FIO_ASSERT(fio_ls_embd_any(&emlist), - "List should be populated after fio_ls_embd_push"); - counter = 0; - FIO_LS_EMBD_FOR(&emlist, pos) { - FIO_ASSERT(FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, pos)->i == counter, - "`FIO_LS_EMBD_FOR` value error (%zu != %zu)", - FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, pos)->i, counter); - ++counter; - } - counter = 0; - while (fio_ls_embd_any(&emlist)) { - FIO_ASSERT(counter < FIO_LLIST_TEST_LIMIT, - "`fio_ls_embd_any` didn't return false when expected %p<=%p=>%p", - (void *)emlist.prev, (void *)&emlist, (void *)emlist.next); - struct fio_ls_test_s *n = - FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, fio_ls_embd_shift(&emlist)); - FIO_ASSERT(n->i == counter, "`fio_ls_embd_shift` value error (%zu != %zu)", - n->i, counter); - free(n); - ++counter; - } - FIO_ASSERT(counter == FIO_LLIST_TEST_LIMIT, - "List item count error (%zu != %zu)", counter, - (size_t)FIO_LLIST_TEST_LIMIT); - /* test shift/unshift */ - for (uintptr_t i = 0; i < FIO_LLIST_TEST_LIMIT; ++i) { - struct fio_ls_test_s *n = malloc(sizeof(*n)); - FIO_ASSERT_ALLOC(n) - n->i = i; - fio_ls_embd_unshift(&emlist, &n->node); - FIO_ASSERT(FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, emlist.next)->i == i, - "fio_ls_embd_unshift should push to the start."); - } - FIO_ASSERT(fio_ls_embd_any(&emlist), - "List should be populated after fio_ls_embd_unshift"); - counter = 0; - while (!fio_ls_embd_is_empty(&emlist)) { - FIO_ASSERT( - counter < FIO_LLIST_TEST_LIMIT, - "`fio_ls_embd_is_empty` didn't return true when expected %p<=%p=>%p", - (void *)emlist.prev, (void *)&emlist, (void *)emlist.next); - struct fio_ls_test_s *n = - FIO_LS_EMBD_OBJ(struct fio_ls_test_s, node, fio_ls_embd_pop(&emlist)); - FIO_ASSERT(n->i == counter, "`fio_ls_embd_pop` value error (%zu != %zu)", - n->i, counter); - free(n); - ++counter; - } - FIO_ASSERT(counter == FIO_LLIST_TEST_LIMIT, - "List item count error (%zu != %zu)", counter, - (size_t)FIO_LLIST_TEST_LIMIT); - fprintf(stderr, "* passed.\n"); -} -#endif - -/* ***************************************************************************** -Testing Strings -***************************************************************************** */ - -#ifndef H_FIO_STR_H -#define fio_str_test() -#else - -static int fio_str_test_dealloc_counter = 0; - -FIO_FUNC void fio_str_test_dealloc(void *s) { - FIO_ASSERT(!fio_str_test_dealloc_counter, - "fio_str_s reference count error!\n"); - fio_free(s); - fprintf(stderr, "* reference counting `fio_str_free2` pass.\n"); -} - -/** - * Tests the fio_str functionality. - */ -FIO_FUNC inline void fio_str_test(void) { -#define ROUND_UP_CAPA_2WORDS(num) \ - (((num + 1) & (sizeof(long double) - 1)) \ - ? ((num + 1) | (sizeof(long double) - 1)) \ - : (num)) - fprintf(stderr, "=== Testing Core String features (fio_str_s functions)\n"); - fprintf(stderr, "* String container size: %zu\n", sizeof(fio_str_s)); - fprintf(stderr, - "* Self-Contained String Capacity (FIO_STR_SMALL_CAPA): %zu\n", - FIO_STR_SMALL_CAPA); - fio_str_s str = {.small = 0}; /* test zeroed out memory */ - FIO_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1, - "Small String capacity reporting error!"); - FIO_ASSERT(fio_str_len(&str) == 0, "Small String length reporting error!"); - FIO_ASSERT(fio_str_data(&str) == - (char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA), - "Small String pointer reporting error (%zd offset)!", - (ssize_t)(((char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA)) - - fio_str_data(&str))); - fio_str_write(&str, "World", 4); - FIO_ASSERT(str.small, - "Small String writing error - not small on small write!"); - FIO_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1, - "Small String capacity reporting error after write!"); - FIO_ASSERT(fio_str_len(&str) == 4, - "Small String length reporting error after write!"); - FIO_ASSERT(fio_str_data(&str) == - (char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA), - "Small String pointer reporting error after write!"); - FIO_ASSERT(strlen(fio_str_data(&str)) == 4, - "Small String NUL missing after write (%zu)!", - strlen(fio_str_data(&str))); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Worl"), - "Small String write error (%s)!", fio_str_data(&str)); - FIO_ASSERT(fio_str_data(&str) == fio_str_info(&str).data, - "Small String `fio_str_data` != `fio_str_info(s).data` (%p != %p)", - (void *)fio_str_data(&str), (void *)fio_str_info(&str).data); - - fio_str_capa_assert(&str, sizeof(fio_str_s) - 1); - FIO_ASSERT(!str.small, - "Long String reporting as small after capacity update!"); - FIO_ASSERT(fio_str_capa(&str) >= sizeof(fio_str_s) - 1, - "Long String capacity update error (%zu != %zu)!", - fio_str_capa(&str), sizeof(fio_str_s)); - FIO_ASSERT(fio_str_data(&str) == fio_str_info(&str).data, - "Long String `fio_str_data` !>= `fio_str_info(s).data` (%p != %p)", - (void *)fio_str_data(&str), (void *)fio_str_info(&str).data); - - FIO_ASSERT( - fio_str_len(&str) == 4, - "Long String length changed during conversion from small string (%zu)!", - fio_str_len(&str)); - FIO_ASSERT(fio_str_data(&str) == str.data, - "Long String pointer reporting error after capacity update!"); - FIO_ASSERT(strlen(fio_str_data(&str)) == 4, - "Long String NUL missing after capacity update (%zu)!", - strlen(fio_str_data(&str))); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Worl"), - "Long String value changed after capacity update (%s)!", - fio_str_data(&str)); - - fio_str_write(&str, "d!", 2); - FIO_ASSERT(!strcmp(fio_str_data(&str), "World!"), - "Long String `write` error (%s)!", fio_str_data(&str)); - - fio_str_replace(&str, 0, 0, "Hello ", 6); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello World!"), - "Long String `insert` error (%s)!", fio_str_data(&str)); - - fio_str_resize(&str, 6); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello "), - "Long String `resize` clipping error (%s)!", fio_str_data(&str)); - - fio_str_replace(&str, 6, 0, "My World!", 9); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello My World!"), - "Long String `replace` error when testing overflow (%s)!", - fio_str_data(&str)); - - str.capa = str.len; - fio_str_replace(&str, -10, 2, "Big", 3); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"), - "Long String `replace` error when testing splicing (%s)!", - fio_str_data(&str)); - - FIO_ASSERT( - fio_str_capa(&str) == ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!")), - "Long String `fio_str_replace` capacity update error (%zu != %zu)!", - fio_str_capa(&str), ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!"))); - - if (str.len < FIO_STR_SMALL_CAPA) { - fio_str_compact(&str); - FIO_ASSERT(str.small, "Compacting didn't change String to small!"); - FIO_ASSERT(fio_str_len(&str) == strlen("Hello Big World!"), - "Compacting altered String length! (%zu != %zu)!", - fio_str_len(&str), strlen("Hello Big World!")); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"), - "Compact data error (%s)!", fio_str_data(&str)); - FIO_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1, - "Compacted String capacity reporting error!"); - } else { - fprintf(stderr, "* skipped `compact` test!\n"); - } - - { - fio_str_freeze(&str); - fio_str_info_s old_state = fio_str_info(&str); - fio_str_write(&str, "more data to be written here", 28); - fio_str_replace(&str, 2, 1, "more data to be written here", 28); - fio_str_info_s new_state = fio_str_info(&str); - FIO_ASSERT(old_state.len == new_state.len, "Frozen String length changed!"); - FIO_ASSERT(old_state.data == new_state.data, - "Frozen String pointer changed!"); - FIO_ASSERT( - old_state.capa == new_state.capa, - "Frozen String capacity changed (allowed, but shouldn't happen)!"); - str.frozen = 0; - } - fio_str_printf(&str, " %u", 42); - FIO_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World! 42"), - "`fio_str_printf` data error (%s)!", fio_str_data(&str)); - - { - fio_str_s str2 = FIO_STR_INIT; - fio_str_concat(&str2, &str); - FIO_ASSERT(fio_str_iseq(&str, &str2), - "`fio_str_concat` error, strings not equal (%s != %s)!", - fio_str_data(&str), fio_str_data(&str2)); - fio_str_write(&str2, ":extra data", 11); - FIO_ASSERT( - !fio_str_iseq(&str, &str2), - "`fio_str_write` error after copy, strings equal ((%zu)%s == (%zu)%s)!", - fio_str_len(&str), fio_str_data(&str), fio_str_len(&str2), - fio_str_data(&str2)); - - fio_str_free(&str2); - } - - fio_str_free(&str); - - fio_str_write_i(&str, -42); - FIO_ASSERT(fio_str_len(&str) == 3 && !memcmp("-42", fio_str_data(&str), 3), - "fio_str_write_i output error ((%zu) %s != -42)", - fio_str_len(&str), fio_str_data(&str)); - fio_str_free(&str); - - { - fprintf(stderr, "* testing `fio_str_readfile`, and reference counting.\n"); - fio_str_s *s = fio_str_new2(); - FIO_ASSERT(s && s->small, - "`fio_str_new2` error, string not initialized (%p)!", (void *)s); - fio_str_s *s2 = fio_str_dup(s); - - ++fio_str_test_dealloc_counter; - - FIO_ASSERT(s2 == s, "`fio_str_dup` error, should return self!"); - FIO_ASSERT(s->ref == 1, - "`fio_str_dup` error, reference counter not incremented!"); - - fprintf(stderr, "* reading a file.\n"); - fio_str_info_s state = fio_str_readfile(s, __FILE__, 0, 0); - if (!s->small) /* attach deallocation test */ - s->dealloc = fio_str_test_dealloc; - - FIO_ASSERT(state.data, - "`fio_str_readfile` error, no data was read for file %s!", - __FILE__); - - FIO_ASSERT(!memcmp(state.data, - "/* " - "******************************************************" - "***********************", - 80), - "`fio_str_readfile` content error, header mismatch!\n %s", - state.data); - fprintf(stderr, "* testing UTF-8 validation and length.\n"); - FIO_ASSERT( - fio_str_utf8_valid(s), - "`fio_str_utf8_valid` error, code in this file should be valid!"); - FIO_ASSERT(fio_str_utf8_len(s) && (fio_str_utf8_len(s) <= fio_str_len(s)) && - (fio_str_utf8_len(s) >= (fio_str_len(s)) >> 1), - "`fio_str_utf8_len` error, invalid value (%zu / %zu!", - fio_str_utf8_len(s), fio_str_len(s)); - - fprintf(stderr, "* reviewing reference counting `fio_str_free2` (1/2).\n"); - fio_str_free2(s2); - --fio_str_test_dealloc_counter; - FIO_ASSERT(s->ref == 0, - "`fio_str_free2` error, reference counter not subtracted!"); - FIO_ASSERT(s->small == 0, "`fio_str_free2` error, strring reinitialized!"); - FIO_ASSERT( - fio_str_data(s) == state.data, - "`fio_str_free2` error, data freed while references exist! (%p != %p)", - (void *)fio_str_data(s), (void *)state.data); - - if (1) { - /* String content == whole file (this file) */ - intptr_t pos = -11; - size_t len = 20; - fprintf(stderr, "* testing UTF-8 positioning.\n"); - - FIO_ASSERT( - fio_str_utf8_select(s, &pos, &len) == 0, - "`fio_str_utf8_select` returned error for negative pos! (%zd, %zu)", - (ssize_t)pos, len); - FIO_ASSERT( - pos == (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */ - "`fio_str_utf8_select` error, negative position invalid! (%zd)", - (ssize_t)pos); - FIO_ASSERT(len == 10, - "`fio_str_utf8_select` error, trancated length invalid! (%zd)", - (ssize_t)len); - pos = 10; - len = 20; - FIO_ASSERT(fio_str_utf8_select(s, &pos, &len) == 0, - "`fio_str_utf8_select` returned error! (%zd, %zu)", - (ssize_t)pos, len); - FIO_ASSERT(pos == 10, - "`fio_str_utf8_select` error, position invalid! (%zd)", - (ssize_t)pos); - FIO_ASSERT(len == 20, - "`fio_str_utf8_select` error, length invalid! (%zd)", - (ssize_t)len); - } - fprintf(stderr, "* reviewing reference counting `fio_str_free2` (2/2).\n"); - fio_str_free2(s); - fprintf(stderr, "* finished reference counting test.\n"); - } - fio_str_free(&str); - if (1) { - - const char *utf8_sample = /* three hearts, small-big-small*/ - "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95"; - fio_str_write(&str, utf8_sample, strlen(utf8_sample)); - intptr_t pos = -2; - size_t len = 2; - FIO_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0, - "`fio_str_utf8_select` returned error for negative pos on " - "UTF-8 data! (%zd, %zu)", - (ssize_t)pos, len); - FIO_ASSERT(pos == (intptr_t)fio_str_len(&str) - 4, /* 4 byte emoji */ - "`fio_str_utf8_select` error, negative position invalid on " - "UTF-8 data! (%zd)", - (ssize_t)pos); - FIO_ASSERT(len == 4, /* last utf-8 char is 4 byte long */ - "`fio_str_utf8_select` error, trancated length invalid on " - "UTF-8 data! (%zd)", - (ssize_t)len); - pos = 1; - len = 20; - FIO_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0, - "`fio_str_utf8_select` returned error on UTF-8 data! (%zd, %zu)", - (ssize_t)pos, len); - FIO_ASSERT( - pos == 4, - "`fio_str_utf8_select` error, position invalid on UTF-8 data! (%zd)", - (ssize_t)pos); - FIO_ASSERT( - len == 10, - "`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)", - (ssize_t)len); - pos = 1; - len = 3; - FIO_ASSERT( - fio_str_utf8_select(&str, &pos, &len) == 0, - "`fio_str_utf8_select` returned error on UTF-8 data (2)! (%zd, %zu)", - (ssize_t)pos, len); - FIO_ASSERT( - len == 10, /* 3 UTF-8 chars: 4 byte + 4 byte + 2 byte codes == 10 */ - "`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)", - (ssize_t)len); - } - fio_str_free(&str); - if (1) { - str = FIO_STR_INIT_STATIC("Welcome"); - FIO_ASSERT(fio_str_capa(&str) == 0, "Static string capacity non-zero."); - FIO_ASSERT(fio_str_len(&str) > 0, - "Static string length should be automatically calculated."); - FIO_ASSERT(str.dealloc == NULL, - "Static string deallocation function should be NULL."); - fio_str_free(&str); - str = FIO_STR_INIT_STATIC("Welcome"); - fio_str_info_s state = fio_str_write(&str, " Home", 5); - FIO_ASSERT(state.capa > 0, "Static string not converted to non-static."); - FIO_ASSERT(str.dealloc, "Missing static string deallocation function" - " after `fio_str_write`."); - - fprintf(stderr, "* reviewing `fio_str_detach`.\n (%zu): %s\n", - fio_str_info(&str).len, fio_str_info(&str).data); - char *cstr = fio_str_detach(&str); - FIO_ASSERT(cstr, "`fio_str_detach` returned NULL"); - FIO_ASSERT(!memcmp(cstr, "Welcome Home\0", 13), - "`fio_str_detach` string error: %s", cstr); - fio_free(cstr); - FIO_ASSERT(fio_str_len(&str) == 0, "`fio_str_detach` data wasn't cleared."); - // fio_str_free(&str); - } - fprintf(stderr, "* passed.\n"); -} -#endif - -/* ***************************************************************************** -Testing Memory Allocator -***************************************************************************** */ - -#if FIO_FORCE_MALLOC -#define fio_malloc_test() \ - fprintf(stderr, "\n=== SKIPPED facil.io memory allocator (bypassed)\n"); -#else -FIO_FUNC void fio_malloc_test(void) { - fprintf(stderr, "\n=== Testing facil.io memory allocator's system calls\n"); - char *mem = sys_alloc(FIO_MEMORY_BLOCK_SIZE, 0); - FIO_ASSERT(mem, "sys_alloc failed to allocate memory!\n"); - FIO_ASSERT(!((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK), - "Memory allocation not aligned to FIO_MEMORY_BLOCK_SIZE!"); - mem[0] = 'a'; - mem[FIO_MEMORY_BLOCK_SIZE - 1] = 'z'; - fprintf(stderr, "* Testing reallocation from %p\n", (void *)mem); - char *mem2 = - sys_realloc(mem, FIO_MEMORY_BLOCK_SIZE, FIO_MEMORY_BLOCK_SIZE * 2); - if (mem == mem2) - fprintf(stderr, "* Performed system realloc without copy :-)\n"); - FIO_ASSERT(mem2[0] == 'a' && mem2[FIO_MEMORY_BLOCK_SIZE - 1] == 'z', - "Reaclloc data was lost!"); - sys_free(mem2, FIO_MEMORY_BLOCK_SIZE * 2); - fprintf(stderr, "=== Testing facil.io memory allocator's internal data.\n"); - FIO_ASSERT(arenas, "Missing arena data - library not initialized!"); - fio_free(NULL); /* fio_free(NULL) shouldn't crash... */ - mem = fio_malloc(1); - FIO_ASSERT(mem, "fio_malloc failed to allocate memory!\n"); - FIO_ASSERT(!((uintptr_t)mem & 15), "fio_malloc memory not aligned!\n"); - FIO_ASSERT(((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) != 16, - "small fio_malloc memory indicates system allocation!\n"); - mem[0] = 'a'; - FIO_ASSERT(mem[0] == 'a', "allocate memory wasn't written to!\n"); - mem = fio_realloc(mem, 1); - FIO_ASSERT(mem, "fio_realloc failed!\n"); - FIO_ASSERT(mem[0] == 'a', "fio_realloc memory wasn't copied!\n"); - pthread_once(&arena_last_used_once, init_arena_last_used_key); - arena_s *arena_last_used = pthread_getspecific(arena_last_used_key); - FIO_ASSERT(arena_last_used, "arena_last_used wasn't initialized!\n"); - fio_free(mem); - block_s *b = arena_last_used->block; - - /* move arena to block's start */ - while (arena_last_used->block == b) { - mem = fio_malloc(1); - FIO_ASSERT(mem, "fio_malloc failed to allocate memory!\n"); - fio_free(mem); - } - /* make sure a block is assigned */ - fio_free(fio_malloc(1)); - b = arena_last_used->block; - size_t count = 1; - /* count allocations within block */ - do { - FIO_ASSERT(mem, "fio_malloc failed to allocate memory!\n"); - FIO_ASSERT(!((uintptr_t)mem & 15), - "fio_malloc memory not aligned at allocation #%zu!\n", count); - FIO_ASSERT((((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) != 16), - "fio_malloc memory indicates system allocation!\n"); -#if __x86_64__ - fio_memcpy((size_t *)mem, (size_t *)"0123456789abcdefg", 1); -#else - mem[0] = 'a'; -#endif - fio_free(mem); /* make sure we hold on to the block, so it rotates */ - mem = fio_malloc(1); - ++count; - } while (arena_last_used->block == b); - { - fprintf(stderr, "* Confirm block address: %p, last allocation was %p\n", - (void *)arena_last_used->block, (void *)mem); - fprintf( - stderr, - "* Performed %zu allocations out of expected %zu allocations per " - "block.\n", - count, - (size_t)((FIO_MEMORY_BLOCK_SLICES - 2) - (sizeof(block_s) >> 4) - 1)); - fio_ls_embd_s old_memory_list = memory.available; - fio_free(mem); - FIO_ASSERT(fio_ls_embd_any(&memory.available), - "memory pool empty (memory block wasn't freed)!\n"); - FIO_ASSERT(old_memory_list.next != memory.available.next || - memory.available.prev != old_memory_list.prev, - "memory pool not updated after block being freed!\n"); - } - /* rotate block again */ - b = arena_last_used->block; - mem = fio_realloc(mem, 1); - do { - mem2 = mem; - mem = fio_malloc(1); - fio_free(mem2); /* make sure we hold on to the block, so it rotates */ - FIO_ASSERT(mem, "fio_malloc failed to allocate memory!\n"); - FIO_ASSERT(!((uintptr_t)mem & 15), - "fio_malloc memory not aligned at allocation #%zu!\n", count); - FIO_ASSERT((((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) != 16), - "fio_malloc memory indicates system allocation!\n"); -#if __x86_64__ - fio_memcpy((size_t *)mem, (size_t *)"0123456789abcdefg", 1); -#else - mem[0] = 'a'; -#endif - ++count; - } while (arena_last_used->block == b); - - mem2 = mem; - mem = fio_calloc(FIO_MEMORY_BLOCK_ALLOC_LIMIT - 64, 1); - fio_free(mem2); - FIO_ASSERT(mem, - "failed to allocate FIO_MEMORY_BLOCK_ALLOC_LIMIT - 64 bytes!\n"); - FIO_ASSERT(((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) != 16, - "fio_calloc (under limit) memory alignment error!\n"); - mem2 = fio_malloc(1); - FIO_ASSERT(mem2, "fio_malloc(1) failed to allocate memory!\n"); - mem2[0] = 'a'; - - for (uintptr_t i = 0; i < (FIO_MEMORY_BLOCK_ALLOC_LIMIT - 64); ++i) { - FIO_ASSERT(mem[i] == 0, - "calloc returned memory that wasn't initialized?!\n"); - } - fio_free(mem); - - mem = fio_malloc(FIO_MEMORY_BLOCK_SIZE); - FIO_ASSERT(mem, "fio_malloc failed to FIO_MEMORY_BLOCK_SIZE bytes!\n"); - FIO_ASSERT(((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) == 16, - "fio_malloc (big) memory isn't aligned!\n"); - mem = fio_realloc(mem, FIO_MEMORY_BLOCK_SIZE * 2); - FIO_ASSERT(mem, - "fio_realloc (big) failed on FIO_MEMORY_BLOCK_SIZE X2 bytes!\n"); - fio_free(mem); - FIO_ASSERT(((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) == 16, - "fio_realloc (big) memory isn't aligned!\n"); - - { - void *m0 = fio_malloc(0); - void *rm0 = fio_realloc(m0, 16); - FIO_ASSERT(m0 != rm0, "fio_realloc(fio_malloc(0), 16) failed!\n"); - fio_free(rm0); - } - { - size_t pool_size = 0; - FIO_LS_EMBD_FOR(&memory.available, node) { ++pool_size; } - mem = fio_mmap(512); - FIO_ASSERT(mem, "fio_mmap allocation failed!\n"); - fio_free(mem); - size_t new_pool_size = 0; - FIO_LS_EMBD_FOR(&memory.available, node) { ++new_pool_size; } - FIO_ASSERT(new_pool_size == pool_size, - "fio_free of fio_mmap went to memory pool!\n"); - } - - fprintf(stderr, "* passed.\n"); -} -#endif - -/* ***************************************************************************** -Testing Core Callback add / remove / ensure -***************************************************************************** */ - -FIO_FUNC void fio_state_callback_test_task(void *pi) { - ((uintptr_t *)pi)[0] += 1; -} - -#define FIO_STATE_TEST_COUNT 10 -FIO_FUNC void fio_state_callback_order_test_task(void *pi) { - static uintptr_t start = FIO_STATE_TEST_COUNT; - --start; - FIO_ASSERT((uintptr_t)pi == start, - "Callback order error, expecting %zu, got %zu", (size_t)start, - (size_t)pi); -} - -FIO_FUNC void fio_state_callback_test(void) { - fprintf(stderr, "=== Testing facil.io workflow state callback system\n"); - uintptr_t result = 0; - uintptr_t other = 0; - fio_state_callback_add(FIO_CALL_NEVER, fio_state_callback_test_task, &result); - FIO_ASSERT(callback_collection[FIO_CALL_NEVER].callbacks.next, - "Callback list failed to initialize."); - fio_state_callback_force(FIO_CALL_NEVER); - FIO_ASSERT(result == 1, "Callback wasn't called!"); - fio_state_callback_force(FIO_CALL_NEVER); - FIO_ASSERT(result == 2, "Callback wasn't called (second time)!"); - fio_state_callback_remove(FIO_CALL_NEVER, fio_state_callback_test_task, - &result); - fio_state_callback_force(FIO_CALL_NEVER); - FIO_ASSERT(result == 2, "Callback wasn't removed!"); - fio_state_callback_add(FIO_CALL_NEVER, fio_state_callback_test_task, &result); - fio_state_callback_add(FIO_CALL_NEVER, fio_state_callback_test_task, &other); - fio_state_callback_clear(FIO_CALL_NEVER); - fio_state_callback_force(FIO_CALL_NEVER); - FIO_ASSERT(result == 2 && other == 0, "Callbacks werent cleared!"); - for (uintptr_t i = 0; i < FIO_STATE_TEST_COUNT; ++i) { - fio_state_callback_add(FIO_CALL_NEVER, fio_state_callback_order_test_task, - (void *)i); - } - fio_state_callback_force(FIO_CALL_NEVER); - fio_state_callback_clear(FIO_CALL_NEVER); - fprintf(stderr, "* passed.\n"); -} -#undef FIO_STATE_TEST_COUNT -/* ***************************************************************************** -Testing fio_timers -***************************************************************************** */ - -FIO_FUNC void fio_timer_test_task(void *arg) { ++(((size_t *)arg)[0]); } - -FIO_FUNC void fio_timer_test(void) { - fprintf(stderr, "=== Testing facil.io timer system\n"); - size_t result = 0; - const size_t total = 5; - fio_data->active = 1; - FIO_ASSERT(fio_timers.next, "Timers not initialized!"); - FIO_ASSERT(fio_run_every(0, 0, fio_timer_test_task, NULL, NULL) == -1, - "Timers without an interval should be an error."); - FIO_ASSERT(fio_run_every(1000, 0, NULL, NULL, NULL) == -1, - "Timers without a task should be an error."); - FIO_ASSERT(fio_run_every(900, total, fio_timer_test_task, &result, - fio_timer_test_task) == 0, - "Timer creation failure."); - FIO_ASSERT(fio_ls_embd_any(&fio_timers), - "Timer scheduling failure - no timer in list."); - FIO_ASSERT(fio_timer_calc_first_interval() >= 898 && - fio_timer_calc_first_interval() <= 902, - "next timer calculation error %zu", - fio_timer_calc_first_interval()); - - fio_ls_embd_s *first = fio_timers.next; - FIO_ASSERT(fio_run_every(10000, total, fio_timer_test_task, &result, - fio_timer_test_task) == 0, - "Timer creation failure (second timer)."); - FIO_ASSERT(fio_timers.next == first, "Timer Ordering error!"); - - FIO_ASSERT(fio_timer_calc_first_interval() >= 898 && - fio_timer_calc_first_interval() <= 902, - "next timer calculation error (after added timer) %zu", - fio_timer_calc_first_interval()); - - fio_data->last_cycle.tv_nsec += 800; - fio_timer_schedule(); - fio_defer_perform(); - FIO_ASSERT(result == 0, "Timer filtering error (%zu != 0)\n", result); - - for (size_t i = 0; i < total; ++i) { - fio_data->last_cycle.tv_sec += 1; - // fio_data->last_cycle.tv_nsec += 1; - fio_timer_schedule(); - fio_defer_perform(); - FIO_ASSERT(((i != total - 1 && result == i + 1) || - (i == total - 1 && result == total + 1)), - "Timer running and rescheduling error (%zu != %zu)\n", result, - i + 1); - FIO_ASSERT(fio_timers.next == first || i == total - 1, - "Timer Ordering error on cycle %zu!", i); - } - - fio_data->last_cycle.tv_sec += 10; - fio_timer_schedule(); - fio_defer_perform(); - FIO_ASSERT(result == total + 2, "Timer # 2 error (%zu != %zu)\n", result, - total + 2); - fio_data->active = 0; - fio_timer_clear_all(); - fio_defer_clear_tasks(); - fprintf(stderr, "* passed.\n"); -} - -/* ***************************************************************************** -Testing listening socket -***************************************************************************** */ - -FIO_FUNC void fio_socket_test(void) { - /* initialize unix socket name */ - fio_str_s sock_name = FIO_STR_INIT; -#ifdef P_tmpdir - fio_str_write(&sock_name, P_tmpdir, strlen(P_tmpdir)); - if (fio_str_len(&sock_name) && - fio_str_data(&sock_name)[fio_str_len(&sock_name) - 1] == '/') - fio_str_resize(&sock_name, fio_str_len(&sock_name) - 1); -#else - fio_str_write(&sock_name, "/tmp", 4); -#endif - fio_str_printf(&sock_name, "/fio_test_sock-%d.sock", (int)getpid()); - - fprintf(stderr, "=== Testing facil.io listening socket creation (partial " - "testing only).\n"); -#ifdef __MINGW32__ - fprintf(stderr, "* testing on TCP/IP port 8765\n"); - intptr_t uuid; - intptr_t client1; - intptr_t client2; -#else - fprintf(stderr, "* testing on TCP/IP port 8765 and Unix socket: %s\n", - fio_str_data(&sock_name)); - intptr_t uuid = fio_socket(fio_str_data(&sock_name), NULL, 1); - FIO_ASSERT(uuid != -1, "Failed to open unix socket\n"); - FIO_ASSERT(uuid_data(uuid).open, "Unix socket not initialized"); - intptr_t client1 = fio_socket(fio_str_data(&sock_name), NULL, 0); - FIO_ASSERT(client1 != -1, "Failed to connect to unix socket."); - intptr_t client2 = fio_accept(uuid); - FIO_ASSERT(client2 != -1, "Failed to accept unix socket connection."); - fprintf(stderr, "* Unix server addr %s\n", fio_peer_addr(uuid).data); - fprintf(stderr, "* Unix client1 addr %s\n", fio_peer_addr(client1).data); - fprintf(stderr, "* Unix client2 addr %s\n", fio_peer_addr(client2).data); - { - char tmp_buf[28]; - ssize_t r = -1; - ssize_t timer_junk; - fio_write(client1, "Hello World", 11); - if (0) { - /* packet may have been sent synchronously, don't test */ - if (!uuid_data(client1).packet) - unlink(__FILE__ ".sock"); - FIO_ASSERT(uuid_data(client1).packet, "fio_write error, no packet!") - } - /* prevent poll from hanging */ - fio_run_every(5, 1, fio_timer_test_task, &timer_junk, fio_timer_test_task); - errno = EAGAIN; - for (size_t i = 0; i < 100 && r <= 0 && - (r == 0 || errno == EAGAIN || errno == EWOULDBLOCK); - ++i) { - fio_poll(); - fio_defer_perform(); - fio_reschedule_thread(); - errno = 0; - r = fio_read(client2, tmp_buf, 28); - } - if (!(r > 0 && r <= 28) || memcmp("Hello World", tmp_buf, r)) { - perror("* ernno"); - unlink(__FILE__ ".sock"); - } - FIO_ASSERT(r > 0 && r <= 28, - "Failed to read from unix socket " __FILE__ ".sock %zd", r); - FIO_ASSERT(!memcmp("Hello World", tmp_buf, r), - "Unix socket Read/Write cycle error (%zd: %.*s)", r, (int)r, - tmp_buf); - fprintf(stderr, "* Unix socket Read/Write cycle passed: %.*s\n", (int)r, - tmp_buf); - fio_data->last_cycle.tv_sec += 10; - fio_timer_clear_all(); - } - - fio_force_close(client1); - fio_force_close(client2); - fio_force_close(uuid); - unlink(fio_str_data(&sock_name)); - /* free unix socket name */ - fio_str_free(&sock_name); -#endif - - uuid = fio_socket(NULL, "8765", 1); - FIO_ASSERT(uuid != -1, "Failed to open TCP/IP socket on port 8765"); - FIO_ASSERT(uuid_data(uuid).open, "TCP/IP socket not initialized"); - fprintf(stderr, "* TCP/IP server addr %s\n", fio_peer_addr(uuid).data); - client1 = fio_socket("Localhost", "8765", 0); - FIO_ASSERT(client1 != -1, "Failed to connect to TCP/IP socket on port 8765"); - fprintf(stderr, "* TCP/IP client1 addr %s\n", fio_peer_addr(client1).data); - errno = EAGAIN; - for (size_t i = 0; i < 100 && (errno == EAGAIN || errno == EWOULDBLOCK); - ++i) { - errno = 0; - fio_reschedule_thread(); - client2 = fio_accept(uuid); - } - if (client2 == -1) - perror("accept error"); - FIO_ASSERT(client2 != -1, - "Failed to accept TCP/IP socket connection on port 8765"); - fprintf(stderr, "* TCP/IP client2 addr %s\n", fio_peer_addr(client2).data); - fio_force_close(client1); - fio_force_close(client2); - fio_force_close(uuid); - fio_timer_clear_all(); - fio_defer_clear_tasks(); - fprintf(stderr, "* passed.\n"); -} - -/* ***************************************************************************** -Testing listening socket -***************************************************************************** */ - -FIO_FUNC void fio_cycle_test_task(void *arg) { - fio_stop(); - (void)arg; -} -FIO_FUNC void fio_cycle_test_task2(void *arg) { - fprintf(stderr, "* facil.io cycling test fatal error!\n"); - exit(-1); - (void)arg; -} - -FIO_FUNC void fio_cycle_test(void) { - fprintf(stderr, - "=== Testing facil.io cycling logic (partial - only tests timers)\n"); - fio_mark_time(); - fio_timer_clear_all(); - struct timespec start = fio_last_tick(); - fio_run_every(1000, 1, fio_cycle_test_task, NULL, NULL); - fio_run_every(10000, 1, fio_cycle_test_task2, NULL, NULL); - fio_start(.threads = 1, .workers = 1); - struct timespec end = fio_last_tick(); - fio_timer_clear_all(); - FIO_ASSERT(end.tv_sec == start.tv_sec + 1 || end.tv_sec == start.tv_sec + 2, - "facil.io cycling error?"); - fprintf(stderr, "* passed.\n"); -} -/* ***************************************************************************** -Testing fio_defer task system -***************************************************************************** */ - -#define FIO_DEFER_TOTAL_COUNT (512 * 1024) - -#ifndef FIO_DEFER_TEST_PRINT -#define FIO_DEFER_TEST_PRINT 0 -#endif - -FIO_FUNC void sample_task(void *i_count, void *unused2) { - (void)(unused2); - fio_atomic_add((uintptr_t *)i_count, 1); -} - -FIO_FUNC void sched_sample_task(void *count, void *i_count) { - for (size_t i = 0; i < (uintptr_t)count; i++) { - fio_defer(sample_task, i_count, NULL); - } -} - -FIO_FUNC void fio_defer_test(void) { - const size_t cpu_cores = fio_detect_cpu_cores(); - FIO_ASSERT(cpu_cores, "couldn't detect CPU cores!"); - uintptr_t i_count; - clock_t start, end; - fprintf(stderr, "=== Testing facil.io task scheduling (fio_defer)\n"); - FIO_ASSERT(!fio_defer_has_queue(), "facil.io queue always active.") - i_count = 0; - start = clock(); - for (size_t i = 0; i < FIO_DEFER_TOTAL_COUNT; i++) { - sample_task(&i_count, NULL); - } - end = clock(); - if (FIO_DEFER_TEST_PRINT) { - fprintf(stderr, - "Deferless (direct call) counter: %lu cycles with i_count = %lu, " - "%lu/%lu free/malloc\n", - (unsigned long)(end - start), (unsigned long)i_count, - (unsigned long)fio_defer_count_dealloc, - (unsigned long)fio_defer_count_alloc); - } - size_t i_count_should_be = i_count; - - if (FIO_DEFER_TEST_PRINT) { - fprintf(stderr, "\n"); - } - - for (size_t i = 1; FIO_DEFER_TOTAL_COUNT >> i; ++i) { - i_count = 0; - const size_t per_task = FIO_DEFER_TOTAL_COUNT >> i; - const size_t tasks = 1 << i; - start = clock(); - for (size_t j = 0; j < tasks; ++j) { - fio_defer(sched_sample_task, (void *)per_task, &i_count); - } - FIO_ASSERT(fio_defer_has_queue(), "facil.io queue not marked.") - fio_defer_thread_pool_join(fio_defer_thread_pool_new((i % cpu_cores) + 1)); - end = clock(); - if (FIO_DEFER_TEST_PRINT) { - fprintf(stderr, - "- Defer %zu threads, %zu scheduling loops (%zu each):\n" - " %lu cycles with i_count = %lu, %lu/%lu " - "free/malloc\n", - ((i % cpu_cores) + 1), tasks, per_task, - (unsigned long)(end - start), (unsigned long)i_count, - (unsigned long)fio_defer_count_dealloc, - (unsigned long)fio_defer_count_alloc); - } else { - fprintf(stderr, "."); - } - FIO_ASSERT(i_count == i_count_should_be, "ERROR: defer count invalid\n"); - FIO_ASSERT(fio_defer_count_dealloc == fio_defer_count_alloc, - "defer deallocation vs. allocation error, %zu != %zu", - fio_defer_count_dealloc, fio_defer_count_alloc); - } - FIO_ASSERT(task_queue_normal.writer == &task_queue_normal.static_queue, - "defer library didn't release dynamic queue (should be static)"); - fprintf(stderr, "\n* passed.\n"); -} - -/* ***************************************************************************** -Array data-structure Testing -***************************************************************************** */ - -typedef struct { - int i; - char c; -} fio_ary_test_type_s; - -#define FIO_ARY_NAME fio_i_ary -#define FIO_ARY_TYPE uintptr_t -#include "fio.h" - -FIO_FUNC intptr_t ary_alloc_counter = 0; -FIO_FUNC void copy_s(fio_ary_test_type_s *d, fio_ary_test_type_s *s) { - ++ary_alloc_counter; - *d = *s; -} - -#define FIO_ARY_NAME fio_s_ary -#define FIO_ARY_TYPE fio_ary_test_type_s -#define FIO_ARY_COPY(dest, src) copy_s(&(dest), &(src)) -#define FIO_ARY_COMPARE(dest, src) ((dest).i == (src).i && (dest).c == (src).c) -#define FIO_ARY_DESTROY(obj) (--ary_alloc_counter) -#include "fio.h" - -FIO_FUNC void fio_ary_test(void) { - /* code */ - fio_i_ary__test(); - fio_s_ary__test(); - FIO_ASSERT(!ary_alloc_counter, "array object deallocation error, %ld != 0", - ary_alloc_counter); -} - -/* ***************************************************************************** -Set data-structure Testing -***************************************************************************** */ - -#define FIO_SET_TEST_COUNT 524288UL - -#define FIO_SET_NAME fio_set_test -#define FIO_SET_OBJ_TYPE uintptr_t -#include - -#define FIO_SET_NAME fio_hash_test -#define FIO_SET_KEY_TYPE uintptr_t -#define FIO_SET_OBJ_TYPE uintptr_t -#include - -#define FIO_SET_NAME fio_set_attack -#define FIO_SET_OBJ_COMPARE(a, b) ((a) == (b)) -#define FIO_SET_OBJ_TYPE uintptr_t -#include - -FIO_FUNC void fio_set_test(void) { - fio_set_test_s s = FIO_SET_INIT; - fio_hash_test_s h = FIO_SET_INIT; - fprintf( - stderr, - "=== Testing Core ordered Set (re-including fio.h with FIO_SET_NAME)\n"); - fprintf(stderr, "* Inserting %lu items\n", FIO_SET_TEST_COUNT); - - FIO_ASSERT(fio_set_test_count(&s) == 0, "empty set should have zero objects"); - FIO_ASSERT(fio_set_test_capa(&s) == 0, "empty set should have no capacity"); - FIO_ASSERT(fio_hash_test_capa(&h) == 0, "empty hash should have no capacity"); - FIO_ASSERT(!fio_set_test_is_fragmented(&s), - "empty set shouldn't be considered fragmented"); - FIO_ASSERT(!fio_hash_test_is_fragmented(&h), - "empty hash shouldn't be considered fragmented"); - FIO_ASSERT(!fio_set_test_last(&s), "empty set shouldn't have a last object"); - FIO_ASSERT(!fio_hash_test_last(&h).key && !fio_hash_test_last(&h).obj, - "empty hash shouldn't have a last object"); - - for (uintptr_t i = 1; i < FIO_SET_TEST_COUNT; ++i) { - fio_set_test_insert(&s, i, i); - fio_hash_test_insert(&h, i, i, i + 1, NULL); - FIO_ASSERT(fio_set_test_find(&s, i, i), "set find failed after insert"); - FIO_ASSERT(fio_hash_test_find(&h, i, i), "hash find failed after insert"); - FIO_ASSERT(i == fio_set_test_find(&s, i, i), "set insertion != find"); - FIO_ASSERT(i + 1 == fio_hash_test_find(&h, i, i), "hash insertion != find"); - } - fprintf(stderr, "* Seeking %lu items\n", FIO_SET_TEST_COUNT); - for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; ++i) { - FIO_ASSERT((i == fio_set_test_find(&s, i, i)), - "set insertion != find (seek)"); - FIO_ASSERT((i + 1 == fio_hash_test_find(&h, i, i)), - "hash insertion != find (seek)"); - } - { - fprintf(stderr, "* Testing order for %lu items in set\n", - FIO_SET_TEST_COUNT); - uintptr_t i = 1; - FIO_SET_FOR_LOOP(&s, pos) { - FIO_ASSERT(pos->obj == i, "object order mismatch %lu != %lu.", - (unsigned long)i, (unsigned long)pos->obj); - ++i; - } - } - { - fprintf(stderr, "* Testing order for %lu items in hash\n", - FIO_SET_TEST_COUNT); - uintptr_t i = 1; - FIO_SET_FOR_LOOP(&h, pos) { - FIO_ASSERT(pos->obj.obj == i + 1 && pos->obj.key == i, - "object order mismatch %lu != %lu.", (unsigned long)i, - (unsigned long)pos->obj.key); - ++i; - } - } - - fprintf(stderr, "* Removing odd items from %lu items\n", FIO_SET_TEST_COUNT); - for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; i += 2) { - fio_set_test_remove(&s, i, i, NULL); - fio_hash_test_remove(&h, i, i, NULL); - FIO_ASSERT(!(fio_set_test_find(&s, i, i)), - "Removal failed in set (still exists)."); - FIO_ASSERT(!(fio_hash_test_find(&h, i, i)), - "Removal failed in hash (still exists)."); - } - { - fprintf(stderr, "* Testing for %lu / 2 holes\n", FIO_SET_TEST_COUNT); - uintptr_t i = 1; - FIO_SET_FOR_LOOP(&s, pos) { - if (pos->hash == 0) { - FIO_ASSERT((i & 1) == 1, "deleted object wasn't odd"); - } else { - FIO_ASSERT(pos->obj == i, "deleted object value mismatch %lu != %lu", - (unsigned long)i, (unsigned long)pos->obj); - } - ++i; - } - i = 1; - FIO_SET_FOR_LOOP(&h, pos) { - if (pos->hash == 0) { - FIO_ASSERT((i & 1) == 1, "deleted object wasn't odd"); - } else { - FIO_ASSERT(pos->obj.key == i, - "deleted object value mismatch %lu != %lu", (unsigned long)i, - (unsigned long)pos->obj.key); - } - ++i; - } - { - fprintf(stderr, "* Poping two elements (testing pop through holes)\n"); - FIO_ASSERT(fio_set_test_last(&s), "Pop `last` 1 failed - no last object"); - uintptr_t tmp = fio_set_test_last(&s); - FIO_ASSERT(tmp, "Pop set `last` 1 failed to collect object"); - fio_set_test_pop(&s); - FIO_ASSERT( - fio_set_test_last(&s) != tmp, - "Pop `last` 2 in set same as `last` 1 - failed to collect object"); - tmp = fio_hash_test_last(&h).key; - FIO_ASSERT(tmp, "Pop hash `last` 1 failed to collect object"); - fio_hash_test_pop(&h); - FIO_ASSERT( - fio_hash_test_last(&h).key != tmp, - "Pop `last` 2 in hash same as `last` 1 - failed to collect object"); - FIO_ASSERT(fio_set_test_last(&s), "Pop `last` 2 failed - no last object"); - FIO_ASSERT(fio_hash_test_last(&h).obj, - "Pop `last` 2 failed in hash - no last object"); - fio_set_test_pop(&s); - fio_hash_test_pop(&h); - } - if (1) { - uintptr_t tmp = 1; - fio_set_test_remove(&s, tmp, tmp, NULL); - fio_hash_test_remove(&h, tmp, tmp, NULL); - size_t count = s.count; - fio_set_test_overwrite(&s, tmp, tmp, NULL); - FIO_ASSERT( - count + 1 == s.count, - "Re-adding a removed item in set should increase count by 1 (%zu + " - "1 != %zu).", - count, (size_t)s.count); - count = h.count; - fio_hash_test_insert(&h, tmp, tmp, tmp, NULL); - FIO_ASSERT( - count + 1 == h.count, - "Re-adding a removed item in hash should increase count by 1 (%zu + " - "1 != %zu).", - count, (size_t)s.count); - tmp = fio_set_test_find(&s, tmp, tmp); - FIO_ASSERT(tmp == 1, - "Re-adding a removed item should update the item in the set " - "(%lu != 1)!", - (unsigned long)fio_set_test_find(&s, tmp, tmp)); - fio_set_test_remove(&s, tmp, tmp, NULL); - fio_hash_test_remove(&h, tmp, tmp, NULL); - FIO_ASSERT(count == h.count, - "Re-removing an item should decrease count (%zu != %zu).", - count, (size_t)s.count); - FIO_ASSERT(!fio_set_test_find(&s, tmp, tmp), - "Re-removing a re-added item should update the item!"); - } - } - fprintf(stderr, "* Compacting HashMap to %lu\n", FIO_SET_TEST_COUNT >> 1); - fio_set_test_compact(&s); - { - fprintf(stderr, "* Testing that %lu items are continuous\n", - FIO_SET_TEST_COUNT >> 1); - uintptr_t i = 0; - FIO_SET_FOR_LOOP(&s, pos) { - FIO_ASSERT(pos->hash != 0, "Found a hole after compact."); - ++i; - } - FIO_ASSERT(i == s.count, "count error (%lu != %lu).", i, s.count); - } - - fio_set_test_free(&s); - fio_hash_test_free(&h); - FIO_ASSERT(!s.map && !s.ordered && !s.pos && !s.capa, - "HashMap not re-initialized after free."); - - fio_set_test_capa_require(&s, FIO_SET_TEST_COUNT); - - FIO_ASSERT( - s.map && s.ordered && !s.pos && s.capa >= FIO_SET_TEST_COUNT, - "capa_require changes state in a bad way (%p, %p, %zu, %zu ?>= %zu)", - (void *)s.map, (void *)s.ordered, s.pos, s.capa, FIO_SET_TEST_COUNT); - - for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; ++i) { - fio_set_test_insert(&s, i, i); - FIO_ASSERT(fio_set_test_find(&s, i, i), - "find failed after insert (2nd round)"); - FIO_ASSERT(i == fio_set_test_find(&s, i, i), - "insertion (2nd round) != find"); - FIO_ASSERT(i == s.count, "count error (%lu != %lu) post insertion.", i, - s.count); - } - fio_set_test_free(&s); - /* full/partial collision attack against set and test response */ - if (1) { - fio_set_attack_s as = FIO_SET_INIT; - time_t start_ok = clock(); - for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) { - fio_set_attack_insert(&as, i, i + 1); - FIO_ASSERT(fio_set_attack_find(&as, i, i + 1) == i + 1, - "set attack verctor failed sanity test (seek != insert)"); - } - time_t end_ok = clock(); - FIO_ASSERT(fio_set_attack_count(&as) == FIO_SET_TEST_COUNT, - "set attack verctor failed sanity test (count error %zu != %zu)", - fio_set_attack_count(&as), FIO_SET_TEST_COUNT); - fio_set_attack_free(&as); - - /* full collision attack */ - time_t start_bad = clock(); - for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) { - fio_set_attack_insert(&as, 1, i + 1); - } - time_t end_bad = clock(); - FIO_ASSERT(fio_set_attack_count(&as) != FIO_SET_TEST_COUNT, - "set attack success! too many full-collisions inserts!"); - FIO_LOG_DEBUG("set full-collision attack final count/capa = %zu / %zu", - fio_set_attack_count(&as), fio_set_attack_capa(&as)); - FIO_LOG_DEBUG("set full-collision attack timing impact (attack vs. normal) " - "%zu vs. %zu", - end_bad - start_bad, end_ok - start_ok); - fio_set_attack_free(&as); - - /* partial collision attack */ - start_bad = clock(); - for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) { - fio_set_attack_insert(&as, ((i << 20) | 1), i + 1); - } - end_bad = clock(); - FIO_ASSERT(fio_set_attack_count(&as) == FIO_SET_TEST_COUNT, - "partial collision resolusion failed, not enough inserts!"); - FIO_LOG_DEBUG("set partial collision attack final count/capa = %zu / %zu", - fio_set_attack_count(&as), fio_set_attack_capa(&as)); - FIO_LOG_DEBUG("set partial collision attack timing impact (attack vs. " - "normal) %zu vs. %zu", - end_bad - start_bad, end_ok - start_ok); - fio_set_attack_free(&as); - } -} - -/* ***************************************************************************** -Bad Hash (risky hash) tests -***************************************************************************** */ - -FIO_FUNC void fio_riskyhash_speed_test(void) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - uint8_t buffer[8192]; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - uint64_t hash = 0; - for (size_t i = 0; i < 4; i++) { - hash += fio_risky_hash(buffer, 8192, 1); - memcpy(buffer, &hash, sizeof(hash)); - } - /* loop until test runs for more than 2 seconds */ - for (uint64_t cycles = 8192;;) { - clock_t start, end; - start = clock(); - for (size_t i = cycles; i > 0; i--) { - hash += fio_risky_hash(buffer, 8192, 1); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - memcpy(buffer, &hash, sizeof(hash)); - if ((end - start) >= (2 * CLOCKS_PER_SEC) || - cycles >= ((uint64_t)1 << 62)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio_risky_hash", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 2; - } -} - -FIO_FUNC void fio_riskyhash_test(void) { - fprintf(stderr, "===================================\n"); -#if NODEBUG - fio_riskyhash_speed_test(); -#else - fprintf(stderr, "fio_risky_hash speed test skipped (debug mode is slow)\n"); - fio_str_info_s str1 = - (fio_str_info_s){.data = "nothing_is_really_here1", .len = 23}; - fio_str_info_s str2 = - (fio_str_info_s){.data = "nothing_is_really_here2", .len = 23}; - fio_str_s copy = FIO_STR_INIT; - FIO_ASSERT(fio_risky_hash(str1.data, str1.len, 1) != - fio_risky_hash(str2.data, str2.len, 1), - "Different strings should have a different risky hash"); - fio_str_write(©, str1.data, str1.len); - FIO_ASSERT(fio_risky_hash(str1.data, str1.len, 1) == - fio_risky_hash(fio_str_data(©), fio_str_len(©), 1), - "Same string values should have the same risky hash"); - fio_str_free(©); - (void)fio_riskyhash_speed_test; -#endif -} - -/* ***************************************************************************** -SipHash tests -***************************************************************************** */ - -FIO_FUNC void fio_siphash_speed_test(void) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - uint8_t buffer[8192]; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - uint64_t hash = 0; - for (size_t i = 0; i < 4; i++) { - hash += fio_siphash24(buffer, sizeof(buffer), 0, 0); - memcpy(buffer, &hash, sizeof(hash)); - } - /* loop until test runs for more than 2 seconds */ - for (uint64_t cycles = 8192;;) { - clock_t start, end; - start = clock(); - for (size_t i = cycles; i > 0; i--) { - hash += fio_siphash24(buffer, sizeof(buffer), 0, 0); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - memcpy(buffer, &hash, sizeof(hash)); - if ((end - start) >= (2 * CLOCKS_PER_SEC) || - cycles >= ((uint64_t)1 << 62)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash24", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 2; - } - /* loop until test runs for more than 2 seconds */ - for (uint64_t cycles = 8192;;) { - clock_t start, end; - start = clock(); - for (size_t i = cycles; i > 0; i--) { - hash += fio_siphash13(buffer, sizeof(buffer), 0, 0); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - memcpy(buffer, &hash, sizeof(hash)); - if ((end - start) >= (2 * CLOCKS_PER_SEC) || - cycles >= ((uint64_t)1 << 62)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash13", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 2; - } -} - -FIO_FUNC void fio_siphash_test(void) { - fprintf(stderr, "===================================\n"); -#if NODEBUG - fio_siphash_speed_test(); -#else - fprintf(stderr, "fio SipHash speed test skipped (debug mode is slow)\n"); - (void)fio_siphash_speed_test; -#endif -} -/* ***************************************************************************** -SHA-1 tests -***************************************************************************** */ - -FIO_FUNC void fio_sha1_speed_test(void) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - uint8_t buffer[8192]; - uint8_t result[21]; - fio_sha1_s sha1; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - sha1 = fio_sha1_init(); - fio_sha1_write(&sha1, buffer, sizeof(buffer)); - memcpy(result, fio_sha1_result(&sha1), 21); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 8192;;) { - clock_t start, end; - sha1 = fio_sha1_init(); - start = clock(); - for (size_t i = cycles; i > 0; i--) { - fio_sha1_write(&sha1, buffer, sizeof(buffer)); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fio_sha1_result(&sha1); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SHA-1", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 1; - } -} - -#ifdef HAVE_OPENSSL -FIO_FUNC void fio_sha1_open_ssl_speed_test(void) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - uint8_t buffer[8192]; - uint8_t result[21]; - SHA_CTX o_sh1; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - SHA1_Init(&o_sh1); - SHA1_Update(&o_sh1, buffer, sizeof(buffer)); - SHA1_Final(result, &o_sh1); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 8192;;) { - clock_t start, end; - SHA1_Init(&o_sh1); - start = clock(); - for (size_t i = cycles; i > 0; i--) { - SHA1_Update(&o_sh1, buffer, sizeof(buffer)); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - SHA1_Final(result, &o_sh1); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "OpenSSL SHA-1", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 1; - } -} -#endif - -FIO_FUNC void fio_sha1_test(void) { - // clang-format off - struct { - char *str; - uint8_t hash[21]; - } sets[] = { - {"The quick brown fox jumps over the lazy dog", - {0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28, 0xfc, 0xed, 0x84, 0x9e, - 0xe1, 0xbb, 0x76, 0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12, 0}}, // a set with - // a string - {"", - { - 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, - 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09, - }}, // an empty set - {NULL, {0}} // Stop - }; - // clang-format on - int i = 0; - fio_sha1_s sha1; - fprintf(stderr, "===================================\n"); - fprintf(stderr, "fio SHA-1 struct size: %zu\n", sizeof(fio_sha1_s)); - fprintf(stderr, "+ fio"); - while (sets[i].str) { - sha1 = fio_sha1_init(); - fio_sha1_write(&sha1, sets[i].str, strlen(sets[i].str)); - if (strcmp(fio_sha1_result(&sha1), (char *)sets[i].hash)) { - fprintf(stderr, ":\n--- fio SHA-1 Test FAILED!\nstring: %s\nexpected: ", - sets[i].str); - char *p = (char *)sets[i].hash; - while (*p) - fprintf(stderr, "%02x", *(p++) & 0xFF); - fprintf(stderr, "\ngot: "); - p = fio_sha1_result(&sha1); - while (*p) - fprintf(stderr, "%02x", *(p++) & 0xFF); - fprintf(stderr, "\n"); - FIO_ASSERT(0, "SHA-1 failure."); - return; - } - i++; - } - fprintf(stderr, " SHA-1 passed.\n"); -#if NODEBUG - fio_sha1_speed_test(); -#else - fprintf(stderr, "fio SHA1 speed test skipped (debug mode is slow)\n"); - (void)fio_sha1_speed_test; -#endif - -#ifdef HAVE_OPENSSL - -#if NODEBUG - fio_sha1_open_ssl_speed_test(); -#else - fprintf(stderr, "OpenSSL SHA1 speed test skipped (debug mode is slow)\n"); - (void)fio_sha1_open_ssl_speed_test; -#endif - fprintf(stderr, "===================================\n"); - fprintf(stderr, "fio SHA-1 struct size: %lu\n", - (unsigned long)sizeof(fio_sha1_s)); - fprintf(stderr, "OpenSSL SHA-1 struct size: %lu\n", - (unsigned long)sizeof(SHA_CTX)); - fprintf(stderr, "===================================\n"); -#endif /* HAVE_OPENSSL */ -} - -/* ***************************************************************************** -SHA-2 tests -***************************************************************************** */ - -FIO_FUNC char *sha2_variant_names[] = { - "unknown", "SHA_512", "SHA_256", "SHA_512_256", - "SHA_224", "SHA_512_224", "none", "SHA_384", -}; - -FIO_FUNC void fio_sha2_speed_test(fio_sha2_variant_e var, - const char *var_name) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - uint8_t buffer[8192]; - uint8_t result[65]; - fio_sha2_s sha2; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - sha2 = fio_sha2_init(var); - fio_sha2_write(&sha2, buffer, sizeof(buffer)); - memcpy(result, fio_sha2_result(&sha2), 65); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 8192;;) { - clock_t start, end; - sha2 = fio_sha2_init(var); - start = clock(); - for (size_t i = cycles; i > 0; i--) { - fio_sha2_write(&sha2, buffer, sizeof(buffer)); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fio_sha2_result(&sha2); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", var_name, - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 1; - } -} - -FIO_FUNC void fio_sha2_openssl_speed_test(const char *var_name, int (*init)(), - int (*update)(), int (*final)(), - void *sha) { - /* test adapted from BearSSL code with credit to Thomas Pornin */ - uint8_t buffer[8192]; - uint8_t result[1024]; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - init(sha); - update(sha, buffer, sizeof(buffer)); - final(result, sha); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 2048;;) { - clock_t start, end; - init(sha); - start = clock(); - for (size_t i = cycles; i > 0; i--) { - update(sha, buffer, sizeof(buffer)); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - final(result, sha); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", var_name, - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 1; - } -} -FIO_FUNC void fio_sha2_test(void) { - fio_sha2_s s; - char *expect; - char *got; - char *str = ""; - fprintf(stderr, "===================================\n"); - fprintf(stderr, "fio SHA-2 struct size: %zu\n", sizeof(fio_sha2_s)); - fprintf(stderr, "+ fio"); - // start tests - s = fio_sha2_init(SHA_224); - fio_sha2_write(&s, str, 0); - expect = "\xd1\x4a\x02\x8c\x2a\x3a\x2b\xc9\x47\x61\x02\xbb\x28\x82\x34\xc4" - "\x15\xa2\xb0\x1f\x82\x8e\xa6\x2a\xc5\xb3\xe4\x2f"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_256); - fio_sha2_write(&s, str, 0); - expect = - "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\x27" - "\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_512); - fio_sha2_write(&s, str, 0); - expect = "\xcf\x83\xe1\x35\x7e\xef\xb8\xbd\xf1\x54\x28\x50\xd6\x6d" - "\x80\x07\xd6\x20\xe4\x05\x0b\x57\x15\xdc\x83\xf4\xa9\x21" - "\xd3\x6c\xe9\xce\x47\xd0\xd1\x3c\x5d\x85\xf2\xb0\xff\x83" - "\x18\xd2\x87\x7e\xec\x2f\x63\xb9\x31\xbd\x47\x41\x7a\x81" - "\xa5\x38\x32\x7a\xf9\x27\xda\x3e"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_384); - fio_sha2_write(&s, str, 0); - expect = "\x38\xb0\x60\xa7\x51\xac\x96\x38\x4c\xd9\x32\x7e" - "\xb1\xb1\xe3\x6a\x21\xfd\xb7\x11\x14\xbe\x07\x43\x4c\x0c" - "\xc7\xbf\x63\xf6\xe1\xda\x27\x4e\xde\xbf\xe7\x6f\x65\xfb" - "\xd5\x1a\xd2\xf1\x48\x98\xb9\x5b"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_512_224); - fio_sha2_write(&s, str, 0); - expect = "\x6e\xd0\xdd\x02\x80\x6f\xa8\x9e\x25\xde\x06\x0c\x19\xd3" - "\xac\x86\xca\xbb\x87\xd6\xa0\xdd\xd0\x5c\x33\x3b\x84\xf4"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_512_256); - fio_sha2_write(&s, str, 0); - expect = "\xc6\x72\xb8\xd1\xef\x56\xed\x28\xab\x87\xc3\x62\x2c\x51\x14\x06" - "\x9b\xdd\x3a\xd7\xb8\xf9\x73\x74\x98\xd0\xc0\x1e\xce\xf0\x96\x7a"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - s = fio_sha2_init(SHA_512); - str = "god is a rotten tomato"; - fio_sha2_write(&s, str, strlen(str)); - expect = "\x61\x97\x4d\x41\x9f\x77\x45\x21\x09\x4e\x95\xa3\xcb\x4d\xe4\x79" - "\x26\x32\x2f\x2b\xe2\x62\x64\x5a\xb4\x5d\x3f\x73\x69\xef\x46\x20" - "\xb2\xd3\xce\xda\xa9\xc2\x2c\xac\xe3\xf9\x02\xb2\x20\x5d\x2e\xfd" - "\x40\xca\xa0\xc1\x67\xe0\xdc\xdf\x60\x04\x3e\x4e\x76\x87\x82\x74"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - - // s = fio_sha2_init(SHA_256); - // str = "The quick brown fox jumps over the lazy dog"; - // fio_sha2_write(&s, str, strlen(str)); - // expect = - // "\xd7\xa8\xfb\xb3\x07\xd7\x80\x94\x69\xca\x9a\xbc\xb0\x08\x2e\x4f" - // "\x8d\x56\x51\xe4\x6d\x3c\xdb\x76\x2d\x02\xd0\xbf\x37\xc9\xe5\x92"; - // got = fio_sha2_result(&s); - // if (strcmp(expect, got)) - // goto error; - - s = fio_sha2_init(SHA_224); - str = "The quick brown fox jumps over the lazy dog"; - fio_sha2_write(&s, str, strlen(str)); - expect = "\x73\x0e\x10\x9b\xd7\xa8\xa3\x2b\x1c\xb9\xd9\xa0\x9a\xa2" - "\x32\x5d\x24\x30\x58\x7d\xdb\xc0\xc3\x8b\xad\x91\x15\x25"; - got = fio_sha2_result(&s); - if (strcmp(expect, got)) - goto error; - fprintf(stderr, " SHA-2 passed.\n"); - -#if NODEBUG - fio_sha2_speed_test(SHA_224, "fio SHA-224"); - fio_sha2_speed_test(SHA_256, "fio SHA-256"); - fio_sha2_speed_test(SHA_384, "fio SHA-384"); - fio_sha2_speed_test(SHA_512, "fio SHA-512"); -#else - fprintf(stderr, "fio SHA-2 speed test skipped (debug mode is slow)\n"); -#endif - -#ifdef HAVE_OPENSSL - -#if NODEBUG - { - SHA512_CTX s2; - SHA256_CTX s3; - fio_sha2_openssl_speed_test("OpenSSL SHA512", SHA512_Init, SHA512_Update, - SHA512_Final, &s2); - fio_sha2_openssl_speed_test("OpenSSL SHA256", SHA256_Init, SHA256_Update, - SHA256_Final, &s3); - } -#endif - fprintf(stderr, "===================================\n"); - fprintf(stderr, "fio SHA-2 struct size: %zu\n", sizeof(fio_sha2_s)); - fprintf(stderr, "OpenSSL SHA-2/256 struct size: %zu\n", sizeof(SHA256_CTX)); - fprintf(stderr, "OpenSSL SHA-2/512 struct size: %zu\n", sizeof(SHA512_CTX)); - fprintf(stderr, "===================================\n"); -#endif /* HAVE_OPENSSL */ - - return; - -error: - fprintf(stderr, - ":\n--- fio SHA-2 Test FAILED!\ntype: " - "%s (%d)\nstring %s\nexpected:\n", - sha2_variant_names[s.type], s.type, str); - while (*expect) - fprintf(stderr, "%02x", *(expect++) & 0xFF); - fprintf(stderr, "\ngot:\n"); - while (*got) - fprintf(stderr, "%02x", *(got++) & 0xFF); - fprintf(stderr, "\n"); - (void)fio_sha2_speed_test; - (void)fio_sha2_openssl_speed_test; - FIO_ASSERT(0, "SHA-2 failure."); -} - -/* ***************************************************************************** -Base64 tests -***************************************************************************** */ - -FIO_FUNC void fio_base64_speed_test(void) { - /* test based on code from BearSSL with credit to Thomas Pornin */ - char buffer[8192]; - char result[8192 * 2]; - memset(buffer, 'T', sizeof(buffer)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - fio_base64_encode(result, buffer, sizeof(buffer)); - memcpy(buffer, result, sizeof(buffer)); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 8192;;) { - clock_t start, end; - start = clock(); - for (size_t i = cycles; i > 0; i--) { - fio_base64_encode(result, buffer, sizeof(buffer)); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio Base64 Encode", - (double)(sizeof(buffer) * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 2; - } - - /* speed test decoding */ - const int encoded_len = - fio_base64_encode(result, buffer, (int)(sizeof(buffer) - 2)); - /* warmup */ - for (size_t i = 0; i < 4; i++) { - fio_base64_decode(buffer, result, encoded_len); - __asm__ volatile("" ::: "memory"); - } - /* loop until test runs for more than 2 seconds */ - for (size_t cycles = 8192;;) { - clock_t start, end; - start = clock(); - for (size_t i = cycles; i > 0; i--) { - fio_base64_decode(buffer, result, encoded_len); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - if ((end - start) >= (2 * CLOCKS_PER_SEC)) { - fprintf(stderr, "%-20s %8.2f MB/s\n", "fio Base64 Decode", - (double)(encoded_len * cycles) / - (((end - start) * 1000000.0 / CLOCKS_PER_SEC))); - break; - } - cycles <<= 2; - } -} - -FIO_FUNC void fio_base64_test(void) { - struct { - char *str; - char *base64; - } sets[] = { - {"Man is distinguished, not only by his reason, but by this singular " - "passion from other animals, which is a lust of the mind, that by a " - "perseverance of delight in the continued " - "and indefatigable generation " - "of knowledge, exceeds the short vehemence of any carnal pleasure.", - "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB" - "0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG" - "x1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpb" - "iB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xl" - "ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3V" - "yZS4="}, - {"any carnal pleasure.", "YW55IGNhcm5hbCBwbGVhc3VyZS4="}, - {"any carnal pleasure", "YW55IGNhcm5hbCBwbGVhc3VyZQ=="}, - {"any carnal pleasur", "YW55IGNhcm5hbCBwbGVhc3Vy"}, - {"", ""}, - {"f", "Zg=="}, - {"fo", "Zm8="}, - {"foo", "Zm9v"}, - {"foob", "Zm9vYg=="}, - {"fooba", "Zm9vYmE="}, - {"foobar", "Zm9vYmFy"}, - {NULL, NULL} // Stop - }; - int i = 0; - char buffer[1024]; - fprintf(stderr, "===================================\n"); - fprintf(stderr, "+ fio"); - while (sets[i].str) { - fio_base64_encode(buffer, sets[i].str, strlen(sets[i].str)); - if (strcmp(buffer, sets[i].base64)) { - fprintf(stderr, - ":\n--- fio Base64 Test FAILED!\nstring: %s\nlength: %lu\n " - "expected: %s\ngot: %s\n\n", - sets[i].str, (u_long)strlen(sets[i].str), sets[i].base64, buffer); - FIO_ASSERT(0, "Base64 failure."); - } - i++; - } - if (!sets[i].str) - fprintf(stderr, " Base64 encode passed.\n"); - - i = 0; - fprintf(stderr, "+ fio"); - while (sets[i].str) { - fio_base64_decode(buffer, sets[i].base64, strlen(sets[i].base64)); - if (strcmp(buffer, sets[i].str)) { - fprintf(stderr, - ":\n--- fio Base64 Test FAILED!\nbase64: %s\nexpected: " - "%s\ngot: %s\n\n", - sets[i].base64, sets[i].str, buffer); - FIO_ASSERT(0, "Base64 failure."); - } - i++; - } - fprintf(stderr, " Base64 decode passed.\n"); - -#if NODEBUG - fio_base64_speed_test(); -#else - fprintf(stderr, - "* Base64 speed test skipped (debug speeds are always slow).\n"); - (void)fio_base64_speed_test; -#endif -} - -/******************************************************************************* -Random Testing -***************************************************************************** */ - -FIO_FUNC void fio_test_random(void) { - fprintf(stderr, "=== Testing random generator\n"); - uint64_t rnd = fio_rand64(); - FIO_ASSERT((rnd != fio_rand64() && rnd != fio_rand64()), - "fio_rand64 returned the same result three times in a row."); -#if NODEBUG - uint64_t buffer1[8]; - uint8_t buffer2[8192]; - clock_t start, end; - start = clock(); - for (size_t i = 0; i < (8388608 / (64 / 8)); i++) { - buffer1[i & 7] = fio_rand64(); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, - "+ Random generator available\n+ created 8Mb using 64bits " - "Random %lu CPU clock count ~%.2fMb/s\n", - end - start, (8.0) / (((double)(end - start)) / CLOCKS_PER_SEC)); - start = clock(); - for (size_t i = 0; i < (8388608 / (8192)); i++) { - fio_rand_bytes(buffer2, 8192); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, - "+ created 8Mb using 8,192 Bytes " - "Random %lu CPU clock count ~%.2fMb/s\n", - end - start, (8.0) / (((double)(end - start)) / CLOCKS_PER_SEC)); - (void)buffer1; - (void)buffer2; -#endif -} - -/* ***************************************************************************** -Poll (not kqueue or epoll) tests -***************************************************************************** */ -#if FIO_ENGINE_POLL || FIO_ENGINE_WSAPOLL -#ifdef __MINGW32__ -FIO_FUNC void fio_poll_test(void) { - fprintf(stderr, "=== Testing poll add / remove fd\n"); - fio_poll_add(5); - FIO_ASSERT(fio_data->poll[5].fd == 5, "fio_poll_add didn't set used fd data"); - FIO_ASSERT(fio_data->poll[5].events == - (FIO_POLL_READ_EVENTS | FIO_POLL_WRITE_EVENTS), - "fio_poll_add didn't set used fd flags"); - fio_poll_add(7); - FIO_ASSERT(fio_data->poll[6].fd == INVALID_SOCKET, - "fio_poll_add didn't reset unused fd data %d", - fio_data->poll[6].fd); - fio_poll_add(6); - fio_poll_remove_fd(6); - FIO_ASSERT(fio_data->poll[6].fd == INVALID_SOCKET, - "fio_poll_remove_fd didn't reset unused fd data"); - FIO_ASSERT(fio_data->poll[6].events == 0, - "fio_poll_remove_fd didn't reset unused fd flags"); - fio_poll_remove_read(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_WRITE_EVENTS), - "fio_poll_remove_read didn't remove read flags"); - fio_poll_add_read(7); - fio_poll_remove_write(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_READ_EVENTS), - "fio_poll_remove_write didn't remove read flags"); - fio_poll_add_write(7); - fio_poll_remove_read(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_WRITE_EVENTS), - "fio_poll_add_write didn't add the write flag?"); - fio_poll_remove_write(7); - FIO_ASSERT(fio_data->poll[7].fd == INVALID_SOCKET, - "fio_poll_remove (both) didn't reset unused fd data"); - FIO_ASSERT(fio_data->poll[7].events == 0, - "fio_poll_remove (both) didn't reset unused fd flags"); - fio_poll_remove_fd(5); - fprintf(stderr, "\n* passed.\n"); -} -#else -FIO_FUNC void fio_poll_test(void) { - fprintf(stderr, "=== Testing poll add / remove fd\n"); - fio_poll_add(5); - FIO_ASSERT(fio_data->poll[5].fd == 5, "fio_poll_add didn't set used fd data"); - FIO_ASSERT(fio_data->poll[5].events == - (FIO_POLL_READ_EVENTS | FIO_POLL_WRITE_EVENTS), - "fio_poll_add didn't set used fd flags"); - fio_poll_add(7); - FIO_ASSERT(fio_data->poll[6].fd == -1, - "fio_poll_add didn't reset unused fd data %d", - fio_data->poll[6].fd); - fio_poll_add(6); - fio_poll_remove_fd(6); - FIO_ASSERT(fio_data->poll[6].fd == -1, - "fio_poll_remove_fd didn't reset unused fd data"); - FIO_ASSERT(fio_data->poll[6].events == 0, - "fio_poll_remove_fd didn't reset unused fd flags"); - fio_poll_remove_read(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_WRITE_EVENTS), - "fio_poll_remove_read didn't remove read flags"); - fio_poll_add_read(7); - fio_poll_remove_write(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_READ_EVENTS), - "fio_poll_remove_write didn't remove read flags"); - fio_poll_add_write(7); - fio_poll_remove_read(7); - FIO_ASSERT(fio_data->poll[7].events == (FIO_POLL_WRITE_EVENTS), - "fio_poll_add_write didn't add the write flag?"); - fio_poll_remove_write(7); - FIO_ASSERT(fio_data->poll[7].fd == -1, - "fio_poll_remove (both) didn't reset unused fd data"); - FIO_ASSERT(fio_data->poll[7].events == 0, - "fio_poll_remove (both) didn't reset unused fd flags"); - fio_poll_remove_fd(5); - fprintf(stderr, "\n* passed.\n"); -} -#endif -#else -#define fio_poll_test() -#endif - -/* ***************************************************************************** -Test UUID Linking -***************************************************************************** */ - -FIO_FUNC void fio_uuid_link_test_on_close(void *obj) { - fio_atomic_add((uintptr_t *)obj, 1); -} - -FIO_FUNC void fio_uuid_link_test(void) { - fprintf(stderr, "=== Testing fio_uuid_link\n"); - uintptr_t called = 0; - uintptr_t removed = 0; - intptr_t uuid = fio_socket(NULL, "8765", 1); - FIO_ASSERT(uuid != -1, "fio_uuid_link_test failed to create a socket!"); - fio_uuid_link(uuid, &called, fio_uuid_link_test_on_close); - FIO_ASSERT(called == 0, - "fio_uuid_link failed - on_close callback called too soon!"); - fio_uuid_link(uuid, &removed, fio_uuid_link_test_on_close); - fio_uuid_unlink(uuid, &removed); - fio_close(uuid); - fio_defer_perform(); - FIO_ASSERT(called, "fio_uuid_link failed - on_close callback wasn't called!"); - FIO_ASSERT(called, "fio_uuid_unlink failed - on_close callback was called " - "(wasn't removed)!"); - fprintf(stderr, "* passed.\n"); -} - -/* ***************************************************************************** -Byte Order Testing -***************************************************************************** */ - -FIO_FUNC void fio_str2u_test(void) { - fprintf(stderr, "=== Testing fio_u2strX and fio_u2strX functions.\n"); - char buffer[32]; - for (int64_t i = -1024; i < 1024; ++i) { - fio_u2str64(buffer, i); - __asm__ volatile("" ::: "memory"); - FIO_ASSERT((int64_t)fio_str2u64(buffer) == i, - "fio_u2str64 / fio_str2u64 mismatch %zd != %zd", - (ssize_t)fio_str2u64(buffer), (ssize_t)i); - } - for (int32_t i = -1024; i < 1024; ++i) { - fio_u2str32(buffer, i); - __asm__ volatile("" ::: "memory"); - FIO_ASSERT((int32_t)fio_str2u32(buffer) == i, - "fio_u2str32 / fio_str2u32 mismatch %zd != %zd", - (ssize_t)(fio_str2u32(buffer)), (ssize_t)i); - } - for (int16_t i = -1024; i < 1024; ++i) { - fio_u2str16(buffer, i); - __asm__ volatile("" ::: "memory"); - FIO_ASSERT((int16_t)fio_str2u16(buffer) == i, - "fio_u2str16 / fio_str2u16 mismatch %zd != %zd", - (ssize_t)(fio_str2u16(buffer)), (ssize_t)i); - } - fprintf(stderr, "* passed.\n"); -} - -/* ***************************************************************************** -Pub/Sub partial tests -***************************************************************************** */ - -#if FIO_PUBSUB_SUPPORT - -FIO_FUNC void fio_pubsub_test_on_message(fio_msg_s *msg) { - fio_atomic_add((uintptr_t *)msg->udata1, 1); -} -FIO_FUNC void fio_pubsub_test_on_unsubscribe(void *udata1, void *udata2) { - fio_atomic_add((uintptr_t *)udata1, 1); - (void)udata2; -} - -FIO_FUNC void fio_pubsub_test(void) { - fprintf(stderr, "=== Testing pub/sub (partial)\n"); - fio_data->active = 1; - fio_data->is_worker = 1; - fio_data->workers = 1; - subscription_s *s = fio_subscribe(.filter = 1, .on_message = NULL); - uintptr_t counter = 0; - uintptr_t expect = 0; - FIO_ASSERT(!s, "fio_subscribe should fail without a callback!"); - char buffer[8]; - fio_u2str32((uint8_t *)buffer + 1, 42); - FIO_ASSERT(fio_str2u32((uint8_t *)buffer + 1) == 42, - "fio_u2str32 / fio_str2u32 not reversible (42)!"); - fio_u2str32((uint8_t *)buffer, 4); - FIO_ASSERT(fio_str2u32((uint8_t *)buffer) == 4, - "fio_u2str32 / fio_str2u32 not reversible (4)!"); - subscription_s *s2 = - fio_subscribe(.filter = 1, .udata1 = &counter, - .on_message = fio_pubsub_test_on_message, - .on_unsubscribe = fio_pubsub_test_on_unsubscribe); - FIO_ASSERT(s2, "fio_subscribe FAILED on filtered subscription."); - fio_publish(.filter = 1); - ++expect; - fio_defer_perform(); - FIO_ASSERT(counter == expect, "publishing failed to filter 1!"); - fio_publish(.filter = 2); - fio_defer_perform(); - FIO_ASSERT(counter == expect, "publishing to filter 2 arrived at filter 1!"); - fio_unsubscribe(s); - fio_unsubscribe(s2); - ++expect; - fio_defer_perform(); - FIO_ASSERT(counter == expect, "unsubscribe wasn't called for filter 1!"); - s = fio_subscribe(.channel = {0, 4, "name"}, .udata1 = &counter, - .on_message = fio_pubsub_test_on_message, - .on_unsubscribe = fio_pubsub_test_on_unsubscribe); - FIO_ASSERT(s, "fio_subscribe FAILED on named subscription."); - fio_publish(.channel = {0, 4, "name"}); - ++expect; - fio_defer_perform(); - FIO_ASSERT(counter == expect, "publishing failed to named channel!"); - fio_publish(.channel = {0, 4, "none"}); - fio_defer_perform(); - FIO_ASSERT(counter == expect, - "publishing arrived to named channel with wrong name!"); - fio_unsubscribe(s); - ++expect; - fio_defer_perform(); - FIO_ASSERT(counter == expect, "unsubscribe wasn't called for named channel!"); - fio_data->is_worker = 0; - fio_data->active = 0; - fio_data->workers = 0; - fio_defer_perform(); - (void)fio_pubsub_test_on_message; - (void)fio_pubsub_test_on_unsubscribe; - fprintf(stderr, "* passed.\n"); -} -#else -#define fio_pubsub_test() -#endif - -/* ***************************************************************************** -String 2 Number and Number 2 String (partial) testing -***************************************************************************** */ - -#if NODEBUG -#define FIO_ATOL_TEST_MAX_CYCLES 3145728 -#else -#define FIO_ATOL_TEST_MAX_CYCLES 4096 -#endif -FIO_FUNC void fio_atol_test(void) { - fprintf(stderr, "=== Testing fio_ltoa and fio_atol (partial)\n"); -#ifndef NODEBUG - fprintf(stderr, - "Note: No optimizations - facil.io performance will be slow.\n"); -#endif - fprintf(stderr, - " Test with make test/optimized for realistic results.\n"); - time_t start, end; - -#define TEST_ATOL(s, n) \ - do { \ - char *p = (char *)(s); \ - int64_t r = fio_atol(&p); \ - FIO_ASSERT(r == (n), "fio_atol test error! %s => %zd (not %zd)", \ - ((char *)(s)), (size_t)r, (size_t)n); \ - FIO_ASSERT((s) + strlen((s)) == p, \ - "fio_atol test error! %s reading position not at end (%zu)", \ - (s), (size_t)(p - (s))); \ - char buf[72]; \ - buf[fio_ltoa(buf, n, 2)] = 0; \ - p = buf; \ - FIO_ASSERT(fio_atol(&p) == (n), \ - "fio_ltoa base 2 test error! " \ - "%s != %s (%zd)", \ - buf, ((char *)(s)), (size_t)((p = buf), fio_atol(&p))); \ - buf[fio_ltoa(buf, n, 8)] = 0; \ - p = buf; \ - FIO_ASSERT(fio_atol(&p) == (n), \ - "fio_ltoa base 8 test error! " \ - "%s != %s (%zd)", \ - buf, ((char *)(s)), (size_t)((p = buf), fio_atol(&p))); \ - buf[fio_ltoa(buf, n, 10)] = 0; \ - p = buf; \ - FIO_ASSERT(fio_atol(&p) == (n), \ - "fio_ltoa base 10 test error! " \ - "%s != %s (%zd)", \ - buf, ((char *)(s)), (size_t)((p = buf), fio_atol(&p))); \ - buf[fio_ltoa(buf, n, 16)] = 0; \ - p = buf; \ - FIO_ASSERT(fio_atol(&p) == (n), \ - "fio_ltoa base 16 test error! " \ - "%s != %s (%zd)", \ - buf, ((char *)(s)), (size_t)((p = buf), fio_atol(&p))); \ - } while (0) - TEST_ATOL("0x1", 1); - TEST_ATOL("-0x1", -1); - TEST_ATOL("-0xa", -10); /* sign before hex */ - TEST_ATOL("0xe5d4c3b2a1908770", -1885667171979196560); /* sign within hex */ - TEST_ATOL("0b00000000000011", 3); - TEST_ATOL("-0b00000000000011", -3); - TEST_ATOL("0b0000000000000000000000000000000000000000000000000", 0); - TEST_ATOL("0", 0); - TEST_ATOL("1", 1); - TEST_ATOL("2", 2); - TEST_ATOL("-2", -2); - TEST_ATOL("0000000000000000000000000000000000000000000000042", 34); /* oct */ - TEST_ATOL("9223372036854775807", 9223372036854775807LL); /* INT64_MAX */ - TEST_ATOL("9223372036854775808", - 9223372036854775807LL); /* INT64_MAX overflow protection */ - TEST_ATOL("9223372036854775999", - 9223372036854775807LL); /* INT64_MAX overflow protection */ - - char number_hex[128] = "0xe5d4c3b2a1908770"; /* hex with embedded sign */ - // char number_hex[128] = "-0x1a2b3c4d5e6f7890"; - char number[128] = "-1885667171979196560"; - intptr_t expect = -1885667171979196560; - intptr_t result = 0; - - result = 0; - - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - char *pos = number; - result = fio_atol(&pos); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, "fio_atol base 10 (%ld): %zd CPU cycles\n", (long int)result, - end - start); - - result = 0; - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - result = strtol(number, NULL, 0); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, "native strtol base 10 (%ld): %zd CPU cycles\n", (long int)result, - end - start); - - result = 0; - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - char *pos = number_hex; - result = fio_atol(&pos); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, "fio_atol base 16 (%ld): %zd CPU cycles\n", (long int)result, - end - start); - - result = 0; - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - result = strtol(number_hex, NULL, 0); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, "native strtol base 16 (%ld): %zd CPU cycles%s\n", (long int)result, - end - start, (result != expect ? " (!?stdlib overflow?!)" : "")); - - result = 0; - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - fio_ltoa(number, expect, 10); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - { - char *buf = number; - FIO_ASSERT(fio_atol(&buf) == expect, - "fio_ltoa with base 10 returned wrong result (%s != %ld)", - number, expect); - } - fprintf(stderr, "fio_ltoa base 10 (%s): %zd CPU cycles\n", number, - end - start); - - result = 0; - start = clock(); - for (size_t i = 0; i < FIO_ATOL_TEST_MAX_CYCLES; ++i) { - __asm__ volatile("" ::: "memory"); - sprintf(number, "%ld", (long int)expect); - __asm__ volatile("" ::: "memory"); - } - end = clock(); - fprintf(stderr, "native sprintf base 10 (%s): %zd CPU cycles\n", number, - end - start); - FIO_ASSERT(fio_ltoa(number, 0, 0) == 1, - "base 10 zero should be single char."); - FIO_ASSERT(memcmp(number, "0", 2) == 0, "base 10 zero should be \"0\" (%s).", - number); - fprintf(stderr, "* passed.\n"); -#undef TEST_ATOL -} - -/* ***************************************************************************** -String 2 Float and Float 2 String (partial) testing -***************************************************************************** */ - -FIO_FUNC void fio_atof_test(void) { - fprintf(stderr, "=== Testing fio_ftoa and fio_ftoa (partial)\n"); -#define TEST_DOUBLE(s, d, must) \ - do { \ - char *p = (char *)(s); \ - double r = fio_atof(&p); \ - if (r != (d)) { \ - FIO_LOG_DEBUG("Double Test Error! %s => %.19g (not %.19g)", \ - ((char *)(s)), r, d); \ - if (must) { \ - FIO_ASSERT(0, "double test failed on %s", ((char *)(s))); \ - exit(-1); \ - } \ - } \ - } while (0) - /* The numbers were copied from https://github.com/miloyip/rapidjson */ - TEST_DOUBLE("0.0", 0.0, 1); - TEST_DOUBLE("-0.0", -0.0, 1); - TEST_DOUBLE("1.0", 1.0, 1); - TEST_DOUBLE("-1.0", -1.0, 1); - TEST_DOUBLE("1.5", 1.5, 1); - TEST_DOUBLE("-1.5", -1.5, 1); - TEST_DOUBLE("3.1416", 3.1416, 1); - TEST_DOUBLE("1E10", 1E10, 1); - TEST_DOUBLE("1e10", 1e10, 1); - TEST_DOUBLE("1E+10", 1E+10, 1); - TEST_DOUBLE("1E-10", 1E-10, 1); - TEST_DOUBLE("-1E10", -1E10, 1); - TEST_DOUBLE("-1e10", -1e10, 1); - TEST_DOUBLE("-1E+10", -1E+10, 1); - TEST_DOUBLE("-1E-10", -1E-10, 1); - TEST_DOUBLE("1.234E+10", 1.234E+10, 1); - TEST_DOUBLE("1.234E-10", 1.234E-10, 1); - TEST_DOUBLE("1.79769e+308", 1.79769e+308, 1); - TEST_DOUBLE("2.22507e-308", 2.22507e-308, 1); - TEST_DOUBLE("-1.79769e+308", -1.79769e+308, 1); - TEST_DOUBLE("-2.22507e-308", -2.22507e-308, 1); - TEST_DOUBLE("4.9406564584124654e-324", 4.9406564584124654e-324, 0); - TEST_DOUBLE("2.2250738585072009e-308", 2.2250738585072009e-308, 0); - TEST_DOUBLE("2.2250738585072014e-308", 2.2250738585072014e-308, 1); - TEST_DOUBLE("1.7976931348623157e+308", 1.7976931348623157e+308, 1); - TEST_DOUBLE("1e-10000", 0.0, 0); - TEST_DOUBLE("18446744073709551616", 18446744073709551616.0, 0); - - TEST_DOUBLE("-9223372036854775809", -9223372036854775809.0, 0); - - TEST_DOUBLE("0.9868011474609375", 0.9868011474609375, 0); - TEST_DOUBLE("123e34", 123e34, 1); - TEST_DOUBLE("45913141877270640000.0", 45913141877270640000.0, 1); - TEST_DOUBLE("2.2250738585072011e-308", 2.2250738585072011e-308, 0); - TEST_DOUBLE("1e-214748363", 0.0, 1); - TEST_DOUBLE("1e-214748364", 0.0, 1); - TEST_DOUBLE("0.017976931348623157e+310, 1", 1.7976931348623157e+308, 0); - - TEST_DOUBLE("2.2250738585072012e-308", 2.2250738585072014e-308, 0); - TEST_DOUBLE("2.22507385850720113605740979670913197593481954635164565e-308", - 2.2250738585072014e-308, 0); - - TEST_DOUBLE("0.999999999999999944488848768742172978818416595458984375", 1.0, - 0); - TEST_DOUBLE("0.999999999999999944488848768742172978818416595458984376", 1.0, - 0); - TEST_DOUBLE("1.00000000000000011102230246251565404236316680908203125", 1.0, - 0); - TEST_DOUBLE("1.00000000000000011102230246251565404236316680908203124", 1.0, - 0); - - TEST_DOUBLE("72057594037927928.0", 72057594037927928.0, 0); - TEST_DOUBLE("72057594037927936.0", 72057594037927936.0, 0); - TEST_DOUBLE("72057594037927932.0", 72057594037927936.0, 0); - TEST_DOUBLE("7205759403792793200001e-5", 72057594037927936.0, 0); - - TEST_DOUBLE("9223372036854774784.0", 9223372036854774784.0, 0); - TEST_DOUBLE("9223372036854775808.0", 9223372036854775808.0, 0); - TEST_DOUBLE("9223372036854775296.0", 9223372036854775808.0, 0); - TEST_DOUBLE("922337203685477529600001e-5", 9223372036854775808.0, 0); - - TEST_DOUBLE("10141204801825834086073718800384", - 10141204801825834086073718800384.0, 0); - TEST_DOUBLE("10141204801825835211973625643008", - 10141204801825835211973625643008.0, 0); - TEST_DOUBLE("10141204801825834649023672221696", - 10141204801825835211973625643008.0, 0); - TEST_DOUBLE("1014120480182583464902367222169600001e-5", - 10141204801825835211973625643008.0, 0); - - TEST_DOUBLE("5708990770823838890407843763683279797179383808", - 5708990770823838890407843763683279797179383808.0, 0); - TEST_DOUBLE("5708990770823839524233143877797980545530986496", - 5708990770823839524233143877797980545530986496.0, 0); - TEST_DOUBLE("5708990770823839207320493820740630171355185152", - 5708990770823839524233143877797980545530986496.0, 0); - TEST_DOUBLE("5708990770823839207320493820740630171355185152001e-3", - 5708990770823839524233143877797980545530986496.0, 0); - fprintf(stderr, "\n* passed.\n"); -} -/* ***************************************************************************** -Run all tests -***************************************************************************** */ - -void fio_test(void) { - FIO_ASSERT(fio_capa(), "facil.io initialization error!"); - fio_malloc_test(); - fio_state_callback_test(); - fio_str_test(); - fio_atol_test(); - fio_atof_test(); - fio_str2u_test(); - fio_llist_test(); - fio_ary_test(); - fio_set_test(); - fio_defer_test(); - fio_timer_test(); - fio_poll_test(); - fio_socket_test(); - fio_uuid_link_test(); - fio_cycle_test(); - fio_riskyhash_test(); - fio_siphash_test(); - fio_sha1_test(); - fio_sha2_test(); - fio_base64_test(); - fio_test_random(); - fio_pubsub_test(); - (void)fio_sentinel_task; - (void)deferred_on_shutdown; - (void)fio_poll; -} - -#endif /* DEBUG */ diff --git a/ext/iodine/fio.h b/ext/iodine/fio.h deleted file mode 100644 index 4ddc4cfd..00000000 --- a/ext/iodine/fio.h +++ /dev/null @@ -1,6373 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ - -#ifndef H_FACIL_IO_H -/** -"facil.h" is the main header for the facil.io server platform. -*/ -#define H_FACIL_IO_H - -/* ***************************************************************************** - * Table of contents (find by subject): - * ================= - * Version and helper macros - * Helper String Information Type - * Memory pool / custom allocator for short lived objects - * Logging and testing helpers - * - * Connection Callback (Protocol) Management - * Listening to Incoming Connections - * Connecting to remote servers as a client - * Starting the IO reactor and reviewing it's state - * Socket / Connection Functions - * Connection Read / Write Hooks, for overriding the system calls - * Concurrency overridable functions - * Connection Task scheduling - * Event / Task scheduling - * Startup / State Callbacks (fork, start up, idle, etc') - * Lower Level API - for special circumstances, use with care under - * - * Pub/Sub / Cluster Messages API - * Cluster Messages and Pub/Sub - * Cluster / Pub/Sub Middleware and Extensions ("Engines") - * - * Atomic Operations and Spin Locking Helper Functions - * Simple Constant Time Operations - * Byte Swapping and Network Order - * - * Converting Numbers to Strings (and back) - * Strings to Numbers - * Numbers to Strings* Random Generator Functions - * - * SipHash - * SHA-1 - * SHA-2 - * Base64 (URL) encoding - * - * Memory Allocator Details - * - * Spin locking Implementation - * - ******** facil.io Data Types (String, Set / Hash Map, Linked Lists, etc') - * - * These types can be included by defining the macros and (re)including fio.h. - * - * - * - * #ifdef FIO_INCLUDE_LINKED_LIST - * - * Linked List Helpers - * Independent Linked List API - * Embedded Linked List API* Independent Linked List Implementation - * Embeded Linked List Implementation - * - * - * - * #ifdef FIO_INCLUDE_STR - * - * String Helpers - * String API - Initialization and Destruction - * String API - String state (data pointers, length, capacity, etc') - * String API - Memory management - * String API - UTF-8 State - * String Implementation - state (data pointers, length, capacity, etc') - * String Implementation - Memory management - * String Implementation - UTF-8 State - * String Implementation - Content Manipulation and Review - * - * - * - * #ifdef FIO_ARY_NAME - can be included more than once - * - * Dynamic Array Data-Store - * Array API - * Array Type - * Array Memory Management - * Array API implementation - * Array Testing - * - * - * - * #ifdef FIO_SET_NAME - can be included more than once - * - * Set / Hash Map Data-Store - * Set / Hash Map API - * Set / Hash Map Internal Data Structures - * Set / Hash Map Internal Helpers - * Set / Hash Map Implementation - * - ***************************************************************************** - */ - -/* ***************************************************************************** -Version and helper macros -***************************************************************************** */ - -#define FIO_VERSION_MAJOR 0 -#define FIO_VERSION_MINOR 7 -#define FIO_VERSION_PATCH 4 -#define FIO_VERSION_BETA 0 - -/* Automatically convert version data to a string constant - ignore these two */ -#define FIO_MACRO2STR_STEP2(macro) #macro -#define FIO_MACRO2STR(macro) FIO_MACRO2STR_STEP2(macro) - -/** The facil.io version as a String literal */ -#if FIO_VERSION_BETA -#define FIO_VERSION_STRING \ - FIO_MACRO2STR(FIO_VERSION_MAJOR) \ - "." FIO_MACRO2STR(FIO_VERSION_MINOR) "." FIO_MACRO2STR( \ - FIO_VERSION_PATCH) ".beta" FIO_MACRO2STR(FIO_VERSION_BETA) -#else -#define FIO_VERSION_STRING \ - FIO_MACRO2STR(FIO_VERSION_MAJOR) \ - "." FIO_MACRO2STR(FIO_VERSION_MINOR) "." FIO_MACRO2STR(FIO_VERSION_PATCH) -#endif - -#ifndef FIO_MAX_SOCK_CAPACITY -/** - * The maximum number of connections per worker process. - */ -#define FIO_MAX_SOCK_CAPACITY 131072 -#endif - -#ifndef FIO_CPU_CORES_LIMIT -/** - * If facil.io detects more CPU cores than the number of cores stated in the - * FIO_CPU_CORES_LIMIT, it will assume an error and cap the number of cores - * detected to the assigned limit. - * - * This is only relevant to automated values, when running facil.io with zero - * threads and processes, which invokes a large matrix of workers and threads - * (see {facil_run}) - * - * The default auto-detection cap is set at 8 cores. The number is arbitrary - * (historically the number 7 was used after testing `malloc` race conditions on - * a MacBook Pro). - * - * This does NOT effect manually set (non-zero) worker/thread values. - */ -#define FIO_CPU_CORES_LIMIT 8 -#endif - -#ifndef FIO_DEFER_THROTTLE_PROGRESSIVE -/** - * The progressive throttling model makes concurrency and parallelism more - * likely. - * - * Otherwise threads are assumed to be intended for "fallback" in case of slow - * user code, where a single thread should be active most of the time and other - * threads are activated only when that single thread is slow to perform. - */ -#define FIO_DEFER_THROTTLE_PROGRESSIVE 1 -#endif - -#ifndef FIO_PRINT_STATE -/** - * Enables the depraceted FIO_LOG_STATE(msg,...) macro, which prints information - * level messages to stderr. - */ -#define FIO_PRINT_STATE 0 -#endif - -#ifndef FIO_PUBSUB_SUPPORT -/** - * If true (1), compiles the facil.io pub/sub API. - */ -#define FIO_PUBSUB_SUPPORT 1 -#endif - -#ifndef FIO_LOG_LENGTH_LIMIT -/** - * Since logging uses stack memory rather than dynamic allocation, it's memory - * usage must be limited to avoid exploding the stack. The following sets the - * memory used for a logging event. - */ -#define FIO_LOG_LENGTH_LIMIT 2048 -#endif - -#ifndef FIO_IGNORE_MACRO -/** - * This is used internally to ignore macros that shadow functions (avoiding - * named arguments when required). - */ -#define FIO_IGNORE_MACRO -#endif - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifndef __MINGW32__ -#include -#endif -#include -#ifdef __MINGW32__ -#include -#include -#include -#endif - -#if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS) -#define __attribute__(...) -#define __has_include(...) 0 -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#elif !defined(__clang__) && !defined(__has_builtin) -/* E.g: GCC < 6.0 doesn't support __has_builtin */ -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#endif - -#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 5)) -/* GCC < 4.5 doesn't support deprecation reason string */ -#define deprecated(reason) deprecated -#endif - -#ifndef FIO_FUNC -#define FIO_FUNC static __attribute__((unused)) -#endif - -#if defined(__FreeBSD__) -#include -#include -#endif - -#ifdef __MINGW32__ -#define __S_IFMT 0170000 -#define __S_IFLNK 0120000 -#define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) -#define S_ISLNK(mode) __S_ISTYPE((mode), __S_IFLNK) - -#define SIGKILL 9 -#define SIGTERM 15 -#define SIGCONT 17 - -#define pipe(fds) _pipe(fds, 65536, _O_BINARY) - -int fork(void); -int kill(int, int); -ssize_t pread(int, void*, size_t, off_t); -ssize_t pwrite(int, const void *, size_t, off_t); -int fio_osffd4fd(unsigned int); -#endif - -/* ***************************************************************************** -Patch for OSX version < 10.12 from https://stackoverflow.com/a/9781275/4025095 -***************************************************************************** */ -#if defined(__MACH__) && !defined(CLOCK_REALTIME) -#include -#define CLOCK_REALTIME 0 -#define clock_gettime patch_clock_gettime -// clock_gettime is not implemented on older versions of OS X (< 10.12). -// If implemented, CLOCK_REALTIME will have already been defined. -static inline int patch_clock_gettime(int clk_id, struct timespec *t) { - struct timeval now; - int rv = gettimeofday(&now, NULL); - if (rv) - return rv; - t->tv_sec = now.tv_sec; - t->tv_nsec = now.tv_usec * 1000; - return 0; - (void)clk_id; -} -#endif - -/* ***************************************************************************** -C++ extern start -***************************************************************************** */ -/* support C++ */ -#ifdef __cplusplus -extern "C" { -/* C++ keyword was deprecated */ -#define register -#endif - -/* ***************************************************************************** -Helper String Information Type -***************************************************************************** */ - -#ifndef FIO_STR_INFO_TYPE -/** A string information type, reports information about a C string. */ -typedef struct fio_str_info_s { - size_t capa; /* Buffer capacity, if the string is writable. */ - size_t len; /* String length. */ - char *data; /* String's first byte. */ -} fio_str_info_s; -#define FIO_STR_INFO_TYPE -#endif - -/* ***************************************************************************** - - - - - - - - - - - - -Memory pool / custom allocator for short lived objects - - - - - - - - - - - - -***************************************************************************** */ - -/* inform the compiler that the returned value is aligned on 16 byte marker */ -#if FIO_FORCE_MALLOC -#define FIO_ALIGN -#define FIO_ALIGN_NEW -#elif __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 8) -#define FIO_ALIGN __attribute__((assume_aligned(16))) -#define FIO_ALIGN_NEW __attribute__((malloc, assume_aligned(16))) -#else -#define FIO_ALIGN -#define FIO_ALIGN_NEW -#endif - -/** - * Allocates memory using a per-CPU core block memory pool. - * Memory is zeroed out. - * - * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (16Kb when using 32Kb blocks) - * will be redirected to `mmap`, as if `fio_mmap` was called. - */ -void *FIO_ALIGN_NEW fio_malloc(size_t size); - -/** - * same as calling `fio_malloc(size_per_unit * unit_count)`; - * - * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (16Kb when using 32Kb blocks) - * will be redirected to `mmap`, as if `fio_mmap` was called. - */ -void *FIO_ALIGN_NEW fio_calloc(size_t size_per_unit, size_t unit_count); - -/** Frees memory that was allocated using this library. */ -void fio_free(void *ptr); - -/** - * Re-allocates memory. An attempt to avoid copying the data is made only for - * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). - */ -void *FIO_ALIGN fio_realloc(void *ptr, size_t new_size); - -/** - * Re-allocates memory. An attempt to avoid copying the data is made only for - * big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). - * - * This variation is slightly faster as it might copy less data. - */ -void *FIO_ALIGN fio_realloc2(void *ptr, size_t new_size, size_t copy_length); - -/** - * Allocates memory directly using `mmap`, this is prefered for objects that - * both require almost a page of memory (or more) and expect a long lifetime. - * - * However, since this allocation will invoke the system call (`mmap`), it will - * be inherently slower. - * - * `fio_free` can be used for deallocating the memory. - */ -void *FIO_ALIGN_NEW fio_mmap(size_t size); - -/** - * When forking is called manually, call this function to reset the facil.io - * memory allocator's locks. - */ -void fio_malloc_after_fork(void); - -#undef FIO_ALIGN - -/* ***************************************************************************** - - - - - - - - - - - - -Logging and testing helpers - - - - - - - - - - - - -***************************************************************************** */ - -/** Logging level of zero (no logging). */ -#define FIO_LOG_LEVEL_NONE 0 -/** Log fatal errors. */ -#define FIO_LOG_LEVEL_FATAL 1 -/** Log errors and fatal errors. */ -#define FIO_LOG_LEVEL_ERROR 2 -/** Log warnings, errors and fatal errors. */ -#define FIO_LOG_LEVEL_WARNING 3 -/** Log every message (info, warnings, errors and fatal errors). */ -#define FIO_LOG_LEVEL_INFO 4 -/** Log everything, including debug messages. */ -#define FIO_LOG_LEVEL_DEBUG 5 - -#if FIO_LOG_LENGTH_LIMIT > 128 -#define FIO_LOG____LENGTH_ON_STACK FIO_LOG_LENGTH_LIMIT -#define FIO_LOG____LENGTH_BORDER (FIO_LOG_LENGTH_LIMIT - 32) -#else -#define FIO_LOG____LENGTH_ON_STACK (FIO_LOG_LENGTH_LIMIT + 32) -#define FIO_LOG____LENGTH_BORDER FIO_LOG_LENGTH_LIMIT -#endif -/** The logging level */ -int __attribute__((weak)) FIO_LOG_LEVEL; - -#pragma weak FIO_LOG2STDERR -void __attribute__((format(printf, 1, 0), weak)) -FIO_LOG2STDERR(const char *format, ...) { - char tmp___log[FIO_LOG____LENGTH_ON_STACK]; - va_list argv; - va_start(argv, format); - int len___log = vsnprintf(tmp___log, FIO_LOG_LENGTH_LIMIT - 2, format, argv); - va_end(argv); - if (len___log <= 0 || len___log >= FIO_LOG_LENGTH_LIMIT - 2) { - if (len___log >= FIO_LOG_LENGTH_LIMIT - 2) { - memcpy(tmp___log + FIO_LOG____LENGTH_BORDER, "... (warning: truncated).", - 25); - len___log = FIO_LOG____LENGTH_BORDER + 25; - } else { - fwrite("ERROR: log output error (can't write).\n", 39, 1, stderr); - return; - } - } - tmp___log[len___log++] = '\n'; - tmp___log[len___log] = '0'; - fwrite(tmp___log, len___log, 1, stderr); -} - -#ifndef FIO_LOG_PRINT -#define FIO_LOG_PRINT(level, ...) \ - do { \ - if (level <= FIO_LOG_LEVEL) { \ - FIO_LOG2STDERR(__VA_ARGS__); \ - } \ - } while (0) -#define FIO_LOG_DEBUG(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_DEBUG, \ - "DEBUG ("__FILE__ \ - ":" FIO_MACRO2STR(__LINE__) "): " __VA_ARGS__) -#define FIO_LOG_INFO(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_INFO, "INFO: " __VA_ARGS__) -#define FIO_LOG_WARNING(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_WARNING, "WARNING: " __VA_ARGS__) -#define FIO_LOG_ERROR(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_ERROR, "ERROR: " __VA_ARGS__) -#define FIO_LOG_FATAL(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_FATAL, "FATAL: " __VA_ARGS__) -#endif - -#if FIO_PRINT_STATE -#define FIO_LOG_STATE(...) \ - FIO_LOG_PRINT(FIO_LOG_LEVEL_INFO, \ - "WARNING: FIO_LOG_STATE is deprecated\n" __VA_ARGS__) -#else -#define FIO_LOG_STATE(...) -#endif - -#define FIO_ASSERT(cond, ...) \ - if (!(cond)) { \ - FIO_LOG_FATAL("(" __FILE__ ":" FIO_MACRO2STR(__LINE__) ") "__VA_ARGS__); \ - perror(" errno"); \ - exit(-1); \ - } - -#ifndef FIO_ASSERT_ALLOC -/** Tests for an allocation failure. The behavior can be overridden. */ -#define FIO_ASSERT_ALLOC(ptr) \ - if (!(ptr)) { \ - FIO_LOG_FATAL("memory allocation error "__FILE__ \ - ":" FIO_MACRO2STR(__LINE__)); \ - kill(0, SIGINT); \ - exit(errno); \ - } -#endif - -#if DEBUG -#define FIO_ASSERT_DEBUG(cond, ...) \ - if (!(cond)) { \ - FIO_LOG_DEBUG(__VA_ARGS__); \ - perror(" errno"); \ - exit(-1); \ - } -#else -#define FIO_ASSERT_DEBUG(...) -#endif - -/* ***************************************************************************** - - - - - - - - - - - - -Connection Callback (Protocol) Management - - - - - - - - - - - - -***************************************************************************** */ - -typedef struct fio_protocol_s fio_protocol_s; -/**************************************************************************/ /** -* The Protocol - -The Protocol struct defines the callbacks used for the connection and sets it's -behaviour. The Protocol struct is part of facil.io's core design. - -For concurrency reasons, a protocol instance SHOULD be unique to each -connections. Different connections shouldn't share a single protocol object -(callbacks and data can obviously be shared). - -All the callbacks receive a unique connection ID (a localized UUID) that can be -converted to the original file descriptor when in need. - -This allows facil.io to prevent old connection handles from sending data -to new connections after a file descriptor is "recycled" by the OS. -*/ -struct fio_protocol_s { - /** Called when a data is available, but will not run concurrently */ - void (*on_data)(intptr_t uuid, fio_protocol_s *protocol); - /** called once all pending `fio_write` calls are finished. */ - void (*on_ready)(intptr_t uuid, fio_protocol_s *protocol); - /** - * Called when the server is shutting down, immediately before closing the - * connection. - * - * The callback runs within a {FIO_PR_LOCK_TASK} lock, so it will never run - * concurrently with {on_data} or other connection specific tasks. - * - * The `on_shutdown` callback should return 0 to close the socket or a number - * between 1..254 to delay the socket closure by that amount of time. - * - * Once the socket wass marked for closure, facil.io will allow 8 seconds for - * all the data to be sent before forcfully closing the socket (regardless of - * state). - * - * If the `on_shutdown` returns 255, the socket is ignored and it will be - * abruptly terminated when all other sockets have finished their graceful - * shutdown procedure. - */ - uint8_t (*on_shutdown)(intptr_t uuid, fio_protocol_s *protocol); - /** Called when the connection was closed, but will not run concurrently */ - void (*on_close)(intptr_t uuid, fio_protocol_s *protocol); - /** called when a connection's timeout was reached */ - void (*ping)(intptr_t uuid, fio_protocol_s *protocol); - /** private metadata used by facil. */ - size_t rsv; -}; - -/** - * Attaches (or updates) a protocol object to a socket UUID. - * - * The new protocol object can be NULL, which will detach ("hijack"), the - * socket . - * - * The old protocol's `on_close` (if any) will be scheduled. - * - * On error, the new protocol's `on_close` callback will be called immediately. - */ -void fio_attach(intptr_t uuid, fio_protocol_s *protocol); - -/** - * Attaches (or updates) a protocol object to a file descriptor (fd). - * - * The new protocol object can be NULL, which will detach ("hijack"), the - * socket and the `fd` can be one created outside of facil.io. - * - * The old protocol's `on_close` (if any) will be scheduled. - * - * On error, the new protocol's `on_close` callback will be called immediately. - * - * NOTE: before attaching a file descriptor that was created outside of - * facil.io's library, make sure it is set to non-blocking mode (see - * `fio_set_non_block`). facil.io file descriptors are all non-blocking and it - * will assumes this is the case for the attached fd. - */ -void fio_attach_fd(int fd, fio_protocol_s *protocol); - -/** - * Sets a socket to non blocking state. - * - * This will also set the O_CLOEXEC flag for the file descriptor. - * - * This function is called automatically for the new socket, when using - * `fio_accept` or `fio_connect`. - */ -int fio_set_non_block(int fd); - -/** - * Returns the maximum number of open files facil.io can handle per worker - * process. - * - * Total OS limits might apply as well but aren't shown. - * - * The value of 0 indicates either that the facil.io library wasn't initialized - * yet or that it's resources were released. - */ -size_t fio_capa(void); - -/** Sets a timeout for a specific connection (only when running and valid). */ -void fio_timeout_set(intptr_t uuid, uint8_t timeout); - -/** Gets a timeout for a specific connection. Returns 0 if none. */ -uint8_t fio_timeout_get(intptr_t uuid); - -/** - * "Touches" a socket connection, resetting it's timeout counter. - */ -void fio_touch(intptr_t uuid); - -enum fio_io_event { - FIO_EVENT_ON_DATA, - FIO_EVENT_ON_READY, - FIO_EVENT_ON_TIMEOUT -}; -/** Schedules an IO event, even if it did not occur. */ -void fio_force_event(intptr_t uuid, enum fio_io_event); - -/** - * Temporarily prevents `on_data` events from firing. - * - * The `on_data` event will be automatically rescheduled when (if) the socket's - * outgoing buffer fills up or when `fio_force_event` is called with - * `FIO_EVENT_ON_DATA`. - * - * Note: the function will work as expected when called within the protocol's - * `on_data` callback and the `uuid` refers to a valid socket. Otherwise the - * function might quietly fail. - */ -void fio_suspend(intptr_t uuid); - -/* ***************************************************************************** -Listening to Incoming Connections -***************************************************************************** */ - -/* Arguments for the fio_listen function */ -struct fio_listen_args { - /** - * Called whenever a new connection is accepted. - * - * Should either call `fio_attach` or close the connection. - */ - void (*on_open)(intptr_t uuid, void *udata); - /** The network service / port. Defaults to "3000". */ - const char *port; - /** The socket binding address. Defaults to the recommended NULL. */ - const char *address; - /** a pointer to a `fio_tls_s` object, for SSL/TLS support (fio_tls.h). */ - void *tls; - /** Opaque user data. */ - void *udata; - /** - * Called when the server starts (or a worker process is respawned), allowing - * for further initialization, such as timed event scheduling or VM - * initialization. - * - * This will be called separately for every worker process whenever it is - * spawned. - */ - void (*on_start)(intptr_t uuid, void *udata); - /** - * Called when the server is done, usable for cleanup. - * - * This will be called separately for every process. */ - void (*on_finish)(intptr_t uuid, void *udata); -}; - -/** - * Sets up a network service on a listening socket. - * - * Returns the listening socket's uuid or -1 (on error). - * - * See the `fio_listen` Macro for details. - */ -intptr_t fio_listen(struct fio_listen_args args); - -/************************************************************************ */ /** -Listening to Incoming Connections -=== - -Listening to incoming connections is pretty straight forward. - -After a new connection is accepted, the `on_open` callback is called. `on_open` -should allocate the new connection's protocol and call `fio_attach` to attach -the protocol to the connection's uuid. - -The protocol's `on_close` callback is expected to handle any cleanup required. - -The following is an example echo server using facil.io: - -```c -#include - -// A callback to be called whenever data is available on the socket -static void echo_on_data(intptr_t uuid, fio_protocol_s *prt) { - (void)prt; // we can ignore the unused argument - // echo buffer - char buffer[1024] = {'E', 'c', 'h', 'o', ':', ' '}; - ssize_t len; - // Read to the buffer, starting after the "Echo: " - while ((len = fio_read(uuid, buffer + 6, 1018)) > 0) { - fprintf(stderr, "Read: %.*s", (int)len, buffer + 6); - // Write back the message - fio_write(uuid, buffer, len + 6); - // Handle goodbye - if ((buffer[6] | 32) == 'b' && (buffer[7] | 32) == 'y' && - (buffer[8] | 32) == 'e') { - fio_write(uuid, "Goodbye.\n", 9); - fio_close(uuid); - return; - } - } -} - -// A callback called whenever a timeout is reach -static void echo_ping(intptr_t uuid, fio_protocol_s *prt) { - (void)prt; // we can ignore the unused argument - fio_write(uuid, "Server: Are you there?\n", 23); -} - -// A callback called if the server is shutting down... -// ... while the connection is still open -static uint8_t echo_on_shutdown(intptr_t uuid, fio_protocol_s *prt) { - (void)prt; // we can ignore the unused argument - fio_write(uuid, "Echo server shutting down\nGoodbye.\n", 35); - return 0; -} - -static void echo_on_close(intptr_t uuid, fio_protocol_s *proto) { - fprintf(stderr, "Connection %p closed.\n", (void *)proto); - free(proto); - (void)uuid; -} - -// A callback called for new connections -static void echo_on_open(intptr_t uuid, void *udata) { - (void)udata; // ignore this - // Protocol objects MUST be dynamically allocated when multi-threading. - fio_protocol_s *echo_proto = malloc(sizeof(*echo_proto)); - *echo_proto = (fio_protocol_s){.service = "echo", - .on_data = echo_on_data, - .on_shutdown = echo_on_shutdown, - .on_close = echo_on_close, - .ping = echo_ping}; - fprintf(stderr, "New Connection %p received from %s\n", (void *)echo_proto, - fio_peer_addr(uuid).data); - fio_attach(uuid, echo_proto); - fio_write2(uuid, .data.buffer = "Echo Service: Welcome\n", .length = 22, - .after.dealloc = FIO_DEALLOC_NOOP); - fio_timeout_set(uuid, 5); -} - -int main() { - // Setup a listening socket - if (fio_listen(.port = "3000", .on_open = echo_on_open) == -1) { - perror("No listening socket available on port 3000"); - exit(-1); - } - // Run the server and hang until a stop signal is received. - fio_start(.threads = 4, .workers = 1); -} -``` -*/ -#define fio_listen(...) fio_listen((struct fio_listen_args){__VA_ARGS__}) - -/* ***************************************************************************** -Connecting to remote servers as a client -***************************************************************************** */ - -/** -Named arguments for the `fio_connect` function, that allows non-blocking -connections to be established. -*/ -struct fio_connect_args { - /** The address of the server we are connecting to. */ - const char *address; - /** The port on the server we are connecting to. */ - const char *port; - /** - * The `on_connect` callback either call `fio_attach` or close the connection. - */ - void (*on_connect)(intptr_t uuid, void *udata); - /** - * The `on_fail` is called when a socket fails to connect. The old sock UUID - * is passed along. - */ - void (*on_fail)(intptr_t uuid, void *udata); - /** a pointer to a `fio_tls_s` object, for SSL/TLS support (fio_tls.h). */ - void *tls; - /** Opaque user data. */ - void *udata; - /** A non-system timeout after which connection is assumed to have failed. */ - uint8_t timeout; -}; - -/** -Creates a client connection (in addition or instead of the server). - -See the `struct fio_connect_args` details for any possible named arguments. - -* `.address` should be the address of the server. - -* `.port` the server's port. - -* `.udata`opaque user data. - -* `.on_connect` called once a connection was established. - - Should return a pointer to a `fio_protocol_s` object, to handle connection - callbacks. - -* `.on_fail` called if a connection failed to establish. - -(experimental: untested) -*/ -intptr_t fio_connect(struct fio_connect_args); -#define fio_connect(...) fio_connect((struct fio_connect_args){__VA_ARGS__}) - -/* ***************************************************************************** -URL address parsing -***************************************************************************** */ - -/** the result returned by `fio_url_parse` */ -typedef struct { - fio_str_info_s scheme; - fio_str_info_s user; - fio_str_info_s password; - fio_str_info_s host; - fio_str_info_s port; - fio_str_info_s path; - fio_str_info_s query; - fio_str_info_s target; -} fio_url_s; - -/** - * Parses the URI returning it's components and their lengths (no decoding - * performed, doesn't accept decoded URIs). - * - * The returned string are NOT NUL terminated, they are merely locations within - * the original string. - * - * This function attempts to accept many different formats, including any of the - * following: - * - * * `/complete_path?query#target` - * - * i.e.: /index.html?page=1#list - * - * * `host:port/complete_path?query#target` - * - * i.e.: - * example.com - * example.com:8080 - * example.com/index.html - * example.com:8080/index.html - * example.com:8080/index.html?key=val#target - * - * * `user:password@host:port/path?query#target` - * - * i.e.: user:1234@example.com:8080/index.html - * - * * `username[:password]@host[:port][...]` - * - * i.e.: john:1234@example.com - * - * * `schema://user:password@host:port/path?query#target` - * - * i.e.: http://example.com/index.html?page=1#list - * - * Invalid formats might produce unexpected results. No error testing performed. - */ -fio_url_s fio_url_parse(const char *url, size_t length); -/* ***************************************************************************** -Starting the IO reactor and reviewing it's state -***************************************************************************** */ - -struct fio_start_args { - /** - * The number of threads to run in the thread pool. Has "smart" defaults. - * - * - * A positive value will indicate a set number of threads (or workers). - * - * Zeros and negative values are fun and include an interesting shorthand: - * - * * Negative values indicate a fraction of the number of CPU cores. i.e. - * -2 will normally indicate "half" (1/2) the number of cores. - * - * * If the other option (i.e. `.workers` when setting `.threads`) is zero, - * it will be automatically updated to reflect the option's absolute value. - * i.e.: - * if .threads == -2 and .workers == 0, - * than facil.io will run 2 worker processes with (cores/2) threads per - * process. - */ - int16_t threads; - /** The number of worker processes to run. See `threads`. */ - int16_t workers; -}; - -/** - * Starts the facil.io event loop. This function will return after facil.io is - * done (after shutdown). - * - * See the `struct fio_start_args` details for any possible named arguments. - * - * This method blocks the current thread until the server is stopped (when a - * SIGINT/SIGTERM is received). - */ -void fio_start(struct fio_start_args args); -#define fio_start(...) fio_start((struct fio_start_args){__VA_ARGS__}) - -/** - * Attempts to stop the facil.io application. This only works within the Root - * process. A worker process will simply respawn itself. - */ -void fio_stop(void); - -/** - * Returns the number of expected threads / processes to be used by facil.io. - * - * The pointers should start with valid values that match the expected threads / - * processes values passed to `fio_start`. - * - * The data in the pointers will be overwritten with the result. - */ -void fio_expected_concurrency(int16_t *threads, int16_t *workers); - -/** - * Returns the number of worker processes if facil.io is running. - * - * (1 is returned when in single process mode, otherwise the number of workers) - */ -int16_t fio_is_running(void); - -/** - * Returns 1 if the current process is a worker process or a single process. - * - * Otherwise returns 0. - * - * NOTE: When cluster mode is off, the root process is also the worker process. - * This means that single process instances don't automatically respawn - * after critical errors. - */ -int fio_is_worker(void); - -/** - * Returns 1 if the current process is the master (root) process. - * - * Otherwise returns 0. - */ -int fio_is_master(void); - -/** Returns facil.io's parent (root) process pid. */ -pid_t fio_parent_pid(void); - -/** - * Initializes zombie reaping for the process. Call before `fio_start` to enable - * global zombie reaping. - */ -void fio_reap_children(void); - -/** - * Resets any existing signal handlers, restoring their state to before they - * were set by facil.io. - * - * This stops both child reaping (`fio_reap_children`) and the default facil.io - * signal handlers (i.e., CTRL-C). - * - * This function will be called automatically by facil.io whenever facil.io - * stops. - */ -void fio_signal_handler_reset(void); - -/** - * Returns the last time the server reviewed any pending IO events. - */ -struct timespec fio_last_tick(void); - -/** - * Returns a C string detailing the IO engine selected during compilation. - * - * Valid values are "kqueue", "epoll" and "poll". - */ -char const *fio_engine(void); - -/* ***************************************************************************** -Socket / Connection Functions -***************************************************************************** */ - -/** - * Creates a Unix or a TCP/IP socket and returns it's unique identifier. - * - * For TCP/IP server sockets (`is_server` is `1`), a NULL `address` variable is - * recommended. Use "localhost" or "127.0.0.1" to limit access to the server - * application. - * - * For TCP/IP client sockets (`is_server` is `0`), a remote `address` and `port` - * combination will be required - * - * For Unix server or client sockets, set the `port` variable to NULL or `0`. - * - * Returns -1 on error. Any other value is a valid unique identifier. - * - * Note: facil.io uses unique identifiers to protect sockets from collisions. - * However these identifiers can be converted to the underlying file - * descriptor using the `fio_uuid2fd` macro. - */ -intptr_t fio_socket(const char *address, const char *port, uint8_t is_server); - -/** - * `fio_accept` accepts a new socket connection from a server socket - see the - * server flag on `fio_socket`. - * - * Accepted connection are automatically set to non-blocking mode and the - * O_CLOEXEC flag is set. - * - * NOTE: this function does NOT attach the socket to the IO reactor - see - * `fio_attach`. - */ -intptr_t fio_accept(intptr_t srv_uuid); - -/** - * Returns 1 if the uuid refers to a valid and open, socket. - * - * Returns 0 if not. - */ -int fio_is_valid(intptr_t uuid); - -/** - * Returns 1 if the uuid is invalid or the socket is flagged to be closed. - * - * Returns 0 if the socket is valid, open and isn't flagged to be closed. - */ -int fio_is_closed(intptr_t uuid); - -/** - * `fio_close` marks the connection for disconnection once all the data was - * sent. The actual disconnection will be managed by the `fio_flush` function. - * - * `fio_flash` will be automatically scheduled. - */ -void fio_close(intptr_t uuid); - -/** - * `fio_force_close` closes the connection immediately, without adhering to any - * protocol restrictions and without sending any remaining data in the - * connection buffer. - */ -void fio_force_close(intptr_t uuid); - -/** - * Returns the information available about the socket's peer address. - * - * If no information is available, the struct will be initialized with zero - * (`addr == NULL`). - * The information is only available when the socket was accepted using - * `fio_accept` or opened using `fio_connect`. - */ -fio_str_info_s fio_peer_addr(intptr_t uuid); - -/** - * Writes the local machine address (qualified host name) to the buffer. - * - * Returns the amount of data written (excluding the NUL byte). - * - * `limit` is the maximum number of bytes in the buffer, including the NUL byte. - * - * If the returned value == limit - 1, the result might have been truncated. - * - * If 0 is returned, an erro might have occured (see `errno`) and the contents - * of `dest` is undefined. - */ -size_t fio_local_addr(char *dest, size_t limit); - -/** - * `fio_read` attempts to read up to count bytes from the socket into the - * buffer starting at `buffer`. - * - * `fio_read`'s return values are wildly different then the native return - * values and they aim at making far simpler sense. - * - * `fio_read` returns the number of bytes read (0 is a valid return value which - * simply means that no bytes were read from the buffer). - * - * On a fatal connection error that leads to the connection being closed (or if - * the connection is already closed), `fio_read` returns -1. - * - * The value 0 is the valid value indicating no data was read. - * - * Data might be available in the kernel's buffer while it is not available to - * be read using `fio_read` (i.e., when using a transport layer, such as TLS). - */ -ssize_t fio_read(intptr_t uuid, void *buffer, size_t count); - -/** The following structure is used for `fio_write2_fn` function arguments. */ -typedef struct { - union { - /** The in-memory data to be sent. */ - const void *buffer; - /** The data to be sent, if this is a file. */ - const intptr_t fd; - } data; - union { - /** - * This deallocation callback will be called when the packet is finished - * with the buffer. - * - * If no deallocation callback is set, `free` (or `close`) will be used. - * - * Note: socket library functions MUST NEVER be called by a callback, or a - * deadlock might occur. - */ - void (*dealloc)(void *buffer); - /** - * This is an alternative deallocation callback accessor (same memory space - * as `dealloc`) for conveniently setting the file `close` callback. - * - * Note: `sock` library functions MUST NEVER be called by a callback, or a - * deadlock might occur. - */ - void (*close)(intptr_t fd); - } after; - /** The length (size) of the buffer, or the amount of data to be sent from the - * file descriptor. - */ - uintptr_t length; - /** Starting point offset from the buffer or file descriptor's beginning. */ - uintptr_t offset; - /** The packet will be sent as soon as possible. */ - unsigned urgent : 1; - /** - * The data union contains the value of a file descriptor (`int`). i.e.: - * `.data.fd = fd` or `.data.buffer = (void*)fd;` - */ - unsigned is_fd : 1; - /** for internal use */ - unsigned rsv : 1; - /** for internal use */ - unsigned rsv2 : 1; -} fio_write_args_s; - -/** - * `fio_write2_fn` is the actual function behind the macro `fio_write2`. - */ -ssize_t fio_write2_fn(intptr_t uuid, fio_write_args_s options); - -/** - * Schedules data to be written to the socket. - * - * `fio_write2` is similar to `fio_write`, except that it allows far more - * flexibility. - * - * On error, -1 will be returned. Otherwise returns 0. - * - * See the `fio_write_args_s` structure for details. - * - * NOTE: The data is "moved" to the ownership of the socket, not copied. The - * data will be deallocated according to the `.after.dealloc` function. - */ -#define fio_write2(uuid, ...) \ - fio_write2_fn(uuid, (fio_write_args_s){__VA_ARGS__}) - -/** A noop function for fio_write2 in cases not deallocation is required. */ -void FIO_DEALLOC_NOOP(void *arg); -#define FIO_CLOSE_NOOP ((void (*)(intptr_t))FIO_DEALLOC_NOOP) - -/** - * `fio_write` copies `legnth` data from the buffer and schedules the data to - * be sent over the socket. - * - * The data isn't necessarily written to the socket. The actual writing to the - * socket is handled by the IO reactor. - * - * On error, -1 will be returned. Otherwise returns 0. - * - * Returns the same values as `fio_write2`. - */ -// ssize_t fio_write(uintptr_t uuid, void *buffer, size_t legnth); -inline FIO_FUNC ssize_t fio_write(const intptr_t uuid, const void *buffer, - const size_t length) { - if (!length || !buffer) - return 0; - void *cpy = fio_malloc(length); - if (!cpy) - return -1; - memcpy(cpy, buffer, length); - return fio_write2(uuid, .data.buffer = cpy, .length = length, - .after.dealloc = fio_free); -} - -/** - * Sends data from a file as if it were a single atomic packet (sends up to - * length bytes or until EOF is reached). - * - * Once the file was sent, the `source_fd` will be closed using `close`. - * - * The file will be buffered to the socket chunk by chunk, so that memory - * consumption is capped. The system's `sendfile` might be used if conditions - * permit. - * - * `offset` dictates the starting point for the data to be sent and length sets - * the maximum amount of data to be sent. - * - * Returns -1 and closes the file on error. Returns 0 on success. - */ -inline FIO_FUNC ssize_t fio_sendfile(intptr_t uuid, intptr_t source_fd, - off_t offset, size_t length) { - return fio_write2(uuid, .data.fd = source_fd, .length = length, .is_fd = 1, - .offset = (uintptr_t)offset); -} - -/** - * Returns the number of `fio_write` calls that are waiting in the socket's - * queue and haven't been processed. - */ -size_t fio_pending(intptr_t uuid); - -/** - * `fio_flush` attempts to write any remaining data in the internal buffer to - * the underlying file descriptor and closes the underlying file descriptor once - * if it's marked for closure (and all the data was sent). - * - * Return values: 1 will be returned if data remains in the buffer. 0 - * will be returned if the buffer was fully drained. -1 will be returned on an - * error or when the connection is closed. - * - * errno will be set to EWOULDBLOCK if the socket's lock is busy. - */ -ssize_t fio_flush(intptr_t uuid); - -/** Blocks until all the data was flushed from the buffer */ -#define fio_flush_strong(uuid) \ - do { \ - errno = 0; \ - } while (fio_flush(uuid) > 0 || errno == EWOULDBLOCK) - -/** - * `fio_flush_all` attempts flush all the open connections. - * - * Returns the number of sockets still in need to be flushed. - */ -size_t fio_flush_all(void); - -/** - * Convert between a facil.io connection's identifier (uuid) and system's fd. - */ -#define fio_uuid2fd(uuid) ((int)((uintptr_t)uuid >> 8)) - -/** - * `fio_fd2uuid` takes an existing file decriptor `fd` and returns it's active - * `uuid`. - * - * If the file descriptor was closed, __it will be registered as open__. - * - * If the file descriptor was closed directly (not using `fio_close`) or the - * closure event hadn't been processed, a false positive will be possible. This - * is not an issue, since the use of an invalid fd will result in the registry - * being updated and the fd being closed. - * - * Returns -1 on error. Returns a valid socket (non-random) UUID. - */ -intptr_t fio_fd2uuid(int fd); - -/** - * `fio_fd2uuid` takes an existing file decriptor `fd` and returns it's active - * `uuid`. - * - * If the file descriptor is marked as closed (wasn't opened / registered with - * facil.io) the function returns -1; - * - * If the file descriptor was closed directly (not using `fio_close`) or the - * closure event hadn't been processed, a false positive will be possible. This - * is not an issue, since the use of an invalid fd will result in the registry - * being updated and the fd being closed. - * - * Returns -1 on error. Returns a valid socket (non-random) UUID. - */ -intptr_t fio_fd2uuid(int fd); - -/* ***************************************************************************** -Connection Object Links -***************************************************************************** */ - -/** - * Links an object to a connection's lifetime, calling the `on_close` callback - * once the connection has died. - * - * If the `uuid` is invalid, the `on_close` callback will be called immediately. - * - * NOTE: the `on_close` callback will be called with high priority. Long tasks - * should be deferred. - */ -void fio_uuid_link(intptr_t uuid, void *obj, void (*on_close)(void *obj)); - -/** - * Un-links an object from the connection's lifetime, so it's `on_close` - * callback will NOT be called. - * - * Returns 0 on success and -1 if the object couldn't be found, setting `errno` - * to `EBADF` if the `uuid` was invalid and `ENOTCONN` if the object wasn't - * found (wasn't linked). - * - * NOTICE: a failure likely means that the object's `on_close` callback was - * already called! - */ -int fio_uuid_unlink(intptr_t uuid, void *obj); - -/* ***************************************************************************** -Connection Read / Write Hooks, for overriding the system calls -***************************************************************************** */ - -/** - * The following struct is used for setting a the read/write hooks that will - * replace the default system calls to `recv` and `write`. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -typedef struct fio_rw_hook_s { - /** - * Implement reading from a file descriptor. Should behave like the file - * system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ - ssize_t (*read)(intptr_t uuid, void *udata, void *buf, size_t count); - /** - * Implement writing to a file descriptor. Should behave like the file system - * `write` call. - * - * If an internal buffer is implemented and it is full, errno should be set to - * EWOULDBLOCK and the function should return -1. - * - * The function is expected to call the `flush` callback (or it's logic) - * internally. Either `write` OR `flush` are called. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ - ssize_t (*write)(intptr_t uuid, void *udata, const void *buf, size_t count); - /** - * When implemented, this function will be called to flush any data remaining - * in the internal buffer. - * - * The function should return the number of bytes remaining in the internal - * buffer (0 is a valid response) or -1 (on error). - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ - ssize_t (*flush)(intptr_t uuid, void *udata); - /** - * The `before_close` callback is called only once before closing the `uuid` - * and it might not get called at all if an abnormal closure is detected. - * - * If the function returns a non-zero value, than closure will be delayed - * until the `flush` returns 0 (or less). This allows a closure signal to be - * sent by the read/write hook when such a signal is required. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - * */ - ssize_t (*before_close)(intptr_t uuid, void *udata); - /** - * Called to perform cleanup after the socket was closed or a new read/write - * hook was set using `fio_rw_hook_set`. - * - * This callback is always called, even if `fio_rw_hook_set` fails. - * */ - void (*cleanup)(void *udata); -} fio_rw_hook_s; - -/** Sets a socket hook state (a pointer to the struct). */ -int fio_rw_hook_set(intptr_t uuid, fio_rw_hook_s *rw_hooks, void *udata); - -/** - * Replaces an existing read/write hook with another from within a read/write - * hook callback. - * - * Does NOT call any cleanup callbacks. - * - * Replaces existing udata. Call with the existing udata to keep it. - * - * Returns -1 on error, 0 on success. - * - * Note: this function is marked as unsafe, since it should only be called from - * within an existing read/write hook callback. Otherwise, data corruption - * might occur. - */ -int fio_rw_hook_replace_unsafe(intptr_t uuid, fio_rw_hook_s *rw_hooks, - void *udata); - -/** The default Read/Write hooks used for system Read/Write (udata == NULL). */ -extern const fio_rw_hook_s FIO_DEFAULT_RW_HOOKS; - -/* ***************************************************************************** -Concurrency overridable functions - -These functions can be overridden so as to adjust for different environments. -***************************************************************************** */ - -/** -OVERRIDE THIS to replace the default `fork` implementation. - -Behaves like the system's `fork`. -*/ -int fio_fork(void); - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Accepts a pointer to a function and a single argument that should be executed - * within a new thread. - * - * The function should allocate memory for the thread object and return a - * pointer to the allocated memory that identifies the thread. - * - * On error NULL should be returned. - */ -void *fio_thread_new(void *(*thread_func)(void *), void *arg); - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Frees the memory associated with a thread identifier (allows the thread to - * run it's course, just the identifier is freed). - */ -void fio_thread_free(void *p_thr); - -/** - * OVERRIDE THIS to replace the default pthread implementation. - * - * Accepts a pointer returned from `fio_thread_new` (should also free any - * allocated memory) and joins the associated thread. - * - * Return value is ignored. - */ -int fio_thread_join(void *p_thr); - -/* ***************************************************************************** -Connection Task scheduling -***************************************************************************** */ - -/** - * This is used to lock the protocol againste concurrency collisions and - * concurrent memory deallocation. - * - * However, there are three levels of protection that allow non-coliding tasks - * to protect the protocol object from being deallocated while in use: - * - * * `FIO_PR_LOCK_TASK` - a task lock locks might change data owned by the - * protocol object. This task is used for tasks such as `on_data`. - * - * * `FIO_PR_LOCK_WRITE` - a lock that promises only to use static data (data - * that tasks never changes) in order to write to the underlying socket. - * This lock is used for tasks such as `on_ready` and `ping` - * - * * `FIO_PR_LOCK_STATE` - a lock that promises only to retrieve static data - * (data that tasks never changes), performing no actions. This usually - * isn't used for client side code (used internally by facil) and is only - * meant for very short locks. - */ -enum fio_protocol_lock_e { - FIO_PR_LOCK_TASK = 0, - FIO_PR_LOCK_WRITE = 1, - FIO_PR_LOCK_STATE = 2 -}; - -/** Named arguments for the `fio_defer` function. */ -typedef struct { - /** The type of task to be performed. Defaults to `FIO_PR_LOCK_TASK` but could - * also be seto to `FIO_PR_LOCK_WRITE`. */ - enum fio_protocol_lock_e type; - /** The task (function) to be performed. This is required. */ - void (*task)(intptr_t uuid, fio_protocol_s *, void *udata); - /** An opaque user data that will be passed along to the task. */ - void *udata; - /** A fallback task, in case the connection was lost. Good for cleanup. */ - void (*fallback)(intptr_t uuid, void *udata); -} fio_defer_iotask_args_s; - -/** - * Schedules a protected connection task. The task will run within the - * connection's lock. - * - * If an error ocuurs or the connection is closed before the task can run, the - * `fallback` task wil be called instead, allowing for resource cleanup. - */ -void fio_defer_io_task(intptr_t uuid, fio_defer_iotask_args_s args); -#define fio_defer_io_task(uuid, ...) \ - fio_defer_io_task((uuid), (fio_defer_iotask_args_s){__VA_ARGS__}) - -/* ***************************************************************************** -Event / Task scheduling -***************************************************************************** */ - -/** - * Defers a task's execution. - * - * Tasks are functions of the type `void task(void *, void *)`, they return - * nothing (void) and accept two opaque `void *` pointers, user-data 1 - * (`udata1`) and user-data 2 (`udata2`). - * - * Returns -1 or error, 0 on success. - */ -int fio_defer(void (*task)(void *, void *), void *udata1, void *udata2); - -/** - * Creates a timer to run a task at the specified interval. - * - * The task will repeat `repetitions` times. If `repetitions` is set to 0, task - * will repeat forever. - * - * Returns -1 on error. - * - * The `on_finish` handler is always called (even on error). - */ -int fio_run_every(size_t milliseconds, size_t repetitions, void (*task)(void *), - void *arg, void (*on_finish)(void *)); - -/** - * Performs all deferred tasks. - */ -void fio_defer_perform(void); - -/** Returns true if there are deferred functions waiting for execution. */ -int fio_defer_has_queue(void); - -/* ***************************************************************************** -Startup / State Callbacks (fork, start up, idle, etc') -***************************************************************************** */ - -/** a callback type signifier */ -typedef enum { - /** Called once during library initialization. */ - FIO_CALL_ON_INITIALIZE, - /** Called once before starting up the IO reactor. */ - FIO_CALL_PRE_START, - /** Called before each time the IO reactor forks a new worker. */ - FIO_CALL_BEFORE_FORK, - /** Called after each fork (both in parent and workers). */ - FIO_CALL_AFTER_FORK, - /** Called by a worker process right after forking. */ - FIO_CALL_IN_CHILD, - /** Called by the master process after spawning a worker (after forking). */ - FIO_CALL_IN_MASTER, - /** Called every time a *Worker* proceess starts. */ - FIO_CALL_ON_START, - /** Called when facil.io enters idling mode. */ - FIO_CALL_ON_IDLE, - /** Called before starting the shutdown sequence. */ - FIO_CALL_ON_SHUTDOWN, - /** Called just before finishing up (both on chlid and parent processes). */ - FIO_CALL_ON_FINISH, - /** Called by each worker the moment it detects the master process crashed. */ - FIO_CALL_ON_PARENT_CRUSH, - /** Called by the parent (master) after a worker process crashed. */ - FIO_CALL_ON_CHILD_CRUSH, - /** An alternative to the system's at_exit. */ - FIO_CALL_AT_EXIT, - /** used for testing. */ - FIO_CALL_NEVER -} callback_type_e; - -/** Adds a callback to the list of callbacks to be called for the event. */ -void fio_state_callback_add(callback_type_e, void (*func)(void *), void *arg); - -/** Removes a callback from the list of callbacks to be called for the event. */ -int fio_state_callback_remove(callback_type_e, void (*func)(void *), void *arg); - -/** - * Forces all the existing callbacks to run, as if the event occurred. - * - * Callbacks are called from last to first (last callback executes first). - * - * During an event, changes to the callback list are ignored (callbacks can't - * remove other callbacks for the same event). - */ -void fio_state_callback_force(callback_type_e); - -/** Clears all the existing callbacks for the event. */ -void fio_state_callback_clear(callback_type_e); - -/* ***************************************************************************** -Lower Level API - for special circumstances, use with care. -***************************************************************************** */ - -/** - * This function allows out-of-task access to a connection's `fio_protocol_s` - * object by attempting to acquire a locked pointer. - * - * CAREFUL: mostly, the protocol object will be locked and a pointer will be - * sent to the connection event's callback. However, if you need access to the - * protocol object from outside a running connection task, you might need to - * lock the protocol to prevent it from being closed / freed in the background. - * - * facil.io uses three different locks: - * - * * FIO_PR_LOCK_TASK locks the protocol for normal tasks (i.e. `on_data`, - * `fio_defer`, `fio_every`). - * - * * FIO_PR_LOCK_WRITE locks the protocol for high priority `fio_write` - * oriented tasks (i.e. `ping`, `on_ready`). - * - * * FIO_PR_LOCK_STATE locks the protocol for quick operations that need to copy - * data from the protocol's data structure. - * - * IMPORTANT: Remember to call `fio_protocol_unlock` using the same lock type. - * - * Returns NULL on error (lock busy == EWOULDBLOCK, connection invalid == EBADF) - * and a pointer to a protocol object on success. - * - * On error, consider calling `fio_defer` or `defer` instead of busy waiting. - * Busy waiting SHOULD be avoided whenever possible. - */ -fio_protocol_s *fio_protocol_try_lock(intptr_t uuid, enum fio_protocol_lock_e); -/** Don't unlock what you don't own... see `fio_protocol_try_lock` for - * details. */ -void fio_protocol_unlock(fio_protocol_s *pr, enum fio_protocol_lock_e); - -/* ***************************************************************************** - * Pub/Sub / Cluster Messages API - * - * Facil supports a message oriented API for use for Inter Process Communication - * (IPC), publish/subscribe patterns, horizontal scaling and similar use-cases. - * - **************************************************************************** */ -#if FIO_PUBSUB_SUPPORT - -/* ***************************************************************************** - * Cluster Messages and Pub/Sub - **************************************************************************** */ - -/** An opaque subscription type. */ -typedef struct subscription_s subscription_s; - -/** A pub/sub engine data structure. See details later on. */ -typedef struct fio_pubsub_engine_s fio_pubsub_engine_s; - -/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ -extern fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT; -/** Used to publish the message to all clients in the cluster. */ -#define FIO_PUBSUB_CLUSTER ((fio_pubsub_engine_s *)1) -/** Used to publish the message only within the current process. */ -#define FIO_PUBSUB_PROCESS ((fio_pubsub_engine_s *)2) -/** Used to publish the message except within the current process. */ -#define FIO_PUBSUB_SIBLINGS ((fio_pubsub_engine_s *)3) -/** Used to publish the message exclusively to the root / master process. */ -#define FIO_PUBSUB_ROOT ((fio_pubsub_engine_s *)4) - -/** Message structure, with an integer filter as well as a channel filter. */ -typedef struct fio_msg_s { - /** A unique message type. Negative values are reserved, 0 == pub/sub. */ - int32_t filter; - /** - * A channel name, allowing for pub/sub patterns. - * - * NOTE: the channel and msg strings should be considered immutable. The .capa - * field might be used for internal data. - */ - fio_str_info_s channel; - /** - * The actual message. - * - * NOTE: the channel and msg strings should be considered immutable. The .capa - *field might be used for internal data. - **/ - fio_str_info_s msg; - /** The `udata1` argument associated with the subscription. */ - void *udata1; - /** The `udata1` argument associated with the subscription. */ - void *udata2; - /** flag indicating if the message is JSON data or binary/text. */ - uint8_t is_json; -} fio_msg_s; - -/** - * Pattern matching callback type - should return 0 unless channel matches - * pattern. - */ -typedef int (*fio_match_fn)(fio_str_info_s pattern, fio_str_info_s channel); - -extern fio_match_fn FIO_MATCH_GLOB; - -/** - * Possible arguments for the fio_subscribe method. - * - * NOTICE: passing protocol objects to the `udata` is not safe. This is because - * protocol objects might be destroyed or invalidated according to both network - * events (socket closure) and internal changes (i.e., `fio_attach` being - * called). The preferred way is to add the `uuid` to the `udata` field and call - * `fio_protocol_try_lock`. - */ -typedef struct { - /** - * If `filter` is set, all messages that match the filter's numerical value - * will be forwarded to the subscription's callback. - * - * Subscriptions can either require a match by filter or match by channel. - * This will match the subscription by filter. - */ - int32_t filter; - /** - * If `channel` is set, all messages where `filter == 0` and the channel is an - * exact match will be forwarded to the subscription's callback. - * - * Subscriptions can either require a match by filter or match by channel. - * This will match the subscription by channel (only messages with no `filter` - * will be received. - */ - fio_str_info_s channel; - /** - * The the `match` function allows pattern matching for channel names. - * - * When using a match function, the channel name is considered to be a pattern - * and each pub/sub message (a message where filter == 0) will be tested - * against that pattern. - * - * Using pattern subscriptions extensively could become a performance concern, - * since channel names are tested against each distinct pattern rather than - * leveraging a hashmap for possible name matching. - */ - fio_match_fn match; - /** - * The callback will be called for each message forwarded to the subscription. - */ - void (*on_message)(fio_msg_s *msg); - /** An optional callback for when a subscription is fully canceled. */ - void (*on_unsubscribe)(void *udata1, void *udata2); - /** The udata values are ignored and made available to the callback. */ - void *udata1; - /** The udata values are ignored and made available to the callback. */ - void *udata2; -} subscribe_args_s; - -/** Publishing and on_message callback arguments. */ -typedef struct fio_publish_args_s { - /** The pub/sub engine that should be used to forward this message. */ - fio_pubsub_engine_s const *engine; - /** A unique message type. Negative values are reserved, 0 == pub/sub. */ - int32_t filter; - /** The pub/sub target channnel. */ - fio_str_info_s channel; - /** The pub/sub message. */ - fio_str_info_s message; - /** flag indicating if the message is JSON data or binary/text. */ - uint8_t is_json; -} fio_publish_args_s; - -/** - * Subscribes to either a filter OR a channel (never both). - * - * Returns a subscription pointer on success or NULL on failure. - * - * See `subscribe_args_s` for details. - */ -subscription_s *fio_subscribe(subscribe_args_s args); -/** - * Subscribes to either a filter OR a channel (never both). - * - * Returns a subscription pointer on success or NULL on failure. - * - * See `subscribe_args_s` for details. - */ -#define fio_subscribe(...) fio_subscribe((subscribe_args_s){__VA_ARGS__}) - -/** - * Cancels an existing subscriptions - actual effects might be delayed, for - * example, if the subscription's callback is running in another thread. - */ -void fio_unsubscribe(subscription_s *subscription); - -/** - * This helper returns a temporary String with the subscription's channel (or a - * string representing the filter). - * - * To keep the string beyond the lifetime of the subscription, copy the string. - */ -fio_str_info_s fio_subscription_channel(subscription_s *subscription); - -/** - * Publishes a message to the relevant subscribers (if any). - * - * See `fio_publish_args_s` for details. - * - * By default the message is sent using the FIO_PUBSUB_CLUSTER engine (all - * processes, including the calling process). - * - * To limit the message only to other processes (exclude the calling process), - * use the FIO_PUBSUB_SIBLINGS engine. - * - * To limit the message only to the calling process, use the - * FIO_PUBSUB_PROCESS engine. - * - * To publish messages to the pub/sub layer, the `.filter` argument MUST be - * equal to 0 or missing. - */ -void fio_publish(fio_publish_args_s args); -/** - * Publishes a message to the relevant subscribers (if any). - * - * See `fio_publish_args_s` for details. - * - * By default the message is sent using the FIO_PUBSUB_CLUSTER engine (all - * processes, including the calling process). - * - * To limit the message only to other processes (exclude the calling process), - * use the FIO_PUBSUB_SIBLINGS engine. - * - * To limit the message only to the calling process, use the - * FIO_PUBSUB_PROCESS engine. - * - * To publish messages to the pub/sub layer, the `.filter` argument MUST be - * equal to 0 or missing. - */ -#define fio_publish(...) fio_publish((fio_publish_args_s){__VA_ARGS__}) -/** for backwards compatibility */ -#define pubsub_publish fio_publish - -/** Finds the message's metadata by it's type ID. Returns the data or NULL. */ -void *fio_message_metadata(fio_msg_s *msg, intptr_t type_id); - -/** - * Defers the current callback, so it will be called again for the message. - */ -void fio_message_defer(fio_msg_s *msg); - -/* ***************************************************************************** - * Cluster / Pub/Sub Middleware and Extensions ("Engines") - **************************************************************************** */ - -/** Contains message metadata, set by message extensions. */ -typedef struct fio_msg_metadata_s fio_msg_metadata_s; -struct fio_msg_metadata_s { - /** - * The type ID should be used to identify the metadata's actual structure. - * - * Negative ID values are reserved for internal use. - */ - intptr_t type_id; - /** - * This method will be called by facil.io to cleanup the metadata resources. - * - * Don't alter / call this method, this data is reserved. - */ - void (*on_finish)(fio_msg_s *msg, void *metadata); - /** The pointer to be disclosed to the `fio_message_metadata` function. */ - void *metadata; -}; - -/** - * Pub/Sub Metadata callback type. - */ -typedef fio_msg_metadata_s (*fio_msg_metadata_fn)(fio_str_info_s ch, - fio_str_info_s msg, - uint8_t is_json); - -/** - * It's possible to attach metadata to facil.io pub/sub messages (filter == 0) - * before they are published. - * - * This allows, for example, messages to be encoded as network packets for - * outgoing protocols (i.e., encoding for WebSocket transmissions), improving - * performance in large network based broadcasting. - * - * The callback should return a valid metadata object. If the `.metadata` field - * returned is NULL than the result will be ignored. - * - * To remove a callback, set the `enable` flag to false (`0`). - * - * The cluster messaging system allows some messages to be flagged as JSON and - * this flag is available to the metadata callback. - */ -void fio_message_metadata_callback_set(fio_msg_metadata_fn callback, - int enable); - -/** - * facil.io can be linked with external Pub/Sub services using "engines". - * - * Only unfiltered messages and subscriptions (where filter == 0) will be - * forwarded to external Pub/Sub services. - * - * Engines MUST provide the listed function pointers and should be attached - * using the `fio_pubsub_attach` function. - * - * Engines should disconnect / detach, before being destroyed, by using the - * `fio_pubsub_detach` function. - * - * When an engine received a message to publish, it should call the - * `pubsub_publish` function with the engine to which the message is forwarded. - * i.e.: - * - * pubsub_publish( - * .engine = FIO_PROCESS_ENGINE, - * .channel = channel_name, - * .message = msg_body ); - * - * IMPORTANT: The `subscribe` and `unsubscribe` callbacks are called from within - * an internal lock. They MUST NEVER call pub/sub functions except by - * exiting the lock using `fio_defer`. - */ -struct fio_pubsub_engine_s { - /** Should subscribe channel. Failures are ignored. */ - void (*subscribe)(const fio_pubsub_engine_s *eng, fio_str_info_s channel, - fio_match_fn match); - /** Should unsubscribe channel. Failures are ignored. */ - void (*unsubscribe)(const fio_pubsub_engine_s *eng, fio_str_info_s channel, - fio_match_fn match); - /** Should publish a message through the engine. Failures are ignored. */ - void (*publish)(const fio_pubsub_engine_s *eng, fio_str_info_s channel, - fio_str_info_s msg, uint8_t is_json); -}; - -/** - * Attaches an engine, so it's callback can be called by facil.io. - * - * The `subscribe` callback will be called for every existing channel. - * - * NOTE: the root (master) process will call `subscribe` for any channel in any - * process, while all the other processes will call `subscribe` only for their - * own channels. This allows engines to use the root (master) process as an - * exclusive subscription process. - */ -void fio_pubsub_attach(fio_pubsub_engine_s *engine); - -/** Detaches an engine, so it could be safely destroyed. */ -void fio_pubsub_detach(fio_pubsub_engine_s *engine); - -/** - * Engines can ask facil.io to call the `subscribe` callback for all active - * channels. - * - * This allows engines that lost their connection to their Pub/Sub service to - * resubscribe all the currently active channels with the new connection. - * - * CAUTION: This is an evented task... try not to free the engine's memory while - * resubscriptions are under way... - * - * NOTE: the root (master) process will call `subscribe` for any channel in any - * process, while all the other processes will call `subscribe` only for their - * own channels. This allows engines to use the root (master) process as an - * exclusive subscription process. - */ -void fio_pubsub_reattach(fio_pubsub_engine_s *eng); - -/** Returns true (1) if the engine is attached to the system. */ -int fio_pubsub_is_attached(fio_pubsub_engine_s *engine); - -#endif /* FIO_PUBSUB_SUPPORT */ - -/* ***************************************************************************** - - - - - - - - - - - - Atomic Operations and Spin Locking Helper Functions - - - - - - - - - - - -***************************************************************************** */ - -/* C11 Atomics are defined? */ -#if defined(__ATOMIC_RELAXED) -/** An atomic exchange operation, returns previous value */ -#define fio_atomic_xchange(p_obj, value) \ - __atomic_exchange_n((p_obj), (value), __ATOMIC_SEQ_CST) -/** An atomic addition operation */ -#define fio_atomic_add(p_obj, value) \ - __atomic_add_fetch((p_obj), (value), __ATOMIC_SEQ_CST) -/** An atomic subtraction operation */ -#define fio_atomic_sub(p_obj, value) \ - __atomic_sub_fetch((p_obj), (value), __ATOMIC_SEQ_CST) -/* Note: __ATOMIC_SEQ_CST is probably safer and __ATOMIC_ACQ_REL may be faster - */ - -/* Select the correct compiler builtin method. */ -#elif __has_builtin(__sync_add_and_fetch) -/** An atomic exchange operation, ruturns previous value */ -#define fio_atomic_xchange(p_obj, value) \ - __sync_val_compare_and_swap((p_obj), *(p_obj), (value)) -/** An atomic addition operation */ -#define fio_atomic_add(p_obj, value) __sync_add_and_fetch((p_obj), (value)) -/** An atomic subtraction operation */ -#define fio_atomic_sub(p_obj, value) __sync_sub_and_fetch((p_obj), (value)) - -#elif __GNUC__ > 3 -/** An atomic exchange operation, ruturns previous value */ -#define fio_atomic_xchange(p_obj, value) \ - __sync_val_compare_and_swap((p_obj), *(p_obj), (value)) -/** An atomic addition operation */ -#define fio_atomic_add(p_obj, value) __sync_add_and_fetch((p_obj), (value)) -/** An atomic subtraction operation */ -#define fio_atomic_sub(p_obj, value) __sync_sub_and_fetch((p_obj), (value)) - -#else -#error Required builtin "__sync_add_and_fetch" not found. -#endif - -/** An atomic based spinlock. */ -typedef uint8_t volatile fio_lock_i; - -/** The initail value of an unlocked spinlock. */ -#define FIO_LOCK_INIT 0 - -/** returns 0 if the lock was acquired and a non-zero value on failure. */ -FIO_FUNC inline int fio_trylock(fio_lock_i *lock); - -/** - * Releases a spinlock. Releasing an unacquired lock will break it. - * - * Returns a non-zero value on success, or 0 if the lock was in an unlocked - * state. - */ -FIO_FUNC inline int fio_unlock(fio_lock_i *lock); - -/** Returns a spinlock's state (non 0 == Busy). */ -FIO_FUNC inline int fio_is_locked(fio_lock_i *lock); - -/** Busy waits for the spinlock (CAREFUL). */ -FIO_FUNC inline void fio_lock(fio_lock_i *lock); - -/** - * Nanosleep seems to be the most effective and efficient thread rescheduler. - */ -FIO_FUNC inline void fio_reschedule_thread(void); - -/** Nanosleep the thread - a blocking throttle. */ -FIO_FUNC inline void fio_throttle_thread(size_t nano_sec); - -/* ***************************************************************************** - - - - - - - - - - - Simple Constant Time Operations - ( boolean true / false and if ) - - - - - - - - - - - -***************************************************************************** */ - -/** Returns 1 if the expression is true (input isn't zero). */ -FIO_FUNC inline uintptr_t fio_ct_true(uintptr_t cond) { - // promise that the highest bit is set if any bits are set, than shift. - return ((cond | (0 - cond)) >> ((sizeof(cond) << 3) - 1)); -} - -/** Returns 1 if the expression is false (input is zero). */ -FIO_FUNC inline uintptr_t fio_ct_false(uintptr_t cond) { - // fio_ct_true returns only one bit, XOR will inverse that bit. - return fio_ct_true(cond) ^ 1; -} - -/** Returns `a` if `cond` is boolean and true, returns b otherwise. */ -FIO_FUNC inline uintptr_t fio_ct_if(uint8_t cond, uintptr_t a, uintptr_t b) { - // b^(a^b) cancels b out. 0-1 => sets all bits. - return (b ^ ((0 - (cond & 1)) & (a ^ b))); -} - -/** Returns `a` if `cond` isn't zero (uses fio_ct_true), returns b otherwise. */ -FIO_FUNC inline uintptr_t fio_ct_if2(uintptr_t cond, uintptr_t a, uintptr_t b) { - // b^(a^b) cancels b out. 0-1 => sets all bits. - return fio_ct_if(fio_ct_true(cond), a, b); -} - -/* ***************************************************************************** - - - - - - - - - - - Byte Swapping and Network Order - (Big Endian v.s Little Endian etc') - - - - - - - - - - - -***************************************************************************** */ - -/** inplace byte swap 16 bit integer */ -#if __has_builtin(__builtin_bswap16) -#define fio_bswap16(i) __builtin_bswap16((uint16_t)(i)) -#else -#define fio_bswap16(i) ((((i)&0xFFU) << 8) | (((i)&0xFF00U) >> 8)) -#endif -/** inplace byte swap 32 bit integer */ -#if __has_builtin(__builtin_bswap32) -#define fio_bswap32(i) __builtin_bswap32((uint32_t)(i)) -#else -#define fio_bswap32(i) \ - ((((i)&0xFFUL) << 24) | (((i)&0xFF00UL) << 8) | (((i)&0xFF0000UL) >> 8) | \ - (((i)&0xFF000000UL) >> 24)) -#endif -/** inplace byte swap 64 bit integer */ -#if __has_builtin(__builtin_bswap64) -#define fio_bswap64(i) __builtin_bswap64((uint64_t)(i)) -#else -#define fio_bswap64(i) \ - ((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \ - (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \ - (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \ - (((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56)) -#endif - -/* Note: using BIG_ENDIAN invokes false positives on some systems */ -#if !defined(__BIG_ENDIAN__) -/* nothing to do */ -#elif (defined(__LITTLE_ENDIAN__) && !__LITTLE_ENDIAN__) || \ - (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) -#define __BIG_ENDIAN__ 1 -#elif !defined(__BIG_ENDIAN__) && !defined(__BYTE_ORDER__) && \ - !defined(__LITTLE_ENDIAN__) -#error Could not detect byte order on this system. -#endif - -#if __BIG_ENDIAN__ - -/** Local byte order to Network byte order, 16 bit integer */ -#define fio_lton16(i) (i) -/** Local byte order to Network byte order, 32 bit integer */ -#define fio_lton32(i) (i) -/** Local byte order to Network byte order, 62 bit integer */ -#define fio_lton64(i) (i) - -/** Network byte order to Local byte order, 16 bit integer */ -#define fio_ntol16(i) (i) -/** Network byte order to Local byte order, 32 bit integer */ -#define fio_ntol32(i) (i) -/** Network byte order to Local byte order, 62 bit integer */ -#define fio_ntol64(i) (i) - -#else /* Little Endian */ - -/** Local byte order to Network byte order, 16 bit integer */ -#define fio_lton16(i) fio_bswap16((i)) -/** Local byte order to Network byte order, 32 bit integer */ -#define fio_lton32(i) fio_bswap32((i)) -/** Local byte order to Network byte order, 62 bit integer */ -#define fio_lton64(i) fio_bswap64((i)) - -/** Network byte order to Local byte order, 16 bit integer */ -#define fio_ntol16(i) fio_bswap16((i)) -/** Network byte order to Local byte order, 32 bit integer */ -#define fio_ntol32(i) fio_bswap32((i)) -/** Network byte order to Local byte order, 62 bit integer */ -#define fio_ntol64(i) fio_bswap64((i)) - -#endif - -/** 32Bit left rotation, inlined. */ -#define fio_lrot32(i, bits) \ - (((uint32_t)(i) << ((bits)&31UL)) | ((uint32_t)(i) >> ((-(bits)) & 31UL))) -/** 32Bit right rotation, inlined. */ -#define fio_rrot32(i, bits) \ - (((uint32_t)(i) >> ((bits)&31UL)) | ((uint32_t)(i) << ((-(bits)) & 31UL))) - -/** 64Bit left rotation, inlined. */ -#define fio_lrot64(i, bits) \ - (((uint64_t)(i) << ((bits)&63UL)) | ((uint64_t)(i) >> ((-(bits)) & 63UL))) -/** 64Bit right rotation, inlined. */ -#define fio_rrot64(i, bits) \ - (((uint64_t)(i) >> ((bits)&63UL)) | ((uint64_t)(i) << ((-(bits)) & 63UL))) - -/** unknown size element - left rotation, inlined. */ -#define fio_lrot(i, bits) \ - (((i) << ((bits) & ((sizeof((i)) << 3) - 1))) | \ - ((i) >> ((-(bits)) & ((sizeof((i)) << 3) - 1)))) -/** unknown size element - right rotation, inlined. */ -#define fio_rrot(i, bits) \ - (((i) >> ((bits) & ((sizeof((i)) << 3) - 1))) | \ - ((i) << ((-(bits)) & ((sizeof((i)) << 3) - 1)))) - -/** Converts an unaligned network ordered byte stream to a 16 bit number. */ -#define fio_str2u16(c) \ - ((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) | \ - (uint16_t)(((uint8_t *)(c))[1]))) -/** Converts an unaligned network ordered byte stream to a 32 bit number. */ -#define fio_str2u32(c) \ - ((uint32_t)(((uint32_t)(((uint8_t *)(c))[0]) << 24) | \ - ((uint32_t)(((uint8_t *)(c))[1]) << 16) | \ - ((uint32_t)(((uint8_t *)(c))[2]) << 8) | \ - (uint32_t)(((uint8_t *)(c))[3]))) - -/** Converts an unaligned network ordered byte stream to a 64 bit number. */ -#define fio_str2u64(c) \ - ((uint64_t)((((uint64_t)((uint8_t *)(c))[0]) << 56) | \ - (((uint64_t)((uint8_t *)(c))[1]) << 48) | \ - (((uint64_t)((uint8_t *)(c))[2]) << 40) | \ - (((uint64_t)((uint8_t *)(c))[3]) << 32) | \ - (((uint64_t)((uint8_t *)(c))[4]) << 24) | \ - (((uint64_t)((uint8_t *)(c))[5]) << 16) | \ - (((uint64_t)((uint8_t *)(c))[6]) << 8) | (((uint8_t *)(c))[7]))) - -/** Writes a local 16 bit number to an unaligned buffer in network order. */ -#define fio_u2str16(buffer, i) \ - do { \ - ((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF; \ - ((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF; \ - } while (0); - -/** Writes a local 32 bit number to an unaligned buffer in network order. */ -#define fio_u2str32(buffer, i) \ - do { \ - ((uint8_t *)(buffer))[0] = ((uint32_t)(i) >> 24) & 0xFF; \ - ((uint8_t *)(buffer))[1] = ((uint32_t)(i) >> 16) & 0xFF; \ - ((uint8_t *)(buffer))[2] = ((uint32_t)(i) >> 8) & 0xFF; \ - ((uint8_t *)(buffer))[3] = ((uint32_t)(i)) & 0xFF; \ - } while (0); - -/** Writes a local 64 bit number to an unaligned buffer in network order. */ -#define fio_u2str64(buffer, i) \ - do { \ - ((uint8_t *)(buffer))[0] = (((uint64_t)(i) >> 56) & 0xFF); \ - ((uint8_t *)(buffer))[1] = (((uint64_t)(i) >> 48) & 0xFF); \ - ((uint8_t *)(buffer))[2] = (((uint64_t)(i) >> 40) & 0xFF); \ - ((uint8_t *)(buffer))[3] = (((uint64_t)(i) >> 32) & 0xFF); \ - ((uint8_t *)(buffer))[4] = (((uint64_t)(i) >> 24) & 0xFF); \ - ((uint8_t *)(buffer))[5] = (((uint64_t)(i) >> 16) & 0xFF); \ - ((uint8_t *)(buffer))[6] = (((uint64_t)(i) >> 8) & 0xFF); \ - ((uint8_t *)(buffer))[7] = (((uint64_t)(i)) & 0xFF); \ - } while (0); - -/* ***************************************************************************** - - - - - - - - - - - Converting Numbers to Strings (and back) - - - - - - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Strings to Numbers -***************************************************************************** */ - -/** - * A helper function that converts between String data to a signed int64_t. - * - * Numbers are assumed to be in base 10. Octal (`0###`), Hex (`0x##`/`x##`) and - * binary (`0b##`/ `b##`) are recognized as well. For binary Most Significant - * Bit must come first. - * - * The most significant difference between this function and `strtol` (aside of - * API design), is the added support for binary representations. - */ -int64_t fio_atol(char **pstr); - -/** A helper function that converts between String data to a signed double. */ -double fio_atof(char **pstr); - -/* ***************************************************************************** -Numbers to Strings -***************************************************************************** */ - -/** - * A helper function that writes a signed int64_t to a string. - * - * No overflow guard is provided, make sure there's at least 68 bytes - * available (for base 2). - * - * Offers special support for base 2 (binary), base 8 (octal), base 10 and base - * 16 (hex). An unsupported base will silently default to base 10. Prefixes - * aren't added (i.e., no "0x" or "0b" at the beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -size_t fio_ltoa(char *dest, int64_t num, uint8_t base); - -/** - * A helper function that converts between a double to a string. - * - * No overflow guard is provided, make sure there's at least 130 bytes - * available (for base 2). - * - * Supports base 2, base 10 and base 16. An unsupported base will silently - * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the - * beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -size_t fio_ftoa(char *dest, double num, uint8_t base); - -/* ***************************************************************************** - - - - - - - - Random Generator Functions - - Probably not cryptographically safe - - - - - - - -***************************************************************************** */ - -/** Returns 64 psedo-random bits. Probably not cryptographically safe. */ -uint64_t fio_rand64(void); - -/** Writes `length` bytes of psedo-random bits to the target buffer. */ -void fio_rand_bytes(void *target, size_t length); - -/* ***************************************************************************** - - - - - - - - Hash Functions and Friends - - - - - - - -***************************************************************************** */ - -/* defines the secret seed to be used by keyd hashing functions*/ -#ifndef FIO_HASH_SECRET_SEED64_1 -uint8_t __attribute__((weak)) fio_hash_secret_marker1; -uint8_t __attribute__((weak)) fio_hash_secret_marker2; -#define FIO_HASH_SECRET_SEED64_1 ((uintptr_t)&fio_hash_secret_marker1) -#define FIO_HASH_SECRET_SEED64_2 ((uintptr_t)&fio_hash_secret_marker2) -#endif - -#if FIO_USE_RISKY_HASH -#define FIO_HASH_FN(data, length, key1, key2) \ - fio_risky_hash((data), (length), \ - ((uint64_t)(key1) >> 19) | ((uint64_t)(key2) << 27)) -#else -#define FIO_HASH_FN(data, length, key1, key2) \ - fio_siphash13((data), (length), (uint64_t)(key1), (uint64_t)(key2)) -#endif - -/* ***************************************************************************** -Risky Hash (always available, even if using only the fio.h header) -***************************************************************************** */ - -/* Risky Hash primes */ -#define RISKY_PRIME_0 0xFBBA3FA15B22113B -#define RISKY_PRIME_1 0xAB137439982B86C9 - -/* Risky Hash consumption round, accepts a state word s and an input word w */ -#define fio_risky_consume(v, w) \ - (v) += (w); \ - (v) = fio_lrot64((v), 33); \ - (v) += (w); \ - (v) *= RISKY_PRIME_0; - -/* Computes a facil.io Risky Hash. */ -FIO_FUNC inline uint64_t fio_risky_hash(const void *data_, size_t len, - uint64_t seed) { - /* reading position */ - const uint8_t *data = (uint8_t *)data_; - - /* The consumption vectors initialized state */ - register uint64_t v0 = seed ^ RISKY_PRIME_1; - register uint64_t v1 = ~seed + RISKY_PRIME_1; - register uint64_t v2 = - fio_lrot64(seed, 17) ^ ((~RISKY_PRIME_1) + RISKY_PRIME_0); - register uint64_t v3 = fio_lrot64(seed, 33) + (~RISKY_PRIME_1); - - /* consume 256 bit blocks */ - for (size_t i = len >> 5; i; --i) { - fio_risky_consume(v0, fio_str2u64(data)); - fio_risky_consume(v1, fio_str2u64(data + 8)); - fio_risky_consume(v2, fio_str2u64(data + 16)); - fio_risky_consume(v3, fio_str2u64(data + 24)); - data += 32; - } - - /* Consume any remaining 64 bit words. */ - switch (len & 24) { - case 24: - fio_risky_consume(v2, fio_str2u64(data + 16)); - /* fallthrough */ - case 16: - fio_risky_consume(v1, fio_str2u64(data + 8)); - /* fallthrough */ - case 8: - fio_risky_consume(v0, fio_str2u64(data)); - data += len & 24; - } - - uint64_t tmp = 0; - /* consume leftover bytes, if any */ - switch ((len & 7)) { - case 7: - tmp |= ((uint64_t)data[6]) << 8; - /* fallthrough */ - case 6: - tmp |= ((uint64_t)data[5]) << 16; - /* fallthrough */ - case 5: - tmp |= ((uint64_t)data[4]) << 24; - /* fallthrough */ - case 4: - tmp |= ((uint64_t)data[3]) << 32; - /* fallthrough */ - case 3: - tmp |= ((uint64_t)data[2]) << 40; - /* fallthrough */ - case 2: - tmp |= ((uint64_t)data[1]) << 48; - /* fallthrough */ - case 1: - tmp |= ((uint64_t)data[0]) << 56; - /* ((len >> 3) & 3) is a 0...3 value indicating consumption vector */ - switch ((len >> 3) & 3) { - case 3: - fio_risky_consume(v3, tmp); - break; - case 2: - fio_risky_consume(v2, tmp); - break; - case 1: - fio_risky_consume(v1, tmp); - break; - case 0: - fio_risky_consume(v0, tmp); - break; - } - } - - /* merge and mix */ - uint64_t result = fio_lrot64(v0, 17) + fio_lrot64(v1, 13) + - fio_lrot64(v2, 47) + fio_lrot64(v3, 57); - - uint64_t len64 = len; - len64 ^= (len64 << 33); - result += len64; - - result += v0 * RISKY_PRIME_1; - result ^= fio_lrot64(result, 13); - result += v1 * RISKY_PRIME_1; - result ^= fio_lrot64(result, 29); - result += v2 * RISKY_PRIME_1; - result ^= fio_lrot64(result, 33); - result += v3 * RISKY_PRIME_1; - result ^= fio_lrot64(result, 51); - - /* irreversible avalanche... I think */ - result ^= (result >> 29) * RISKY_PRIME_0; - return result; -} - -#undef fio_risky_consume -#undef FIO_RISKY_PRIME_0 -#undef FIO_RISKY_PRIME_1 - -/* ***************************************************************************** -SipHash -***************************************************************************** */ - -/** - * A SipHash variation (2-4). - */ -uint64_t fio_siphash24(const void *data, size_t len, uint64_t key1, - uint64_t key2); - -/** - * A SipHash 1-3 variation. - */ -uint64_t fio_siphash13(const void *data, size_t len, uint64_t key1, - uint64_t key2); - -/** - * The Hashing function used by dynamic facil.io objects. - * - * Currently implemented using SipHash 1-3. - */ -#define fio_siphash(data, length, k1, k2) \ - fio_siphash13((data), (length), (k1), (k2)) - -/* ***************************************************************************** -SHA-1 -***************************************************************************** */ - -/** -SHA-1 hashing container - you should ignore the contents of this struct. - -The `sha1_s` type will contain all the sha1 data required to perform the -hashing, managing it's encoding. If it's stack allocated, no freeing will be -required. - -Use, for example: - - fio_sha1_s sha1; - fio_sha1_init(&sha1); - fio_sha1_write(&sha1, - "The quick brown fox jumps over the lazy dog", 43); - char *hashed_result = fio_sha1_result(&sha1); -*/ -typedef struct { - uint64_t length; - uint8_t buffer[64]; - union { - uint32_t i[5]; - unsigned char str[21]; - } digest; -} fio_sha1_s; - -/** -Initialize or reset the `sha1` object. This must be performed before hashing -data using sha1. -*/ -fio_sha1_s fio_sha1_init(void); -/** -Writes data to the sha1 buffer. -*/ -void fio_sha1_write(fio_sha1_s *s, const void *data, size_t len); -/** -Finalizes the SHA1 hash, returning the Hashed data. - -`fio_sha1_result` can be called for the same object multiple times, but the -finalization will only be performed the first time this function is called. -*/ -char *fio_sha1_result(fio_sha1_s *s); - -/** -An SHA1 helper function that performs initialiation, writing and finalizing. -*/ -inline FIO_FUNC char *fio_sha1(fio_sha1_s *s, const void *data, size_t len) { - *s = fio_sha1_init(); - fio_sha1_write(s, data, len); - return fio_sha1_result(s); -} - -/* ***************************************************************************** -SHA-2 -***************************************************************************** */ - -/** -SHA-2 function variants. - -This enum states the different SHA-2 function variants. placing SHA_512 at the -beginning is meant to set this variant as the default (in case a 0 is passed). -*/ -typedef enum { - SHA_512 = 1, - SHA_512_256 = 3, - SHA_512_224 = 5, - SHA_384 = 7, - SHA_256 = 2, - SHA_224 = 4, -} fio_sha2_variant_e; - -/** -SHA-2 hashing container - you should ignore the contents of this struct. - -The `sha2_s` type will contain all the SHA-2 data required to perform the -hashing, managing it's encoding. If it's stack allocated, no freeing will be -required. - -Use, for example: - - fio_sha2_s sha2; - fio_sha2_init(&sha2, SHA_512); - fio_sha2_write(&sha2, - "The quick brown fox jumps over the lazy dog", 43); - char *hashed_result = fio_sha2_result(&sha2); - -*/ -typedef struct { - /* notice: we're counting bits, not bytes. max length: 2^128 bits */ - union { - uint8_t bytes[16]; - uint8_t matrix[4][4]; - uint32_t words_small[4]; - uint64_t words[2]; -#if defined(__SIZEOF_INT128__) - __uint128_t i; -#endif - } length; - uint8_t buffer[128]; - union { - uint32_t i32[16]; - uint64_t i64[8]; - uint8_t str[65]; /* added 64+1 for the NULL byte.*/ - } digest; - fio_sha2_variant_e type; -} fio_sha2_s; - -/** -Initialize/reset the SHA-2 object. - -SHA-2 is actually a family of functions with different variants. When -initializing the SHA-2 container, you must select the variant you intend to -apply. The following are valid options (see the sha2_variant enum): - -- SHA_512 (== 0) -- SHA_384 -- SHA_512_224 -- SHA_512_256 -- SHA_256 -- SHA_224 - -*/ -fio_sha2_s fio_sha2_init(fio_sha2_variant_e variant); -/** -Writes data to the SHA-2 buffer. -*/ -void fio_sha2_write(fio_sha2_s *s, const void *data, size_t len); -/** -Finalizes the SHA-2 hash, returning the Hashed data. - -`sha2_result` can be called for the same object multiple times, but the -finalization will only be performed the first time this function is called. -*/ -char *fio_sha2_result(fio_sha2_s *s); - -/** -An SHA2 helper function that performs initialiation, writing and finalizing. -Uses the SHA2 512 variant. -*/ -inline FIO_FUNC char *fio_sha2_512(fio_sha2_s *s, const void *data, - size_t len) { - *s = fio_sha2_init(SHA_512); - fio_sha2_write(s, data, len); - return fio_sha2_result(s); -} - -/** -An SHA2 helper function that performs initialiation, writing and finalizing. -Uses the SHA2 256 variant. -*/ -inline FIO_FUNC char *fio_sha2_256(fio_sha2_s *s, const void *data, - size_t len) { - *s = fio_sha2_init(SHA_256); - fio_sha2_write(s, data, len); - return fio_sha2_result(s); -} - -/** -An SHA2 helper function that performs initialiation, writing and finalizing. -Uses the SHA2 384 variant. -*/ -inline FIO_FUNC char *fio_sha2_384(fio_sha2_s *s, const void *data, - size_t len) { - *s = fio_sha2_init(SHA_384); - fio_sha2_write(s, data, len); - return fio_sha2_result(s); -} - -/* ***************************************************************************** -Base64 (URL) encoding -***************************************************************************** */ - -/** -This will encode a byte array (data) of a specified length (len) and -place the encoded data into the target byte buffer (target). The target buffer -MUST have enough room for the expected data. - -Base64 encoding always requires 4 bytes for each 3 bytes. Padding is added if -the raw data's length isn't devisable by 3. - -Always assume the target buffer should have room enough for (len*4/3 + 4) -bytes. - -Returns the number of bytes actually written to the target buffer -(including the Base64 required padding and excluding a NULL terminator). - -A NULL terminator char is NOT written to the target buffer. -*/ -int fio_base64_encode(char *target, const char *data, int len); - -/** -Same as fio_base64_encode, but using Base64URL encoding. -*/ -int fio_base64url_encode(char *target, const char *data, int len); - -/** -This will decode a Base64 encoded string of a specified length (len) and -place the decoded data into the target byte buffer (target). - -The target buffer MUST have enough room for 2 bytes in addition to the expected -data (NUL byte + padding test). - -A NUL byte will be appended to the target buffer. The function will return -the number of bytes written to the target buffer (excluding the NUL byte). - -If the target buffer is NUL, the encoded string will be destructively edited -and the decoded data will be placed in the original string's buffer. - -Base64 encoding always requires 4 bytes for each 3 bytes. Padding is added if -the raw data's length isn't devisable by 3. Hence, the target buffer should -be, at least, `base64_len/4*3 + 3` long. - -Returns the number of bytes actually written to the target buffer (excluding -the NUL terminator byte). - -Note: -==== - -The decoder is variation agnostic (will decode Base64, Base64 URL and Base64 XML -variations) and will attempt it's best to ignore invalid data, (in order to -support the MIME Base64 variation in RFC 2045). - -This comes at the cost of error -checking, so the encoding isn't validated and invalid input might produce -surprising results. -*/ -int fio_base64_decode(char *target, char *encoded, int base64_len); - -/* ***************************************************************************** -Testing -***************************************************************************** */ - -#if DEBUG -void fio_test(void); -#else -#define fio_test() -#endif - -/* ***************************************************************************** -C++ extern end -***************************************************************************** */ -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* ***************************************************************************** - - - - - - - - - Memory Allocator Details - - - - - - - - -***************************************************************************** */ - -/** - * This is a custom memory allocator the utilizes memory pools to allow for - * concurrent memory allocations across threads. - * - * Allocated memory is always zeroed out and aligned on a 16 byte boundary. - * - * Reallocated memory is always aligned on a 16 byte boundary but it might be - * filled with junk data after the valid data (this is true also for - * `fio_realloc2`). - * - * The memory allocator assumes multiple concurrent allocation/deallocation, - * short life spans (memory is freed shortly, but not immediately, after it was - * allocated) as well as small allocations (realloc almost always copies data). - * - * These assumptions allow the allocator to avoid lock contention by ignoring - * fragmentation within a memory "block" and waiting for the whole "block" to be - * freed before it's memory is recycled (no per-allocation "free list"). - * - * An "arena" is allocated per-CPU core during initialization - there's no - * dynamic allocation of arenas. This allows threads to minimize lock contention - * by cycling through the arenas until a free arena is detected. - * - * There should be a free arena at any given time (statistically speaking) and - * the thread will only be deferred in the unlikely event in which there's no - * available arena. - * - * By avoiding the "free-list", the need for allocation "headers" is also - * avoided and allocations are performed with practically zero overhead (about - * 32 bytes overhead per 32KB memory, that's 1 bit per 1Kb). - * - * However, the lack of a "free list" means that memory "leaks" are more - * expensive and small long-life allocations could cause fragmentation if - * performed periodically (rather than performed during startup). - * - * This allocator should NOT be used for objects with a long life-span, because - * even a single persistent object will prevent the re-use of the whole memory - * block from which it was allocated (see FIO_MEMORY_BLOCK_SIZE for size). - * - * Some more details: - * - * Allocation and deallocations and (usually) managed by "blocks". - * - * A memory "block" can include any number of memory pages that are a multiple - * of 2 (up to 1Mb of memory). However, the default value, set by the value of - * FIO_MEMORY_BLOCK_SIZE_LOG, is 32Kb (see value at the end of this header). - * - * Each block includes a 32 byte header that uses reference counters and - * position markers (24 bytes are required padding). - * - * The block's position marker (`pos`) marks the next available byte (counted in - * multiples of 16 bytes). - * - * The block's reference counter (`ref`) counts how many allocations reference - * memory in the block (including the "arena" that "owns" the block). - * - * Except for the position marker (`pos`) that acts the same as `sbrk`, there's - * no way to know which "slices" are allocated and which "slices" are available. - * - * The allocator uses `mmap` when requesting memory from the system and for - * allocations bigger than MEMORY_BLOCK_ALLOC_LIMIT (37.5% of the block). - * - * Small allocations are differentiated from big allocations by their memory - * alignment. - * - * If a memory allocation is placed 16 bytes after whole block alignment (within - * a block's padding zone), the memory was allocated directly using `mmap` as a - * "big allocation". The 16 bytes include an 8 byte header and an 8 byte - * padding. - * - * To replace the system's `malloc` function family compile with the - * `FIO_OVERRIDE_MALLOC` defined (`-DFIO_OVERRIDE_MALLOC`). - * - * When using tcmalloc or jemalloc, it's possible to define `FIO_FORCE_MALLOC` - * to prevent the facil.io allocator from compiling (`-DFIO_FORCE_MALLOC`). - */ - -#ifndef FIO_MEMORY_BLOCK_SIZE_LOG -/** - * The logarithmic value for a memory block, 15 == 32Kb, 16 == 64Kb, etc' - * - * By default, a block of memory is a 32Kb slice from an 8Mb allocation. - * - * A value of 16 will make this a 64Kb slice from a 16Mb allocation. - */ -#define FIO_MEMORY_BLOCK_SIZE_LOG (15) -#endif - -#undef FIO_MEMORY_BLOCK_SIZE -/** The resulting memoru block size, depends on `FIO_MEMORY_BLOCK_SIZE_LOG` */ -#define FIO_MEMORY_BLOCK_SIZE ((uintptr_t)1 << FIO_MEMORY_BLOCK_SIZE_LOG) - -/** - * The maximum allocation size, after which `mmap` will be called instead of the - * facil.io allocator. - * - * Defaults to 50% of the block (16Kb), after which `mmap` is used instead - */ -#ifndef FIO_MEMORY_BLOCK_ALLOC_LIMIT -#define FIO_MEMORY_BLOCK_ALLOC_LIMIT (FIO_MEMORY_BLOCK_SIZE >> 1) -#endif - -/* ***************************************************************************** - - - - - - - - - - Spin locking Implementation - - - - - - - - - -***************************************************************************** */ - -/** - * Nanosleep seems to be the most effective and efficient thread rescheduler. - */ -FIO_FUNC inline void fio_reschedule_thread(void) { - const struct timespec tm = {.tv_nsec = 1}; - nanosleep(&tm, NULL); -} - -/** Nanosleep the thread - a blocking throttle. */ -FIO_FUNC inline void fio_throttle_thread(size_t nano_sec) { - const struct timespec tm = {.tv_nsec = (long)(nano_sec % 1000000000), - .tv_sec = (time_t)(nano_sec / 1000000000)}; - nanosleep(&tm, NULL); -} - -/** returns 0 if the lock was acquired and another value on failure. */ -FIO_FUNC inline int fio_trylock(fio_lock_i *lock) { - __asm__ volatile("" ::: "memory"); - fio_lock_i ret = fio_atomic_xchange(lock, 1); - __asm__ volatile("" ::: "memory"); - return ret; -} - -/** - * Releases a spinlock. Releasing an unacquired lock will break it. - * - * Returns a non-zero value on success, or 0 if the lock was in an unlocked - * state. - */ -FIO_FUNC inline int fio_unlock(fio_lock_i *lock) { - __asm__ volatile("" ::: "memory"); - fio_lock_i ret = fio_atomic_xchange(lock, 0); - return ret; -} - -/** Returns a spinlock's state (non 0 == Busy). */ -FIO_FUNC inline int fio_is_locked(fio_lock_i *lock) { - __asm__ volatile("" ::: "memory"); - return *lock; -} - -/** Busy waits for the spinlock (CAREFUL). */ -FIO_FUNC inline void fio_lock(fio_lock_i *lock) { - while (fio_trylock(lock)) { - fio_reschedule_thread(); - } -} - -#if DEBUG_SPINLOCK -/** Busy waits for a lock, reports contention. */ -FIO_FUNC inline void fio_lock_dbg(fio_lock_i *lock, const char *file, - int line) { - size_t lock_cycle_count = 0; - while (fio_trylock(lock)) { - if (lock_cycle_count >= 8 && - (lock_cycle_count == 8 || !(lock_cycle_count & 511))) - fprintf(stderr, "[DEBUG] fio-spinlock spin %s:%d round %zu\n", file, line, - lock_cycle_count); - ++lock_cycle_count; - fio_reschedule_thread(); - } - if (lock_cycle_count >= 8) - fprintf(stderr, "[DEBUG] fio-spinlock spin %s:%d total = %zu\n", file, line, - lock_cycle_count); -} -#define fio_lock(lock) fio_lock_dbg((lock), __FILE__, __LINE__) - -FIO_FUNC inline int fio_trylock_dbg(fio_lock_i *lock, const char *file, - int line) { - static int last_line = 0; - static size_t count = 0; - int result = fio_trylock(lock); - if (!result) { - count = 0; - last_line = 0; - } else if (line == last_line) { - ++count; - if (count >= 2) - fprintf(stderr, "[DEBUG] trying fio-spinlock %s:%d attempt %zu\n", file, - line, count); - } else { - count = 0; - last_line = line; - } - return result; -} -#define fio_trylock(lock) fio_trylock_dbg((lock), __FILE__, __LINE__) -#endif /* DEBUG_SPINLOCK */ - -#endif /* H_FACIL_IO_H */ - -/* ***************************************************************************** - - - - - - - Memory allocation macros for helper types - - - - - - -***************************************************************************** */ - -#undef FIO_MALLOC -#undef FIO_CALLOC -#undef FIO_REALLOC -#undef FIO_FREE - -#if FIO_FORCE_MALLOC || FIO_FORCE_MALLOC_TMP -#define FIO_MALLOC(size) calloc((size), 1) -#define FIO_CALLOC(size, units) calloc((size), (units)) -#define FIO_REALLOC(ptr, new_length, existing_data_length) \ - realloc((ptr), (new_length)) -#define FIO_FREE free - -#else -#define FIO_MALLOC(size) fio_malloc((size)) -#define FIO_CALLOC(size, units) fio_calloc((size), (units)) -#define FIO_REALLOC(ptr, new_length, existing_data_length) \ - fio_realloc2((ptr), (new_length), (existing_data_length)) -#define FIO_FREE fio_free -#endif /* FIO_FORCE_MALLOC || FIO_FORCE_MALLOC_TMP */ - -/* ***************************************************************************** - - - - - - - Linked List Helpers - - exposes internally used inline helpers for linked lists - - - - - - -***************************************************************************** */ - -#if !defined(H_FIO_LINKED_LIST_H) && defined(FIO_INCLUDE_LINKED_LIST) - -#define H_FIO_LINKED_LIST_H -#undef FIO_INCLUDE_LINKED_LIST -/* ***************************************************************************** -Data Structure and Initialization. -***************************************************************************** */ - -/** an embeded linked list. */ -typedef struct fio_ls_embd_s { - struct fio_ls_embd_s *prev; - struct fio_ls_embd_s *next; -} fio_ls_embd_s; - -/** an independent linked list. */ -typedef struct fio_ls_s { - struct fio_ls_s *prev; - struct fio_ls_s *next; - const void *obj; -} fio_ls_s; - -#define FIO_LS_INIT(name) \ - { .next = &(name), .prev = &(name) } - -/* ***************************************************************************** -Embedded Linked List API -***************************************************************************** */ - -/** Adds a node to the list's head. */ -FIO_FUNC inline void fio_ls_embd_push(fio_ls_embd_s *dest, fio_ls_embd_s *node); - -/** Adds a node to the list's tail. */ -FIO_FUNC inline void fio_ls_embd_unshift(fio_ls_embd_s *dest, - fio_ls_embd_s *node); - -/** Removes a node from the list's head. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_pop(fio_ls_embd_s *list); - -/** Removes a node from the list's tail. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_shift(fio_ls_embd_s *list); - -/** Removes a node from the containing node. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_remove(fio_ls_embd_s *node); - -/** Tests if the list is empty. */ -FIO_FUNC inline int fio_ls_embd_is_empty(fio_ls_embd_s *list); - -/** Tests if the list is NOT empty (contains any nodes). */ -FIO_FUNC inline int fio_ls_embd_any(fio_ls_embd_s *list); - -/** - * Iterates through the list using a `for` loop. - * - * Access the data with `pos->obj` (`pos` can be named however you please). - */ -#define FIO_LS_EMBD_FOR(list, node) - -/** - * Takes a list pointer `plist` and returns a pointer to it's container. - * - * This uses pointer offset calculations and can be used to calculate any - * struct's pointer (not just list containers) as an offset from a pointer of - * one of it's members. - * - * Very useful. - */ -#define FIO_LS_EMBD_OBJ(type, member, plist) \ - ((type *)((uintptr_t)(plist) - (uintptr_t)(&(((type *)0)->member)))) - -/* ***************************************************************************** -Independent Linked List API -***************************************************************************** */ - -/** Adds an object to the list's head, returnin's the object's location. */ -FIO_FUNC inline fio_ls_s *fio_ls_push(fio_ls_s *pos, const void *obj); - -/** Adds an object to the list's tail, returnin's the object's location. */ -FIO_FUNC inline fio_ls_s *fio_ls_unshift(fio_ls_s *pos, const void *obj); - -/** Removes an object from the list's head. */ -FIO_FUNC inline void *fio_ls_pop(fio_ls_s *list); - -/** Removes an object from the list's tail. */ -FIO_FUNC inline void *fio_ls_shift(fio_ls_s *list); - -/** Removes a node from the list, returning the contained object. */ -FIO_FUNC inline void *fio_ls_remove(fio_ls_s *node); - -/** Tests if the list is empty. */ -FIO_FUNC inline int fio_ls_is_empty(fio_ls_s *list); - -/** Tests if the list is NOT empty (contains any nodes). */ -FIO_FUNC inline int fio_ls_any(fio_ls_s *list); - -/** - * Iterates through the list using a `for` loop. - * - * Access the data with `pos->obj` (`pos` can be named however you please). - */ -#define FIO_LS_FOR(list, pos) - -/* ***************************************************************************** - - - Linked List Helpers - - IMPLEMENTATION - - -***************************************************************************** */ - -/* ***************************************************************************** -Embeded Linked List Implementation -***************************************************************************** */ - -/** Removes a node from the containing node. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_remove(fio_ls_embd_s *node) { - if (!node->next || node->next == node) { - /* never remove the list's head */ - return NULL; - } - node->next->prev = node->prev; - node->prev->next = node->next; - node->prev = node->next = node; - return node; -} - -/** Adds a node to the list's head. */ -FIO_FUNC inline void fio_ls_embd_push(fio_ls_embd_s *dest, - fio_ls_embd_s *node) { - if (!dest || !node) - return; - node->prev = dest->prev; - node->next = dest; - dest->prev->next = node; - dest->prev = node; -} - -/** Adds a node to the list's tail. */ -FIO_FUNC inline void fio_ls_embd_unshift(fio_ls_embd_s *dest, - fio_ls_embd_s *node) { - fio_ls_embd_push(dest->next, node); -} - -/** Removes a node from the list's head. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_pop(fio_ls_embd_s *list) { - return fio_ls_embd_remove(list->prev); -} - -/** Removes a node from the list's tail. */ -FIO_FUNC inline fio_ls_embd_s *fio_ls_embd_shift(fio_ls_embd_s *list) { - return fio_ls_embd_remove(list->next); -} - -/** Tests if the list is empty. */ -FIO_FUNC inline int fio_ls_embd_is_empty(fio_ls_embd_s *list) { - return list->next == list; -} - -/** Tests if the list is NOT empty (contains any nodes). */ -FIO_FUNC inline int fio_ls_embd_any(fio_ls_embd_s *list) { - return list->next != list; -} - -#undef FIO_LS_EMBD_FOR -#define FIO_LS_EMBD_FOR(list, node) \ - for (fio_ls_embd_s *node = (list)->next; node != (list); node = node->next) - -/* ***************************************************************************** -Independent Linked List Implementation -***************************************************************************** */ - -/** Removes an object from the containing node. */ -FIO_FUNC inline void *fio_ls_remove(fio_ls_s *node) { - if (!node || node->next == node) { - /* never remove the list's head */ - return NULL; - } - const void *ret = node->obj; - node->next->prev = node->prev; - node->prev->next = node->next; - FIO_FREE(node); - return (void *)ret; -} - -/** Adds an object to the list's head. */ -FIO_FUNC inline fio_ls_s *fio_ls_push(fio_ls_s *pos, const void *obj) { - if (!pos) - return NULL; - /* prepare item */ - fio_ls_s *item = (fio_ls_s *)FIO_MALLOC(sizeof(*item)); - FIO_ASSERT_ALLOC(item); - *item = (fio_ls_s){.prev = pos->prev, .next = pos, .obj = obj}; - /* inject item */ - pos->prev->next = item; - pos->prev = item; - return item; -} - -/** Adds an object to the list's tail. */ -FIO_FUNC inline fio_ls_s *fio_ls_unshift(fio_ls_s *pos, const void *obj) { - return fio_ls_push(pos->next, obj); -} - -/** Removes an object from the list's head. */ -FIO_FUNC inline void *fio_ls_pop(fio_ls_s *list) { - return fio_ls_remove(list->prev); -} - -/** Removes an object from the list's tail. */ -FIO_FUNC inline void *fio_ls_shift(fio_ls_s *list) { - return fio_ls_remove(list->next); -} - -/** Tests if the list is empty. */ -FIO_FUNC inline int fio_ls_is_empty(fio_ls_s *list) { - return list->next == list; -} - -/** Tests if the list is NOT empty (contains any nodes). */ -FIO_FUNC inline int fio_ls_any(fio_ls_s *list) { return list->next != list; } - -#undef FIO_LS_FOR -#define FIO_LS_FOR(list, pos) \ - for (fio_ls_s *pos = (list)->next; pos != (list); pos = pos->next) - -#endif /* FIO_INCLUDE_LINKED_LIST */ - -/* ***************************************************************************** - - - - - - - - String Helpers - - exposes internally used inline helpers for binary Strings - - - - - - - -***************************************************************************** */ - -#if !defined(H_FIO_STR_H) && defined(FIO_INCLUDE_STR) - -#define H_FIO_STR_H -#undef FIO_INCLUDE_STR - -/* ***************************************************************************** -String API - Initialization and Destruction -***************************************************************************** */ - -/** - * The `fio_str_s` type should be considered opaque. - * - * The type's attributes should be accessed ONLY through the accessor functions: - * `fio_str_info`, `fio_str_len`, `fio_str_data`, `fio_str_capa`, etc'. - * - * Note: when the `small` flag is present, the structure is ignored and used as - * raw memory for a small String (no additional allocation). This changes the - * String's behavior drastically and requires that the accessor functions be - * used. - */ -typedef struct { -#ifndef FIO_STR_NO_REF - volatile uint32_t ref; /* reference counter for fio_str_dup */ -#endif - uint8_t small; /* Flag indicating the String is small and self-contained */ - uint8_t frozen; /* Flag indicating the String is frozen (don't edit) */ -#ifdef FIO_STR_NO_REF - uint8_t reserved[14]; /* Align struct on 16 byte allocator boundary */ -#else - uint8_t reserved[10]; /* Align struct on 16 byte allocator boundary */ -#endif - uint64_t capa; /* Known capacity for longer Strings */ - uint64_t len; /* String length for longer Strings */ - void (*dealloc)(void *); /* Data deallocation function (NULL for static) */ - char *data; /* Data for longer Strings */ -#if UINTPTR_MAX != UINT64_MAX - uint8_t padding[2 * (sizeof(uint64_t) - - sizeof(void *))]; /* 16 byte boundary for 32bit OS */ -#endif -} fio_str_s; - -/** - * This value should be used for initialization. For example: - * - * // on the stack - * fio_str_s str = FIO_STR_INIT; - * - * // or on the heap - * fio_str_s *str = malloc(sizeof(*str); - * *str = FIO_STR_INIT; - * - * Remember to cleanup: - * - * // on the stack - * fio_str_free(&str); - * - * // or on the heap - * fio_str_free(str); - * free(str); - */ -#define FIO_STR_INIT ((fio_str_s){.data = NULL, .small = 1}) - -/** - * This macro allows the container to be initialized with existing data, as long - * as it's memory was allocated using `fio_malloc`. - * - * The `capacity` value should exclude the NUL character (if exists). - */ -#define FIO_STR_INIT_EXISTING(buffer, length, capacity) \ - ((fio_str_s){.data = (buffer), \ - .len = (length), \ - .capa = (capacity), \ - .dealloc = FIO_FREE}) - -/** - * This macro allows the container to be initialized with existing static data, - * that shouldn't be freed. - */ -#define FIO_STR_INIT_STATIC(buffer) \ - ((fio_str_s){ \ - .data = (char *)(buffer), .len = strlen((buffer)), .dealloc = NULL}) - -/** - * This macro allows the container to be initialized with existing static data, - * that shouldn't be freed. - */ -#define FIO_STR_INIT_STATIC2(buffer, length) \ - ((fio_str_s){.data = (char *)(buffer), .len = (length), .dealloc = NULL}) - -/** - * Allocates a new fio_str_s object on the heap and initializes it. - * - * Use `fio_str_free2` to free both the String data and the container. - * - * NOTE: This makes the allocation and reference counting logic more intuitive. - */ -inline FIO_FUNC fio_str_s *fio_str_new2(void); - -/** - * Allocates a new fio_str_s object on the heap, initializes it and copies the - * original (`src`) string into the new string. - * - * Use `fio_str_free2` to free the new string's data and it's container. - */ -inline FIO_FUNC fio_str_s *fio_str_new_copy2(fio_str_s *src); - -/** - * Adds a references to the current String object and returns itself. - * - * If refecrence counting was disabled (FIO_STR_NO_REF was defined), returns a - * copy of the String (free with `fio_str_free2`). - * - * NOTE: Nothing is copied, reference Strings are referencing the same String. - * Editing one reference will effect the other. - * - * The original's String's container should remain in scope (if on the - * stack) or remain allocated (if on the heap) until all the references - * were freed using `fio_str_free` / `fio_str_free2` or discarded. - */ -inline FIO_FUNC fio_str_s *fio_str_dup(fio_str_s *s); - -/** - * Frees the String's resources and reinitializes the container. - * - * Note: if the container isn't allocated on the stack, it should be freed - * separately using `free(s)`. - * - * Returns 0 if the data was freed and -1 if the String is NULL or has un-freed - * references (see fio_str_dup). - */ -inline FIO_FUNC int fio_str_free(fio_str_s *s); - -/** - * Frees the String's resources AS WELL AS the container. - * - * Note: the container is freed using `fio_free`, make sure `fio_malloc` was - * used to allocate it. - */ -FIO_FUNC void fio_str_free2(fio_str_s *s); - -/** - * `fio_str_send_free2` sends the fio_str_s using `fio_write2`, freeing both the - * String and the container once the data was sent - * - * As the naming indicates, the String is assumed to have been allocated using - * `fio_str_new2` or `fio_malloc`. - */ -inline FIO_FUNC ssize_t fio_str_send_free2(const intptr_t uuid, - const fio_str_s *str); - -/** - * Returns a C string with the existing data, clearing the `fio_str_s` object's - * String. - * - * Note: the String data is removed from the container, but the container isn't - * freed. - * - * Returns NULL if there's no String data. - * - * Remember to `fio_free` the returned data and - if required - `fio_str_free2` - * the container. - */ -FIO_FUNC char *fio_str_detach(fio_str_s *s); - -/* ***************************************************************************** -String API - String state (data pointers, length, capacity, etc') -***************************************************************************** */ - -/* - * String state information, defined above as: -typedef struct { - size_t capa; - size_t len; - char *data; -} fio_str_info_s; -*/ - -/** Returns the String's complete state (capacity, length and pointer). */ -inline FIO_FUNC fio_str_info_s fio_str_info(const fio_str_s *s); - -/** Returns the String's length in bytes. */ -inline FIO_FUNC size_t fio_str_len(fio_str_s *s); - -/** Returns a pointer (`char *`) to the String's content. */ -inline FIO_FUNC char *fio_str_data(fio_str_s *s); - -/** Returns a byte pointer (`uint8_t *`) to the String's unsigned content. */ -#define fio_str_bytes(s) ((uint8_t *)fio_str_data((s))) - -/** Returns the String's existing capacity (total used & available memory). */ -inline FIO_FUNC size_t fio_str_capa(fio_str_s *s); - -/** - * Sets the new String size without reallocating any memory (limited by - * existing capacity). - * - * Returns the updated state of the String. - * - * Note: When shrinking, any existing data beyond the new size may be corrupted. - */ -inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size); - -/** - * Clears the string (retaining the existing capacity). - */ -#define fio_str_clear(s) fio_str_resize((s), 0) - -/** - * Returns the string's Risky Hash value. - * - * Note: Hash algorithm might change without notice. - */ -FIO_FUNC uint64_t fio_str_hash(const fio_str_s *s); - -/* ***************************************************************************** -String API - Memory management -***************************************************************************** */ - -/** - * Performs a best attempt at minimizing memory consumption. - * - * Actual effects depend on the underlying memory allocator and it's - * implementation. Not all allocators will free any memory. - */ -FIO_FUNC void fio_str_compact(fio_str_s *s); - -/** - * Requires the String to have at least `needed` capacity. Returns the current - * state of the String. - */ -FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed); - -/* ***************************************************************************** -String API - UTF-8 State -***************************************************************************** */ - -/** Returns 1 if the String is UTF-8 valid and 0 if not. */ -FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s); - -/** Returns the String's length in UTF-8 characters. */ -FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s); - -/** - * Takes a UTF-8 character selection information (UTF-8 position and length) and - * updates the same variables so they reference the raw byte slice information. - * - * If the String isn't UTF-8 valid up to the requested selection, than `pos` - * will be updated to `-1` otherwise values are always positive. - * - * The returned `len` value may be shorter than the original if there wasn't - * enough data left to accomodate the requested length. When a `len` value of - * `0` is returned, this means that `pos` marks the end of the String. - * - * Returns -1 on error and 0 on success. - */ -FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len); - -/** - * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8 - * character into the i32 variable (which must be a signed integer with 32bits - * or more). On error, `i32` will be equal to `-1` and `ptr` will not step - * forwards. - * - * The `end` value is only used for overflow protection. - * - * This helper macro is used internally but left exposed for external use. - */ -#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32) - -/* ***************************************************************************** -String API - Content Manipulation and Review -***************************************************************************** */ - -/** - * Writes data at the end of the String (similar to `fio_str_insert` with the - * argument `pos == -1`). - */ -inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src, - size_t src_len); - -/** - * Writes a number at the end of the String using normal base 10 notation. - */ -inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num); - -/** - * Appens the `src` String to the end of the `dest` String. - * - * If `dest` is empty, the resulting Strings will be equal. - */ -inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest, - fio_str_s const *src); - -/** Alias for fio_str_concat */ -#define fio_str_join(dest, src) fio_str_concat((dest), (src)) - -/** - * Replaces the data in the String - replacing `old_len` bytes starting at - * `start_pos`, with the data at `src` (`src_len` bytes long). - * - * Negative `start_pos` values are calculated backwards, `-1` == end of String. - * - * When `old_len` is zero, the function will insert the data at `start_pos`. - * - * If `src_len == 0` than `src` will be ignored and the data marked for - * replacement will be erased. - */ -FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos, - size_t old_len, const void *src, - size_t src_len); - -/** - * Writes to the String using a vprintf like interface. - * - * Data is written to the end of the String. - */ -FIO_FUNC fio_str_info_s fio_str_vprintf(fio_str_s *s, const char *format, - va_list argv); - -/** - * Writes to the String using a printf like interface. - * - * Data is written to the end of the String. - */ -FIO_FUNC fio_str_info_s fio_str_printf(fio_str_s *s, const char *format, ...); - -/** - * Opens the file `filename` and pastes it's contents (or a slice ot it) at the - * end of the String. If `limit == 0`, than the data will be read until EOF. - * - * If the file can't be located, opened or read, or if `start_at` is beyond - * the EOF position, NULL is returned in the state's `data` field. - * - * Works on POSIX only. - */ -FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s, const char *filename, - intptr_t start_at, intptr_t limit); - -/** - * Prevents further manipulations to the String's content. - */ -inline FIO_FUNC void fio_str_freeze(fio_str_s *s); - -/** - * Binary comparison returns `1` if both strings are equal and `0` if not. - */ -inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2); - -/* ***************************************************************************** - - - String Implementation - - IMPLEMENTATION - - -***************************************************************************** */ - -/* ***************************************************************************** -String Implementation - state (data pointers, length, capacity, etc') -***************************************************************************** */ - -typedef struct { -#ifndef FIO_STR_NO_REF - volatile uint32_t ref; /* reference counter for fio_str_dup */ -#endif - uint8_t small; /* Flag indicating the String is small and self-contained */ - uint8_t frozen; /* Flag indicating the String is frozen (don't edit) */ -} fio_str__small_s; - -#define FIO_STR_SMALL_DATA(s) ((char *)((&(s)->frozen) + 1)) - -/* the capacity when the string is stored in the container itself */ -#define FIO_STR_SMALL_CAPA \ - (sizeof(fio_str_s) - (size_t)((&((fio_str_s *)0)->frozen) + 1)) - -/** Returns the String's state (capacity, length and pointer). */ -inline FIO_FUNC fio_str_info_s fio_str_info(const fio_str_s *s) { - if (!s) - return (fio_str_info_s){.len = 0}; - return (s->small || !s->data) - ? (fio_str_info_s){.capa = - (s->frozen ? 0 : (FIO_STR_SMALL_CAPA - 1)), - .len = (size_t)(s->small >> 1), - .data = FIO_STR_SMALL_DATA(s)} - : (fio_str_info_s){.capa = (s->frozen ? 0 : s->capa), - .len = s->len, - .data = s->data}; -} - -/** - * Allocates a new fio_str_s object on the heap and initializes it. - * - * Use `fio_str_free2` to free both the String data and the container. - * - * NOTE: This makes the allocation and reference counting logic more intuitive. - */ -inline FIO_FUNC fio_str_s *fio_str_new2(void) { - fio_str_s *str = FIO_MALLOC(sizeof(*str)); - FIO_ASSERT_ALLOC(str); - *str = FIO_STR_INIT; - return str; -} - -/** - * Allocates a new fio_str_s object on the heap, initializes it and copies the - * original (`src`) string into the new string. - * - * Use `fio_str_free2` to free the new string's data and it's container. - */ -inline FIO_FUNC fio_str_s *fio_str_new_copy2(fio_str_s *src) { - fio_str_s *cpy = fio_str_new2(); - fio_str_concat(cpy, src); - return cpy; -} - -/** - * Adds a references to the current String object and returns itself. - * - * If refecrence counting was disabled (FIO_STR_NO_REF was defined), returns a - * copy of the String (free with `fio_str_free2`). - * - * NOTE: Nothing is copied, reference Strings are referencing the same String. - * Editing one reference will effect the other. - * - * The original's String's container should remain in scope (if on the - * stack) or remain allocated (if on the heap) until all the references - * were freed using `fio_str_free` / `fio_str_free2` or discarded. - */ -inline FIO_FUNC fio_str_s *fio_str_dup(fio_str_s *s) { -#ifdef FIO_STR_NO_REF - fio_str_s *s2 = fio_str_new2(); - fio_str_concat(s2, s); - return s2; -#else - if (s) - fio_atomic_add(&s->ref, 1); - return s; -#endif -} - -/** - * Frees the String's resources and reinitializes the container. - * - * Note: if the container isn't allocated on the stack, it should be freed - * separately using `free(s)`. - * - * Returns 0 if the data was freed and -1 if the String is NULL or has un-freed - * references (see fio_str_dup). - */ -inline FIO_FUNC int fio_str_free(fio_str_s *s) { -#ifndef FIO_STR_NO_REF - if (!s || fio_atomic_sub(&s->ref, 1) != (uint32_t)-1) - return -1; -#endif - if (!s->small && s->dealloc) - s->dealloc(s->data); - *s = FIO_STR_INIT; - return 0; -} - -/** - * Frees the String's resources as well as the container. - * - * Note: the container is freed using `free`, make sure `malloc` was used to - * allocate it. - */ -FIO_FUNC void fio_str_free2(fio_str_s *s) { - if (fio_str_free(s)) { - return; - } - FIO_FREE(s); -} - -/** - * Returns a C string with the existing data, clearing the `fio_str_s` object's - * String. - * - * Note: the String data is removed from the container, but the container isn't - * freed. - * - * Returns NULL if there's no String data. - * - * Remember to `fio_free` the returned data and - if required - `fio_str_free2` - * the container. - */ -FIO_FUNC char *fio_str_detach(fio_str_s *s) { - if (!s) - return NULL; - fio_str_info_s i = fio_str_info(s); - if (s->small || !s->data) { - if (!i.len) { - i.data = NULL; - goto finish; - } - /* make a copy */ - void *tmp = FIO_MALLOC(i.len + 1); - FIO_ASSERT_ALLOC(tmp); - memcpy(tmp, i.data, i.len + 1); - i.data = tmp; - } else { - if (!i.len && s->data) { - if (s->dealloc) - s->dealloc(s->data); - i.data = NULL; - } else if (s->dealloc != FIO_FREE) { - /* make a copy */ - void *tmp = FIO_MALLOC(i.len + 1); - FIO_ASSERT_ALLOC(tmp); - memcpy(tmp, i.data, i.len + 1); - i.data = tmp; - if (s->dealloc) - s->dealloc(s->data); - } - } -finish: -#ifdef FIO_STR_NO_REF - *s = (fio_str_s){.small = 1}; - -#else - *s = (fio_str_s){ - .small = s->small, - .ref = s->ref, - }; -#endif - return i.data; -} - -/** Returns the String's length in bytes. */ -inline FIO_FUNC size_t fio_str_len(fio_str_s *s) { - return (s->small || !s->data) ? (s->small >> 1) : s->len; -} - -/** Returns a pointer (`char *`) to the String's content. */ -inline FIO_FUNC char *fio_str_data(fio_str_s *s) { - return (s->small || !s->data) ? FIO_STR_SMALL_DATA(s) : s->data; -} - -/** Returns the String's existing capacity (allocated memory). */ -inline FIO_FUNC size_t fio_str_capa(fio_str_s *s) { - if (s->frozen) - return 0; - return (s->small || !s->data) ? (FIO_STR_SMALL_CAPA - 1) : s->capa; -} - -/** - * Sets the new String size without reallocating any memory (limited by - * existing capacity). - * - * Returns the updated state of the String. - * - * Note: When shrinking, any existing data beyond the new size may be corrupted. - * - * Note: When providing a new size that is grater then the current string - * capacity, any data that was written beyond the current (previous) size might - * be replaced with NUL bytes. - */ -inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size) { - if (!s || s->frozen) { - return fio_str_info(s); - } - if (s->small || !s->data) { - if (size < FIO_STR_SMALL_CAPA) { - s->small = (uint8_t)(((size << 1) | 1) & 0xFF); - FIO_STR_SMALL_DATA(s)[size] = 0; - return (fio_str_info_s){.capa = (FIO_STR_SMALL_CAPA - 1), - .len = size, - .data = FIO_STR_SMALL_DATA(s)}; - } - s->small = (uint8_t)((((FIO_STR_SMALL_CAPA - 1) << 1) | 1) & 0xFF); - fio_str_capa_assert(s, size); - goto big; - } - if (size >= s->capa) { - s->len = fio_ct_if2((uintptr_t)s->dealloc, s->capa, s->len); - fio_str_capa_assert(s, size); - } - -big: - s->len = size; - s->data[size] = 0; - return (fio_str_info_s){.capa = s->capa, .len = size, .data = s->data}; -} - -/* ***************************************************************************** -String Implementation - Hashing -***************************************************************************** */ - -/** - * Return's the String's Risky Hash (see fio_risky_hash). - * - * This value is machine/instance specific (hash seed is a memory address). - * - * NOTE: the hashing function might be changed at any time without notice. It - * wasn't cryptographically analyzed and safety against malicious data can't be - * guaranteed. Use fio_siphash13 or fio_siphash24 when hashing data from - * external sources. - */ -FIO_FUNC uint64_t fio_str_hash(const fio_str_s *s) { - fio_str_info_s state = fio_str_info(s); - return fio_risky_hash(state.data, state.len, FIO_HASH_SECRET_SEED64_1); -} - -/* ***************************************************************************** -String Implementation - Memory management -***************************************************************************** */ - -/** - * Rounds up allocated capacity to the closest 2 words byte boundary (leaving 1 - * byte space for the NUL byte). - * - * This shouldn't effect actual allocation size and should only minimize the - * effects of the memory allocator's alignment rounding scheme. - * - * To clarify: - * - * Memory allocators are required to allocate memory on the minimal alignment - * required by the largest type (`long double`), which usually results in memory - * allocations using this alignment as a minimal spacing. - * - * For example, on 64 bit architectures, it's likely that `malloc(18)` will - * allocate the same amount of memory as `malloc(32)` due to alignment concerns. - * - * In fact, with some allocators (i.e., jemalloc), spacing increases for larger - * allocations - meaning the allocator will round up to more than 16 bytes, as - * noted here: http://jemalloc.net/jemalloc.3.html#size_classes - * - * Note that this increased spacing, doesn't occure with facil.io's allocator, - * since it uses 16 byte alignment right up until allocations are routed - * directly to `mmap` (due to their size, usually over 12KB). - */ -#define ROUND_UP_CAPA2WORDS(num) (((num) + 1) | (sizeof(long double) - 1)) - -/** - * Requires the String to have at least `needed` capacity. Returns the current - * state of the String. - */ -FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed) { - if (!s || s->frozen) { - return fio_str_info(s); - } - char *tmp; - if (s->small || !s->data) { - if (needed < FIO_STR_SMALL_CAPA) { - return (fio_str_info_s){.capa = (FIO_STR_SMALL_CAPA - 1), - .len = (size_t)(s->small >> 1), - .data = FIO_STR_SMALL_DATA(s)}; - } - goto is_small; - } - if (needed < s->capa) { - return (fio_str_info_s){.capa = s->capa, .len = s->len, .data = s->data}; - } - needed = ROUND_UP_CAPA2WORDS(needed); - if (s->dealloc == FIO_FREE) { - tmp = (char *)FIO_REALLOC(s->data, needed + 1, s->len + 1); - FIO_ASSERT_ALLOC(tmp); - } else { - tmp = (char *)FIO_MALLOC(needed + 1); - FIO_ASSERT_ALLOC(tmp); - memcpy(tmp, s->data, s->len + 1); - if (s->dealloc) - s->dealloc(s->data); - s->dealloc = FIO_FREE; - } - s->capa = needed; - s->data = tmp; - s->data[needed] = 0; - return (fio_str_info_s){.capa = s->capa, .len = s->len, .data = s->data}; - -is_small: - /* small string (string data is within the container) */ - needed = ROUND_UP_CAPA2WORDS(needed); - tmp = (char *)FIO_MALLOC(needed + 1); - FIO_ASSERT_ALLOC(tmp); - const size_t existing_len = (size_t)((s->small >> 1) & 0xFF); - if (existing_len) { - memcpy(tmp, FIO_STR_SMALL_DATA(s), existing_len + 1); - } else { - tmp[0] = 0; - } -#ifdef FIO_STR_NO_REF - *s = (fio_str_s){ - .small = 0, - .capa = needed, - .len = existing_len, - .dealloc = FIO_FREE, - .data = tmp, - }; -#else - *s = (fio_str_s){ - .ref = s->ref, - .small = 0, - .capa = needed, - .len = existing_len, - .dealloc = FIO_FREE, - .data = tmp, - }; -#endif - return (fio_str_info_s){.capa = needed, .len = existing_len, .data = s->data}; -} - -/** Performs a best attempt at minimizing memory consumption. */ -FIO_FUNC void fio_str_compact(fio_str_s *s) { - if (!s || (s->small || !s->data)) - return; - char *tmp; - if (s->len < FIO_STR_SMALL_CAPA) - goto shrink2small; - tmp = fio_realloc(s->data, s->len + 1); - FIO_ASSERT_ALLOC(tmp); - s->data = tmp; - s->capa = s->len; - return; - -shrink2small: - /* move the string into the container */ - tmp = s->data; - size_t len = s->len; - *s = (fio_str_s){.small = (uint8_t)(((len << 1) | 1) & 0xFF), - .frozen = s->frozen}; - if (len) { - memcpy(FIO_STR_SMALL_DATA(s), tmp, len + 1); - } - FIO_FREE(tmp); -} - -/* ***************************************************************************** -String Implementation - UTF-8 State -***************************************************************************** */ - -/** - * Maps the first 5 bits in a byte (0b11111xxx) to a UTF-8 codepoint length. - * - * Codepoint length 0 == error. - * - * The first valid length can be any value between 1 to 4. - * - * A continuation byte (second, third or forth) valid length must be 5. - * - * To map was populated using the following Ruby script: - * - * map = []; 32.times { map << 0 }; (0..0b1111).each {|i| map[i] = 1} ; - * (0b10000..0b10111).each {|i| map[i] = 5} ; - * (0b11000..0b11011).each {|i| map[i] = 2} ; - * (0b11100..0b11101).each {|i| map[i] = 3} ; - * map[0b11110] = 4; map; - */ -static uint8_t fio_str_utf8_map[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, - 5, 5, 2, 2, 2, 2, 3, 3, 4, 0}; - -#undef FIO_STR_UTF8_CODE_POINT -/** - * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8 - * character into the i32 variable (which must be a signed integer with 32bits - * or more). On error, `i32` will be equal to `-1` and `ptr` will not step - * forwards. - * - * The `end` value is only used for overflow protection. - */ -#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32) \ - do { \ - switch (fio_str_utf8_map[((uint8_t *)(ptr))[0] >> 3]) { \ - case 1: \ - (i32) = ((uint8_t *)(ptr))[0]; \ - ++(ptr); \ - break; \ - case 2: \ - if (((ptr) + 2 > (end)) || \ - fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5) { \ - (i32) = -1; \ - break; \ - } \ - (i32) = \ - ((((uint8_t *)(ptr))[0] & 31) << 6) | (((uint8_t *)(ptr))[1] & 63); \ - (ptr) += 2; \ - break; \ - case 3: \ - if (((ptr) + 3 > (end)) || \ - fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \ - fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5) { \ - (i32) = -1; \ - break; \ - } \ - (i32) = ((((uint8_t *)(ptr))[0] & 15) << 12) | \ - ((((uint8_t *)(ptr))[1] & 63) << 6) | \ - (((uint8_t *)(ptr))[2] & 63); \ - (ptr) += 3; \ - break; \ - case 4: \ - if (((ptr) + 4 > (end)) || \ - fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \ - fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5 || \ - fio_str_utf8_map[((uint8_t *)(ptr))[3] >> 3] != 5) { \ - (i32) = -1; \ - break; \ - } \ - (i32) = ((((uint8_t *)(ptr))[0] & 7) << 18) | \ - ((((uint8_t *)(ptr))[1] & 63) << 12) | \ - ((((uint8_t *)(ptr))[2] & 63) << 6) | \ - (((uint8_t *)(ptr))[3] & 63); \ - (ptr) += 4; \ - break; \ - default: \ - (i32) = -1; \ - break; \ - } \ - } while (0); - -/** Returns 1 if the String is UTF-8 valid and 0 if not. */ -FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s) { - if (!s) - return 0; - fio_str_info_s state = fio_str_info(s); - if (!state.len) - return 1; - char *const end = state.data + state.len; - int32_t c = 0; - do { - FIO_STR_UTF8_CODE_POINT(state.data, end, c); - } while (c > 0 && state.data < end); - return state.data == end && c >= 0; -} - -/** Returns the String's length in UTF-8 characters. */ -FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s) { - fio_str_info_s state = fio_str_info(s); - if (!state.len) - return 0; - char *end = state.data + state.len; - size_t utf8len = 0; - int32_t c = 0; - do { - ++utf8len; - FIO_STR_UTF8_CODE_POINT(state.data, end, c); - } while (c > 0 && state.data < end); - if (state.data != end || c == -1) { - /* invalid */ - return 0; - } - return utf8len; -} - -/** - * Takes a UTF-8 character selection information (UTF-8 position and length) and - * updates the same variables so they reference the raw byte slice information. - * - * If the String isn't UTF-8 valid up to the requested selection, than `pos` - * will be updated to `-1` otherwise values are always positive. - * - * The returned `len` value may be shorter than the original if there wasn't - * enough data left to accomodate the requested length. When a `len` value of - * `0` is returned, this means that `pos` marks the end of the String. - * - * Returns -1 on error and 0 on success. - */ -FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len) { - fio_str_info_s state = fio_str_info(s); - if (!state.data) - goto error; - if (!state.len || *pos == -1) - goto at_end; - - int32_t c = 0; - char *p = state.data; - char *const end = state.data + state.len; - size_t start; - - if (*pos) { - if ((*pos) > 0) { - start = *pos; - while (start && p < end && c >= 0) { - FIO_STR_UTF8_CODE_POINT(p, end, c); - --start; - } - if (c == -1) - goto error; - if (start || p >= end) - goto at_end; - *pos = p - state.data; - } else { - /* walk backwards */ - p = state.data + state.len - 1; - c = 0; - ++*pos; - do { - switch (fio_str_utf8_map[((uint8_t *)p)[0] >> 3]) { - case 5: - ++c; - break; - case 4: - if (c != 3) - goto error; - c = 0; - ++(*pos); - break; - case 3: - if (c != 2) - goto error; - c = 0; - ++(*pos); - break; - case 2: - if (c != 1) - goto error; - c = 0; - ++(*pos); - break; - case 1: - if (c) - goto error; - ++(*pos); - break; - default: - goto error; - } - --p; - } while (p > state.data && *pos); - if (c) - goto error; - ++p; /* There's always an extra back-step */ - *pos = (p - state.data); - } - } - - /* find end */ - start = *len; - while (start && p < end && c >= 0) { - FIO_STR_UTF8_CODE_POINT(p, end, c); - --start; - } - if (c == -1 || p > end) - goto error; - *len = p - (state.data + (*pos)); - return 0; - -at_end: - *pos = state.len; - *len = 0; - return 0; -error: - *pos = -1; - *len = 0; - return -1; -} - -/* ***************************************************************************** -String Implementation - Content Manipulation and Review -***************************************************************************** */ - -/** - * Writes data at the end of the String (similar to `fio_str_insert` with the - * argument `pos == -1`). - */ -inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src, - size_t src_len) { - if (!s || !src_len || !src || s->frozen) - return fio_str_info(s); - fio_str_info_s state = fio_str_resize(s, src_len + fio_str_len(s)); - memcpy(state.data + (state.len - src_len), src, src_len); - return state; -} - -/** - * Writes a number at the end of the String using normal base 10 notation. - */ -inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num) { - if (!s || s->frozen) - return fio_str_info(s); - fio_str_info_s i; - if (!num) - goto zero; - char buf[22] = {0}; - uint64_t l = 0; - uint8_t neg; - if ((neg = (num < 0))) { - num = 0 - num; - neg = 1; - } - while (num) { - uint64_t t = num / 10; - buf[l++] = '0' + (num - (t * 10)); - num = t; - } - if (neg) { - buf[l++] = '-'; - } - i = fio_str_resize(s, fio_str_len(s) + l); - - while (l) { - --l; - i.data[i.len - (l + 1)] = buf[l]; - } - return i; -zero: - i = fio_str_resize(s, fio_str_len(s) + 1); - i.data[i.len - 1] = '0'; - return i; -} - -/** - * Appens the `src` String to the end of the `dest` String. - */ -inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest, - fio_str_s const *src) { - if (!dest || !src || dest->frozen) - return fio_str_info(dest); - fio_str_info_s src_state = fio_str_info(src); - if (!src_state.len) - return fio_str_info(dest); - fio_str_info_s state = - fio_str_resize(dest, src_state.len + fio_str_len(dest)); - memcpy(state.data + state.len - src_state.len, src_state.data, src_state.len); - return state; -} - -/** - * Replaces the data in the String - replacing `old_len` bytes starting at - * `start_pos`, with the data at `src` (`src_len` bytes long). - * - * Negative `start_pos` values are calculated backwards, `-1` == end of String. - * - * When `old_len` is zero, the function will insert the data at `start_pos`. - * - * If `src_len == 0` than `src` will be ignored and the data marked for - * replacement will be erased. - */ -FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos, - size_t old_len, const void *src, - size_t src_len) { - fio_str_info_s state = fio_str_info(s); - if (!s || s->frozen || (!old_len && !src_len)) - return state; - - if (start_pos < 0) { - /* backwards position indexing */ - start_pos += s->len + 1; - if (start_pos < 0) - start_pos = 0; - } - - if (start_pos + old_len >= state.len) { - /* old_len overflows the end of the String */ - if (s->small || !s->data) { - s->small = 1 | ((size_t)((start_pos << 1) & 0xFF)); - } else { - s->len = start_pos; - } - return fio_str_write(s, src, src_len); - } - - /* data replacement is now always in the middle (or start) of the String */ - const size_t new_size = state.len + (src_len - old_len); - - if (old_len != src_len) { - /* there's an offset requiring an adjustment */ - if (old_len < src_len) { - /* make room for new data */ - const size_t offset = src_len - old_len; - state = fio_str_resize(s, state.len + offset); - } - memmove(state.data + start_pos + src_len, state.data + start_pos + old_len, - (state.len - start_pos) - old_len); - } - if (src_len) { - memcpy(state.data + start_pos, src, src_len); - } - - return fio_str_resize(s, new_size); -} - -/** Writes to the String using a vprintf like interface. */ -FIO_FUNC __attribute__((format(printf, 2, 0))) fio_str_info_s -fio_str_vprintf(fio_str_s *s, const char *format, va_list argv) { - va_list argv_cpy; - va_copy(argv_cpy, argv); - int len = vsnprintf(NULL, 0, format, argv_cpy); - va_end(argv_cpy); - if (len <= 0) - return fio_str_info(s); - fio_str_info_s state = fio_str_resize(s, len + fio_str_len(s)); - vsnprintf(state.data + (state.len - len), len + 1, format, argv); - return state; -} - -/** Writes to the String using a printf like interface. */ -FIO_FUNC __attribute__((format(printf, 2, 3))) fio_str_info_s -fio_str_printf(fio_str_s *s, const char *format, ...) { - va_list argv; - va_start(argv, format); - fio_str_info_s state = fio_str_vprintf(s, format, argv); - va_end(argv); - return state; -} - -/** - * Opens the file `filename` and pastes it's contents (or a slice ot it) at the - * end of the String. If `limit == 0`, than the data will be read until EOF. - * - * If the file can't be located, opened or read, or if `start_at` is beyond - * the EOF position, NULL is returned in the state's `data` field. - */ -FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s, const char *filename, - intptr_t start_at, intptr_t limit) { - fio_str_info_s state = {.data = NULL}; -#if defined(__unix__) || defined(__linux__) || defined(__APPLE__) || \ - defined(__CYGWIN__) || defined(__MINGW32__) - /* POSIX implementations. */ - if (filename == NULL || !s) - return state; - struct stat f_data; - int file = -1; - char *path = NULL; - size_t path_len = 0; - - if (filename[0] == '~' && (filename[1] == '/' || filename[1] == '\\')) { - char *home = getenv("HOME"); - if (home) { - size_t filename_len = strlen(filename); - size_t home_len = strlen(home); - if ((home_len + filename_len) >= (1 << 16)) { - /* too long */ - return state; - } - if (home[home_len - 1] == '/' || home[home_len - 1] == '\\') - --home_len; - path_len = home_len + filename_len - 1; - path = FIO_MALLOC(path_len + 1); - FIO_ASSERT_ALLOC(path); - memcpy(path, home, home_len); - memcpy(path + home_len, filename + 1, filename_len); - path[path_len] = 0; - filename = path; - } - } - - if (stat(filename, &f_data) == -1) { - goto finish; - } - - if (f_data.st_size <= 0 || start_at >= f_data.st_size) { - state = fio_str_info(s); - goto finish; - } -#ifdef __MINGW32__ - file = _open(filename, O_RDONLY); -#else - file = open(filename, O_RDONLY); -#endif - if (file == -1) - goto finish; - - if (start_at < 0) { - start_at = f_data.st_size + start_at; - if (start_at < 0) - start_at = 0; - } - - if (limit <= 0 || f_data.st_size < (limit + start_at)) - limit = f_data.st_size - start_at; - - const size_t org_len = fio_str_len(s); - state = fio_str_resize(s, org_len + limit); - if (pread(file, state.data + org_len, limit, start_at) != (ssize_t)limit) { - fio_str_resize(s, org_len); - state.data = NULL; - state.len = state.capa = 0; - } -#ifdef __MINGW32__ - _close(file); -#else - close(file); -#endif -finish: - FIO_FREE(path); - return state; -#else - /* TODO: consider adding non POSIX implementations. */ - FIO_LOG_ERROR("File reading requires a posix system (ignored!).\n"); - return state; -#endif -} - -/** - * Prevents further manipulations to the String's content. - */ -inline FIO_FUNC void fio_str_freeze(fio_str_s *s) { - if (!s) - return; - s->frozen = 1; -} - -/** - * Binary comparison returns `1` if both strings are equal and `0` if not. - */ -inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2) { - if (str1 == str2) - return 1; - if (!str1 || !str2) - return 0; - fio_str_info_s s1 = fio_str_info(str1); - fio_str_info_s s2 = fio_str_info(str2); - return (s1.len == s2.len && !memcmp(s1.data, s2.data, s1.len)); -} - -/** - * `fio_str_send_free2` sends the fio_str_s using `fio_write2`, freeing the - * String once the data was sent - * - * As the naming indicates, the String is assumed to have been allocated using - * `fio_str_new2` or `fio_malloc`. - */ -inline FIO_FUNC ssize_t fio_str_send_free2(const intptr_t uuid, - const fio_str_s *str) { - if (!str) - return 0; - fio_str_info_s state = fio_str_info(str); - return fio_write2(uuid, .data.buffer = str, .length = state.len, - .offset = ((uintptr_t)state.data - (uintptr_t)str), - .after.dealloc = (void (*)(void *))fio_str_free2); -} - -#undef ROUND_UP_CAPA2WORDS -#undef FIO_STR_SMALL_DATA -#undef FIO_STR_NO_REF - -#endif /* H_FIO_STR_H */ - -/* ***************************************************************************** - - - - - - - - - - - - Dynamic Array Data-Store - - - - - - - - - - - -***************************************************************************** */ - -#ifdef FIO_ARY_NAME -/** - * A simple typed dynamic array with a minimal API. - * - * To create an Array type, define the macro FIO_ARY_NAME. i.e.: - * - * #define FIO_ARY_NAME fio_cstr_ary - * #define FIO_ARY_TYPE char * - * #define FIO_ARY_COMPARE(k1, k2) (!strcmp((k1), (k2))) - * #include - * - * It's possible to create a number of Array types by reincluding the fio.h - * header. i.e.: - * - * - * #define FIO_INCLUDE_STR - * #include // adds the fio_str_s types and functions - * - * #define FIO_ARY_NAME fio_int_ary - * #define FIO_ARY_TYPE int - * #include // creates the fio_int_ary_s Array and functions - * - * #define FIO_ARY_NAME fio_str_ary - * #define FIO_ARY_TYPE fio_str_s * - * #define FIO_ARY_COMPARE(k1, k2) (fio_str_iseq((k1), (k2))) - * #define FIO_ARY_COPY(key) fio_str_dup((key)) - * #define FIO_ARY_DESTROY(key) fio_str_free2((key)) - * #include // creates the fio_str_ary_s Array and functions - * - * Note: Before freeing the Array, FIO_ARY_DESTROY will be automatically called - * for every existing object, including any invalid objects (if any). - */ - -/* Used for naming functions and types, prefixing FIO_ARY_NAME to the name */ -#define FIO_NAME_FROM_MACRO_STEP2(name, postfix) name##_##postfix -#define FIO_NAME_FROM_MACRO_STEP1(name, postfix) \ - FIO_NAME_FROM_MACRO_STEP2(name, postfix) -#define FIO_NAME(postfix) FIO_NAME_FROM_MACRO_STEP1(FIO_ARY_NAME, postfix) - -/* Used for naming the `free` function */ -#define FIO_NAME_FROM_MACRO_STEP4(name) name##_free -#define FIO_NAME_FROM_MACRO_STEP3(name) FIO_NAME_FROM_MACRO_STEP4(name) -#define FIO_NAME_FREE() FIO_NAME_FROM_MACRO_STEP3(FIO_ARY_NAME) - -/* The default Array object type is `void *` */ -#if !defined(FIO_ARY_TYPE) -#define FIO_ARY_TYPE void * -#endif - -/* An invalid object has all bytes set to 0 - a static constant will do. */ -#if !defined(FIO_ARY_INVALID) -static FIO_ARY_TYPE const FIO_NAME(s___const_invalid_object); -#define FIO_ARY_INVALID FIO_NAME(s___const_invalid_object) -#endif - -/* The default Array comparison assumes a simple type */ -#if !defined(FIO_ARY_COMPARE) -#define FIO_ARY_COMPARE(o1, o2) ((o1) == (o2)) -#endif - -/** object copy required? */ -#ifndef FIO_ARY_COPY -#define FIO_ARY_COPY_IS_SIMPLE 1 -#define FIO_ARY_COPY(dest, obj) ((dest) = (obj)) -#endif - -/** object destruction required? */ -#ifndef FIO_ARY_DESTROY -#define FIO_ARY_DESTROY(obj) ((void)0) -#endif - -/* Customizable memory management */ -#ifndef FIO_ARY_MALLOC /* NULL ptr indicates new allocation */ -#define FIO_ARY_MALLOC(size) FIO_MALLOC((size)) -#endif - -/* Customizable memory management */ -#ifndef FIO_ARY_REALLOC /* NULL ptr indicates new allocation */ -#define FIO_ARY_REALLOC(ptr, original_size, new_size, valid_data_length) \ - FIO_REALLOC((ptr), (new_size), (valid_data_length)) -#endif - -#ifndef FIO_ARY_DEALLOC -#define FIO_ARY_DEALLOC(ptr, size) FIO_FREE((ptr)) -#endif - -/* padding to be assumed for future expansion. */ -#ifndef FIO_ARY_PADDING -#define FIO_ARY_PADDING 4 -#endif - -/* minimizes allocation "dead space" by alligning allocated length to 16bytes */ -#undef FIO_ARY_SIZE2WORDS -#define FIO_ARY_SIZE2WORDS(size) \ - ((sizeof(FIO_ARY_TYPE) & 1) \ - ? (((size) & (~15)) + 16) \ - : (sizeof(FIO_ARY_TYPE) & 2) \ - ? (((size) & (~7)) + 8) \ - : (sizeof(FIO_ARY_TYPE) & 4) \ - ? (((size) & (~3)) + 4) \ - : (sizeof(FIO_ARY_TYPE) & 8) ? (((size) & (~1)) + 2) \ - : (size)) - -/* ***************************************************************************** -Array API -***************************************************************************** */ - -/** The Array container type. */ -typedef struct FIO_NAME(s) FIO_NAME(s); - -#ifndef FIO_ARY_INIT -/** Initializes the Array */ -#define FIO_ARY_INIT \ - { .capa = 0 } -#endif - -/** Frees the array's internal data. */ -FIO_FUNC inline void FIO_NAME_FREE()(FIO_NAME(s) * ary); - -/** Returns the number of elements in the Array. */ -FIO_FUNC inline size_t FIO_NAME(count)(FIO_NAME(s) * ary); - -/** Returns the current, temporary, array capacity (it's dynamic). */ -FIO_FUNC inline size_t FIO_NAME(capa)(FIO_NAME(s) * ary); - -/** - * Adds all the items in the `src` Array to the end of the `dest` Array. - * - * The `src` Array remain untouched. - */ -FIO_FUNC inline void FIO_NAME(concat)(FIO_NAME(s) * dest, FIO_NAME(s) * src); - -/** - * Sets `index` to the value in `data`. - * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). - * - * If `old` isn't NULL, the existing data will be copied to the location pointed - * to by `old` before the copy in the Array is destroyed. - */ -FIO_FUNC inline void FIO_NAME(set)(FIO_NAME(s) * ary, intptr_t index, - FIO_ARY_TYPE data, FIO_ARY_TYPE *old); - -/** - * Returns the value located at `index` (no copying is peformed). - * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). - */ -FIO_FUNC inline FIO_ARY_TYPE FIO_NAME(get)(FIO_NAME(s) * ary, intptr_t index); - -/** - * Returns the index of the object or -1 if the object wasn't found. - */ -FIO_FUNC inline intptr_t FIO_NAME(find)(FIO_NAME(s) * ary, FIO_ARY_TYPE data); - -/** - * Removes an object from the array, MOVING all the other objects to prevent - * "holes" in the data. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns 0 on success and -1 on error. - */ -FIO_FUNC inline int FIO_NAME(remove)(FIO_NAME(s) * ary, intptr_t index, - FIO_ARY_TYPE *old); - -/** - * Removes an object from the array, if it exists, MOVING all the other objects - * to prevent "holes" in the data. - * - * Returns -1 if the object wasn't found or 0 if the object was successfully - * removed. - */ -FIO_FUNC inline int FIO_NAME(remove2)(FIO_NAME(s) * ary, FIO_ARY_TYPE data, - FIO_ARY_TYPE *old); - -/** - * Returns a pointer to the C array containing the objects. - */ -FIO_FUNC inline FIO_ARY_TYPE *FIO_NAME(to_a)(FIO_NAME(s) * ary); - -/** - * Pushes an object to the end of the Array. Returns -1 on error. - */ -FIO_FUNC inline int FIO_NAME(push)(FIO_NAME(s) * ary, FIO_ARY_TYPE data); - -/** - * Removes an object from the end of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns -1 on error (Array is empty) and 0 on success. - */ -FIO_FUNC inline int FIO_NAME(pop)(FIO_NAME(s) * ary, FIO_ARY_TYPE *old); - -/** - * Unshifts an object to the beginning of the Array. Returns -1 on error. - * - * This could be expensive, causing `memmove`. - */ -FIO_FUNC inline int FIO_NAME(unshift)(FIO_NAME(s) * ary, FIO_ARY_TYPE data); - -/** - * Removes an object from the beginning of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns -1 on error (Array is empty) and 0 on success. - */ -FIO_FUNC inline int FIO_NAME(shift)(FIO_NAME(s) * ary, FIO_ARY_TYPE *old); - -/** - * Iteration using a callback for each entry in the array. - * - * The callback task function must accept an the entry data as well as an opaque - * user pointer. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. - */ -FIO_FUNC inline size_t FIO_NAME(each)(FIO_NAME(s) * ary, size_t start_at, - int (*task)(FIO_ARY_TYPE pt, void *arg), - void *arg); -/** - * Removes any FIO_ARY_TYPE_INVALID object from an Array (NULL pointers by - * default), keeping all other data in the array. - * - * This action is O(n) where n in the length of the array. - * It could get expensive. - */ -FIO_FUNC inline void FIO_NAME(compact)(FIO_NAME(s) * ary); - -/** - * Iterates through the list using a `for` loop. - * - * Access the object with the pointer `pos`. The `pos` variable can be named - * however you please. - * - * Avoid editing the array during a FOR loop, although I hope it's possible, I - * wouldn't count on it. - */ -#ifndef FIO_ARY_FOR -#define FIO_ARY_FOR(ary, pos) \ - if ((ary)->arry) \ - for (__typeof__((ary)->arry) start__tmp__ = (ary)->arry, \ - pos = ((ary)->arry + (ary)->start); \ - pos < (ary)->arry + (ary)->end; \ - (pos = (ary)->arry + (pos - start__tmp__) + 1), \ - (start__tmp__ = (ary)->arry)) -#endif - -/* ***************************************************************************** -Array Type -***************************************************************************** */ - -struct FIO_NAME(s) { - size_t start; /* first index where data was already written */ - size_t end; /* next spot to write at tail */ - size_t capa; /* existing capacity */ - FIO_ARY_TYPE *arry; /* the actual array's memory, if any */ -}; - -/* ***************************************************************************** -Array Memory Management -***************************************************************************** */ - -FIO_FUNC inline void FIO_NAME_FREE()(FIO_NAME(s) * ary) { - if (ary) { - const size_t count = ary->end; - for (size_t i = ary->start; i < count; ++i) { - FIO_ARY_DESTROY((ary->arry[i])); - } - FIO_ARY_DEALLOC(ary->arry, ary->capa * sizeof(*ary->arry)); - *ary = (FIO_NAME(s))FIO_ARY_INIT; - } -} - -/** Converts between a relative index to an absolute index. */ -FIO_FUNC inline intptr_t FIO_NAME(__rel2absolute)(FIO_NAME(s) * ary, - intptr_t index) { - if (index >= 0) - return index; - index += ary->end - ary->start; - if (index >= 0) - return index; - return 0; -} - -/** Makes sure that `len` positions are available at the Array's end. */ -FIO_FUNC void FIO_NAME(__require_on_top)(FIO_NAME(s) * ary, size_t len) { - if (ary->end + len < ary->capa) - return; - len = FIO_ARY_SIZE2WORDS((len + ary->end)); - /* reallocate enough memory */ - ary->arry = FIO_ARY_REALLOC(ary->arry, sizeof(*ary->arry) * ary->capa, - (len) * sizeof(*ary->arry), - ary->end * sizeof(*ary->arry)); - FIO_ASSERT_ALLOC(ary->arry); - ary->capa = len; -} - -/** Makes sure that `len` positions are available at the Array's head. */ -FIO_FUNC void FIO_NAME(__require_on_bottom)(FIO_NAME(s) * ary, size_t len) { - if (ary->start >= len) - return; - FIO_ARY_TYPE *tmp = ary->arry; - len = FIO_ARY_SIZE2WORDS((len - ary->start) + ary->end); - if (ary->capa <= len) { - /* no room - allocate and copy */ - ary->arry = FIO_ARY_MALLOC(len * sizeof(*ary->arry)); - FIO_ASSERT_ALLOC(ary->arry); - ary->capa = len; - } - /* move existing data to the end of the existing space */ - len = ary->end - ary->start; - ary->end = ary->capa; - if (len) - memmove(ary->arry + (ary->capa - len), tmp + ary->start, - len * sizeof(*ary->arry)); - ary->start = ary->end - len; - if (tmp != ary->arry) { - FIO_FREE(tmp); - } -} - -/* ***************************************************************************** -Array API implementation -***************************************************************************** */ - -/** Returns the number of elements in the Array. */ -FIO_FUNC inline size_t FIO_NAME(count)(FIO_NAME(s) * ary) { - return ary ? (ary->end - ary->start) : 0; -} - -/** Returns the current, temporary, array capacity (it's dynamic). */ -FIO_FUNC inline size_t FIO_NAME(capa)(FIO_NAME(s) * ary) { - return ary ? ary->capa : 0; -} - -/** - * Returns a pointer to the C array containing the objects. - */ -FIO_FUNC inline FIO_ARY_TYPE *FIO_NAME(to_a)(FIO_NAME(s) * ary) { - return ary ? (ary->arry + ary->start) : NULL; -} - -/** - * Adds all the items in the `src` Array to the end of the `dest` Array. - * - * The `src` Array remain untouched. - */ -FIO_FUNC inline void FIO_NAME(concat)(FIO_NAME(s) * dest, FIO_NAME(s) * src) { - if (!src) - return; - const size_t added = FIO_NAME(count)(src); - if (!added || !dest) - return; - FIO_NAME(__require_on_top)(dest, added); -#if FIO_ARY_COPY_IS_SIMPLE - memcpy(dest->arry + dest->end, src->arry + src->start, - added * sizeof(*dest->arry)); -#else - /* don't use memcpy, in case copying has side-effects (see macro) */ - for (size_t i = 0; i < added; ++i) { - FIO_ARY_COPY(((dest->arry + dest->end)[i]), ((src->arry + src->start)[i])); - } -#endif - dest->end += added; -} - -/** - * Sets `index` to the value in `data`. - * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). - * - * If `old` isn't NULL, the existing data will be copied to the location pointed - * to by `old` before the copy in the Array is destroyed. - */ -FIO_FUNC inline void FIO_NAME(set)(FIO_NAME(s) * ary, intptr_t index, - FIO_ARY_TYPE data, FIO_ARY_TYPE *old) { - if (!ary) - return; - if (ary->start == ary->end) /* reset memory starting point? */ - ary->start = ary->end = 0; - - index = FIO_NAME(__rel2absolute)(ary, index); - - const intptr_t spaces = index - (ary->end - ary->start); - if (spaces < 0) { - /* likely */ - if (old) - FIO_ARY_COPY((*old), ((ary->arry + ary->start)[index])); - FIO_ARY_DESTROY(((ary->arry + ary->start)[index])); - FIO_ARY_COPY(((ary->arry + ary->start)[index]), data); - return; - } - - /* fill empty spaces with zero */ - FIO_NAME(__require_on_top)(ary, spaces + 1); - if (spaces) { - memset(ary->arry + ary->end, 0, sizeof(*ary->arry) * spaces); - } - FIO_ARY_COPY(((ary->arry + ary->start)[index]), data); - ary->end = index + 1; -} - -/** - * Returns the value located at `index` (no copying is peformed). - * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). - */ -FIO_FUNC inline FIO_ARY_TYPE FIO_NAME(get)(FIO_NAME(s) * ary, intptr_t index) { - if (!ary) - return FIO_ARY_INVALID; - index = FIO_NAME(__rel2absolute)(ary, index); - if ((size_t)index >= ary->end - ary->start) - return FIO_ARY_INVALID; - return (ary->arry + ary->start)[index]; -} - -/** - * Returns the index of the object or -1 if the object wasn't found. - */ -FIO_FUNC inline intptr_t FIO_NAME(find)(FIO_NAME(s) * ary, FIO_ARY_TYPE data) { - const size_t count = FIO_NAME(count)(ary); - if (!count) { - return -1; - } - size_t pos = ary->start; - register const size_t end = ary->end; - while (pos < end && !FIO_ARY_COMPARE(data, ary->arry[pos])) { - ++pos; - } - if (pos == end) - return -1; - return (pos - ary->start); -} - -/** - * Removes an object from the array, MOVING all the other objects to prevent - * "holes" in the data. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns 0 on success and -1 on error. - */ -FIO_FUNC inline int FIO_NAME(remove)(FIO_NAME(s) * ary, intptr_t index, - FIO_ARY_TYPE *old) { - index = FIO_NAME(__rel2absolute)(ary, index); - const size_t count = FIO_NAME(count)(ary); - if (!count || (size_t)index >= count) { - return -1; - } - index += ary->start; - if (old) - FIO_ARY_COPY((*old), (ary->arry[index])); - FIO_ARY_DESTROY((ary->arry[index])); - if ((size_t)index == ary->start) { - ++ary->start; - return 0; - } - --ary->end; - if ((size_t)index < ary->end) { - memmove(ary->arry + index, ary->arry + index + 1, - (ary->end - index) * sizeof(*ary->arry)); - } - return 0; -} - -/** - * Removes an object from the array, if it exists, MOVING all the other objects - * to prevent "holes" in the data. - * - * Returns -1 if the object wasn't found or 0 if the object was successfully - * removed. - */ -FIO_FUNC inline int FIO_NAME(remove2)(FIO_NAME(s) * ary, FIO_ARY_TYPE data, - FIO_ARY_TYPE *old) { - intptr_t index = FIO_NAME(find)(ary, data); - if (index == -1) { - return -1; - } - return FIO_NAME(remove)(ary, index, old); -} - -/** - * Pushes an object to the end of the Array. Returns -1 on error. - */ -FIO_FUNC inline int FIO_NAME(push)(FIO_NAME(s) * ary, FIO_ARY_TYPE data) { - if (!ary) - return -1; - if (ary->capa <= ary->end) - FIO_NAME(__require_on_top)(ary, 1 + FIO_ARY_PADDING); - if (ary->start == ary->end) /* reset memory starting point? */ - ary->start = ary->end = 0; - FIO_ARY_COPY(ary->arry[ary->end], data); - ++ary->end; - return 0; -} - -/** - * Removes an object from the end of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns -1 on error (Array is empty) and 0 on success. - */ -FIO_FUNC inline int FIO_NAME(pop)(FIO_NAME(s) * ary, FIO_ARY_TYPE *old) { - if (!FIO_NAME(count)(ary)) - return -1; - --ary->end; - if (old) - FIO_ARY_COPY((*old), (ary->arry[ary->end])); - FIO_ARY_DESTROY((ary->arry[ary->end])); - return 0; -} - -/** - * Unshifts an object to the beginning of the Array. Returns -1 on error. - * - * This could be expensive, causing `memmove`. - */ -FIO_FUNC inline int FIO_NAME(unshift)(FIO_NAME(s) * ary, FIO_ARY_TYPE data) { - if (!ary) - return -1; - if (ary->start == 0) - FIO_NAME(__require_on_bottom)(ary, 8); - --ary->start; - FIO_ARY_COPY(ary->arry[ary->start], data); - return 0; -} - -/** - * Removes an object from the beginning of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns -1 on error (Array is empty) and 0 on success. - */ -FIO_FUNC inline int FIO_NAME(shift)(FIO_NAME(s) * ary, FIO_ARY_TYPE *old) { - if (!FIO_NAME(count)(ary)) - return -1; - if (old) - FIO_ARY_COPY((*old), (ary->arry[ary->start])); - FIO_ARY_DESTROY((ary->arry[ary->start])); - ++ary->start; - return 0; -} - -/** - * Iteration using a callback for each entry in the array. - * - * The callback task function must accept an the entry data as well as an opaque - * user pointer. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. - */ -FIO_FUNC inline size_t FIO_NAME(each)(FIO_NAME(s) * ary, size_t start_at, - int (*task)(FIO_ARY_TYPE pt, void *arg), - void *arg) { - const size_t count = FIO_NAME(count)(ary); - if (!count || start_at >= count) { - return count; - } - while (start_at < count && - task(ary->arry[ary->start + (start_at++)], arg) != -1) - ; - return start_at; -} -/** - * Removes any FIO_ARY_TYPE_INVALID object from an Array (NULL pointers by - * default), keeping all other data in the array. - * - * This action is O(n) where n in the length of the array. - * It could get expensive. - */ -FIO_FUNC inline void FIO_NAME(compact)(FIO_NAME(s) * ary) { - const size_t count = FIO_NAME(count)(ary); - if (!count) - return; - register FIO_ARY_TYPE *pos = ary->arry + ary->start; - register FIO_ARY_TYPE *reader = ary->arry + ary->start; - register FIO_ARY_TYPE *stop = ary->arry + ary->end; - while (reader < stop) { - if (!FIO_ARY_COMPARE((*reader), FIO_ARY_INVALID)) { - *pos = *reader; - pos += 1; - } - reader += 1; - } - ary->end = (size_t)(pos - ary->arry); -} - -/* ***************************************************************************** -Array Testing -***************************************************************************** */ - -#if DEBUG -#include -#define TEST_LIMIT 1016 -/** - * Removes any FIO_ARY_TYPE_INVALID *pointers* from an Array, keeping all other - * data in the array. - * - * This action is O(n) where n in the length of the array. - * It could get expensive. - */ -FIO_FUNC inline void FIO_NAME(_test)(void) { - union { - FIO_ARY_TYPE obj; - uintptr_t i; - } mem; - FIO_NAME(s) ary = FIO_ARY_INIT; - fprintf(stderr, "=== Testing Core Array features for type " FIO_MACRO2STR( - FIO_ARY_TYPE) "\n"); - - for (uintptr_t i = 0; i < TEST_LIMIT; ++i) { - mem.i = i + 1; - FIO_NAME(push)(&ary, mem.obj); - } - fprintf(stderr, - "* Array populated using `push` with %zu items,\n" - " with capacity limit of %zu and start index %zu\n", - (size_t)FIO_NAME(count)(&ary), (size_t)FIO_NAME(capa)(&ary), - ary.start); - FIO_ASSERT(FIO_NAME(count)(&ary) == TEST_LIMIT, - "Wrong object count for array %zu", (size_t)FIO_NAME(count)(&ary)); - for (uintptr_t i = 0; i < TEST_LIMIT; ++i) { - FIO_ASSERT(!FIO_NAME(shift)(&ary, &mem.obj), "Array shift failed at %lu.", - i); - FIO_ASSERT(mem.i == i + 1, "Array shift value error %lu != %lu", mem.i, - i + 1); - FIO_ARY_DESTROY(mem.obj); - } - - FIO_NAME_FREE()(&ary); - FIO_ASSERT(!ary.arry, "Array not reset after fio_ary_free"); - - for (uintptr_t i = 0; i < TEST_LIMIT; ++i) { - mem.i = TEST_LIMIT - i; - FIO_NAME(unshift)(&ary, mem.obj); - } - fprintf(stderr, - "* Array populated using `unshift` with %zu items,\n" - " with capacity limit of %zu and start index %zu\n", - (size_t)FIO_NAME(count)(&ary), (size_t)FIO_NAME(capa)(&ary), - ary.start); - - FIO_ASSERT(FIO_NAME(count)(&ary) == TEST_LIMIT, - "Wrong object count for array %zu", (size_t)FIO_NAME(count)(&ary)); - for (uintptr_t i = 0; i < TEST_LIMIT; ++i) { - FIO_NAME(pop)(&ary, &mem.obj); - FIO_ASSERT(mem.i == TEST_LIMIT - i, "Array pop value error"); - FIO_ARY_DESTROY(mem.obj); - } - FIO_NAME_FREE()(&ary); - FIO_ASSERT(!ary.arry, "Array not reset after fio_ary_free"); - - for (uintptr_t i = 0; i < TEST_LIMIT; ++i) { - mem.i = TEST_LIMIT - i; - FIO_NAME(unshift)(&ary, mem.obj); - } - - for (size_t i = 0; i < TEST_LIMIT; ++i) { - mem.i = i + 1; - FIO_ASSERT(FIO_NAME(find)(&ary, mem.obj) == (intptr_t)i, - "Wrong object index - ary[%zd] != %zu", - (ssize_t)FIO_NAME(find)(&ary, mem.obj), (size_t)mem.i); - mem.obj = FIO_NAME(get)(&ary, i); - FIO_ASSERT(mem.i == (uintptr_t)(i + 1), - "Wrong object returned from fio_ary_index - ary[%zu] != %zu", i, - i + 1); - } - - FIO_ASSERT((FIO_NAME(count)(&ary) == TEST_LIMIT), - "Wrong object count before pop %zu", - (size_t)FIO_NAME(count)(&ary)); - FIO_ASSERT(!FIO_NAME(pop)(&ary, &mem.obj), "Couldn't pop element."); - FIO_ASSERT(mem.i == TEST_LIMIT, "Element value error (%zu).", (size_t)mem.i); - FIO_ASSERT((FIO_NAME(count)(&ary) == TEST_LIMIT - 1), - "Wrong object count after pop %zu", (size_t)FIO_NAME(count)(&ary)); - FIO_ARY_DESTROY(mem.obj); - - mem.i = (TEST_LIMIT >> 1); - FIO_ASSERT(!FIO_NAME(remove2)(&ary, mem.obj, NULL), - "Couldn't fio_ary_remove2 object from Array (%zu)", (size_t)mem.i); - FIO_ASSERT(FIO_NAME(count)(&ary) == TEST_LIMIT - 2, - "Wrong object count after remove2 %zu", - (size_t)FIO_NAME(count)(&ary)); - mem.i = (TEST_LIMIT >> 1) + 1; - FIO_ASSERT(FIO_NAME(find)(&ary, mem.obj) != (TEST_LIMIT >> 1) + 1, - "fio_ary_remove2 didn't clear holes from Array (%zu)", - (size_t)FIO_NAME(find)(&ary, mem.obj)); - FIO_ARY_DESTROY(mem.obj); - - FIO_ASSERT(!FIO_NAME(remove)(&ary, 0, &mem.obj), - "fio_ary_remove failed (at %zd)", (ssize_t)mem.i); - FIO_ASSERT(mem.i == 1, "Couldn't fio_ary_remove object from Array (%zd)", - (ssize_t)mem.i); - FIO_ASSERT(FIO_NAME(count)(&ary) == TEST_LIMIT - 3, - "Wrong object count after remove %zu != %d", - (size_t)FIO_NAME(count)(&ary), TEST_LIMIT - 3); - FIO_ASSERT(FIO_NAME(find)(&ary, mem.obj) == -1, - "fio_ary_find should have failed after fio_ary_remove (%zd)", - (ssize_t)FIO_NAME(find)(&ary, mem.obj)); - FIO_ARY_DESTROY(mem.obj); - - mem.i = 2; - FIO_ASSERT(FIO_NAME(find)(&ary, mem.obj) == 0, - "fio_ary_remove didn't clear holes from Array (%zu)", - (size_t)FIO_NAME(find)(&ary, mem.obj)); - - FIO_NAME_FREE()(&ary); - - FIO_NAME(s) ary2 = FIO_ARY_INIT; - for (uintptr_t i = 0; i < (TEST_LIMIT >> 1); ++i) { - mem.i = ((TEST_LIMIT >> 1) << 1) - i; - FIO_NAME(unshift)(&ary2, mem.obj); - mem.i = (TEST_LIMIT >> 1) - i; - FIO_NAME(unshift)(&ary, mem.obj); - } - FIO_NAME(concat)(&ary, &ary2); - FIO_NAME_FREE()(&ary2); - FIO_ASSERT(FIO_NAME(count)(&ary) == ((TEST_LIMIT >> 1) << 1), - "Wrong object count after fio_ary_concat %zu", - (size_t)FIO_NAME(count)(&ary)); - for (int i = 0; i < ((TEST_LIMIT >> 1) << 1); ++i) { - mem.obj = FIO_NAME(get)(&ary, i); - FIO_ASSERT( - mem.i == (uintptr_t)(i + 1), - "Wrong object returned from fio_ary_index after concat - ary[%d] != %d", - i, i + 1); - } - mem.i = 0; - while (FIO_NAME(pop)(&ary, &mem.obj)) { - ++mem.i; - FIO_ARY_DESTROY(mem.obj); - } - FIO_ASSERT(mem.i == ((TEST_LIMIT >> 1) << 1), "fio_ary_pop overflow (%zu)?", - (size_t)mem.i); - FIO_NAME_FREE()(&ary); -} -#undef TEST_LIMIT -#else -FIO_FUNC inline void FIO_NAME(_test)(void) {} -#endif - -/* ***************************************************************************** -Done -***************************************************************************** */ - -#undef FIO_NAME_FROM_MACRO_STEP2 -#undef FIO_NAME_FROM_MACRO_STEP1 -#undef FIO_NAME -#undef FIO_NAME_FROM_MACRO_STEP4 -#undef FIO_NAME_FROM_MACRO_STEP3 -#undef FIO_NAME_FREE -#undef FIO_ARY_NAME -#undef FIO_ARY_TYPE -#undef FIO_ARY_INVALID -#undef FIO_ARY_COMPARE -#undef FIO_ARY_COPY -#undef FIO_ARY_COPY_IS_SIMPLE -#undef FIO_ARY_DESTROY -#undef FIO_ARY_REALLOC -#undef FIO_ARY_DEALLOC -#undef FIO_ARY_SIZE2WORDS - -#endif - -/* ***************************************************************************** - - - - - - - - - - - - Set / Hash Map Data-Store - - - - - - - - - - - -***************************************************************************** */ - -#ifdef FIO_SET_NAME - -/** - * A simple ordered Set / Hash Map implementation, with a minimal API. - * - * A Set is basically a Hash Map where the keys are also the values, it's often - * used for caching objects. - * - * The Set's object type and behavior is controlled by the FIO_SET_OBJ_* marcos. - * - * A Hash Map is basically a set where the objects in the Set are key-value - * couplets and only the keys are tested when searching the Set. - * - * To create a Set or a Hash Map, the macro FIO_SET_NAME must be defined. i.e.: - * - * #define FIO_SET_NAME cstr_set - * #define FIO_SET_OBJ_TYPE char * - * #define FIO_SET_OBJ_COMPARE(k1, k2) (!strcmp((k1), (k2))) - * #include - * - * To create a Hash Map, rather than a pure Set, the macro FIO_SET_KEY_TYPE must - * be defined. i.e.: - * - * #define FIO_SET_KEY_TYPE char * - * - * This allows the FIO_SET_KEY_* macros to be defined as well. For example: - * - * #define FIO_SET_NAME cstr_hashmap - * #define FIO_SET_KEY_TYPE char * - * #define FIO_SET_KEY_COMPARE(k1, k2) (!strcmp((k1), (k2))) - * #define FIO_SET_OBJ_TYPE char * - * #include - * - * It's possible to create a number of Set or HasMap types by reincluding the - * fio.h header. i.e.: - * - * - * #define FIO_INCLUDE_STR - * #include // adds the fio_str_s types and functions - * - * #define FIO_SET_NAME fio_str_set - * #define FIO_SET_OBJ_TYPE fio_str_s * - * #define FIO_SET_OBJ_COMPARE(k1, k2) (fio_str_iseq((k1), (k2))) - * #define FIO_SET_OBJ_COPY(key) fio_str_dup((key)) - * #define FIO_SET_OBJ_DESTROY(key) fio_str_free2((key)) - * #include // creates the fio_str_set_s Set and functions - * - * #define FIO_SET_NAME fio_str_hash - * #define FIO_SET_KEY_TYPE fio_str_s * - * #define FIO_SET_KEY_COMPARE(k1, k2) (fio_str_iseq((k1), (k2))) - * #define FIO_SET_KEY_COPY(key) fio_str_dup((key)) - * #define FIO_SET_KEY_DESTROY(key) fio_str_free2((key)) - * #define FIO_SET_OBJ_TYPE fio_str_s * - * #define FIO_SET_OBJ_COMPARE(k1, k2) (fio_str_iseq((k1), (k2))) - * #define FIO_SET_OBJ_COPY(key) fio_str_dup((key)) - * #define FIO_SET_OBJ_DESTROY(key) fio_str_free2((key)) - * #include // creates the fio_str_hash_s Hash Map and functions - * - * The default integer Hash used is a pointer length type (uintptr_t). This can - * be changed by defining ALL of the following macros: - * * FIO_SET_HASH_TYPE - the type of the hash value. - * * FIO_SET_HASH2UINTPTR(hash, i) - converts the hash value to a uintptr_t. - * * FIO_SET_HASH_COMPARE(h1, h2) - compares two hash values (1 == equal). - * * FIO_SET_HASH_INVALID - an invalid Hash value, all bytes are 0. - * * FIO_SET_HASH_FORCE - an always valid Hash value, all bytes 0xFF - * - * - * Note: FIO_SET_HASH_TYPE should, normaly be left alone (uintptr_t is - * enough). Also, the hash value 0 is reserved to indicate an empty slot. - * - * Note: the FIO_SET_OBJ_COMPARE or the FIO_SET_KEY_COMPARE will be used to - * compare against invalid as well as valid objects. Invalid objects have - * their bytes all zero. FIO_SET_*_DESTROY should somehow mark them as - * invalid. - * - * Note: Before freeing the Set, FIO_SET_OBJ_DESTROY will be automatically - * called for every existing object. - */ - -/* Used for naming functions and types, prefixing FIO_SET_NAME to the name */ -#define FIO_NAME_FROM_MACRO_STEP2(name, postfix) name##_##postfix -#define FIO_NAME_FROM_MACRO_STEP1(name, postfix) \ - FIO_NAME_FROM_MACRO_STEP2(name, postfix) -#define FIO_NAME(postfix) FIO_NAME_FROM_MACRO_STEP1(FIO_SET_NAME, postfix) - -/* Used for naming the `free` function */ -#define FIO_NAME_FROM_MACRO_STEP4(name) name##_free -#define FIO_NAME_FROM_MACRO_STEP3(name) FIO_NAME_FROM_MACRO_STEP4(name) -#define FIO_NAME_FREE() FIO_NAME_FROM_MACRO_STEP3(FIO_SET_NAME) - -/* The default Set object / value type is `void *` */ -#if !defined(FIO_SET_OBJ_TYPE) -#define FIO_SET_OBJ_TYPE void * -#elif !defined(FIO_SET_NO_TEST) -#define FIO_SET_NO_TEST 1 -#endif - -/* The default Set has opaque objects that can't be compared */ -#if !defined(FIO_SET_OBJ_COMPARE) -#define FIO_SET_OBJ_COMPARE(o1, o2) (1) -#endif - -/** object copy required? */ -#ifndef FIO_SET_OBJ_COPY -#define FIO_SET_OBJ_COPY(dest, obj) ((dest) = (obj)) -#endif - -/** object destruction required? */ -#ifndef FIO_SET_OBJ_DESTROY -#define FIO_SET_OBJ_DESTROY(obj) ((void)0) -#endif - -/** test for a pre-defined hash type, must be numerical (i.e. __int128_t)*/ -#ifndef FIO_SET_HASH_TYPE -#define FIO_SET_HASH_TYPE uintptr_t -#endif - -/** test for a pre-defined hash to integer conversion */ -#ifndef FIO_SET_HASH2UINTPTR -#define FIO_SET_HASH2UINTPTR(hash, bits_used) \ - (fio_rrot(hash, bits_used) ^ fio_ct_if2(bits_used, hash, 0)) -#endif - -/** test for a pre-defined hash to integer conversion */ -#ifndef FIO_SET_HASH_FORCE -#define FIO_SET_HASH_FORCE (~(uintptr_t)0) -#endif - -/** test for a pre-defined invalid hash value (all bytes are 0) */ -#ifndef FIO_SET_HASH_INVALID -#define FIO_SET_HASH_INVALID ((FIO_SET_HASH_TYPE)0) -#endif - -/** test for a pre-defined hash comparison */ -#ifndef FIO_SET_HASH_COMPARE -#define FIO_SET_HASH_COMPARE(h1, h2) ((h1) == (h2)) -#endif - -/* Customizable memory management */ -#ifndef FIO_SET_REALLOC /* NULL ptr indicates new allocation */ -#define FIO_SET_REALLOC(ptr, original_size, new_size, valid_data_length) \ - FIO_REALLOC((ptr), (new_size), (valid_data_length)) -#endif - -#ifndef FIO_SET_CALLOC -#define FIO_SET_CALLOC(size, count) FIO_CALLOC((size), (count)) -#endif - -#ifndef FIO_SET_FREE -#define FIO_SET_FREE(ptr, size) FIO_FREE((ptr)) -#endif - -/* The maximum number of bins to rotate when (partial/full) collisions occure */ -#ifndef FIO_SET_MAX_MAP_SEEK -#define FIO_SET_MAX_MAP_SEEK (96) -#endif - -/* The maximum number of full hash collisions that can be consumed */ -#ifndef FIO_SET_MAX_MAP_FULL_COLLISIONS -#define FIO_SET_MAX_MAP_FULL_COLLISIONS (96) -#endif - -/* Prime numbers are better */ -#ifndef FIO_SET_CUCKOO_STEPS -#define FIO_SET_CUCKOO_STEPS 11 -#endif - -#ifdef FIO_SET_KEY_TYPE -typedef struct { - FIO_SET_KEY_TYPE key; - FIO_SET_OBJ_TYPE obj; -} FIO_NAME(couplet_s); - -#define FIO_SET_TYPE FIO_NAME(couplet_s) - -/** key copy required? */ -#ifndef FIO_SET_KEY_COPY -#define FIO_SET_KEY_COPY(dest, obj) ((dest) = (obj)) -#endif - -/** key destruction required? */ -#ifndef FIO_SET_KEY_DESTROY -#define FIO_SET_KEY_DESTROY(obj) ((void)0) -#endif - -/* The default Hash Map-Set has will use straight euqality operators */ -#ifndef FIO_SET_KEY_COMPARE -#define FIO_SET_KEY_COMPARE(o1, o2) ((o1) == (o2)) -#endif - -/** Internal macros for object actions in Hash mode */ -#define FIO_SET_COMPARE(o1, o2) FIO_SET_KEY_COMPARE((o1).key, (o2).key) -#define FIO_SET_COPY(dest, src) \ - do { \ - FIO_SET_OBJ_COPY((dest).obj, (src).obj); \ - FIO_SET_KEY_COPY((dest).key, (src).key); \ - } while (0); -#define FIO_SET_DESTROY(couplet) \ - do { \ - FIO_SET_KEY_DESTROY((couplet).key); \ - FIO_SET_OBJ_DESTROY((couplet).obj); \ - } while (0); - -#else /* a pure Set, not a Hash Map*/ -/** Internal macros for object actions in Set mode */ -#define FIO_SET_COMPARE(o1, o2) FIO_SET_OBJ_COMPARE((o1), (o2)) -#define FIO_SET_COPY(dest, obj) FIO_SET_OBJ_COPY((dest), (obj)) -#define FIO_SET_DESTROY(obj) FIO_SET_OBJ_DESTROY((obj)) -#define FIO_SET_TYPE FIO_SET_OBJ_TYPE -#endif - -/* ***************************************************************************** -Set / Hash Map API -***************************************************************************** */ - -/** The Set container type. By default: fio_ptr_set_s */ -typedef struct FIO_NAME(s) FIO_NAME(s); - -#ifndef FIO_SET_INIT -/** Initializes the set */ -#define FIO_SET_INIT \ - { .capa = 0 } -#endif - -/** Frees all the objects in the set and deallocates any internal resources. */ -FIO_FUNC void FIO_NAME_FREE()(FIO_NAME(s) * set); - -#ifdef FIO_SET_KEY_TYPE - -/** - * Locates an object in the Hash Map, if it exists. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC inline FIO_SET_OBJ_TYPE - FIO_NAME(find)(FIO_NAME(s) * set, const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key); - -/** - * Inserts an object to the Hash Map, rehashing if required, returning the new - * object's location using a pointer. - * - * If an object already exists in the Hash Map, it will be destroyed. - * - * If `old` is set, the existing object (if any) will be copied to the location - * pointed to by `old` before it is destroyed. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC inline void FIO_NAME(insert)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key, - FIO_SET_OBJ_TYPE obj, - FIO_SET_OBJ_TYPE *old); - -/** - * Removes an object from the Hash Map, rehashing if required. - * - * Returns 0 on success and -1 if the object wasn't found. - * - * If `old` is set, the existing object (if any) will be copied to the location - * pointed to by `old`. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC inline int FIO_NAME(remove)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key, - FIO_SET_OBJ_TYPE *old); - -#else - -/** - * Locates an object in the Set, if it exists. - * - * NOTE: This is the function's pure Set variant (no FIO_SET_KEY_TYPE). - */ -FIO_FUNC inline FIO_SET_OBJ_TYPE - FIO_NAME(find)(FIO_NAME(s) * set, const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj); - -/** - * Inserts an object to the Set only if it's missing, rehashing if required, - * returning the new (or old) object. - * - * If the object already exists in the set, than the new object will be - * destroyed and the old object will be returned. - * - * NOTE: This is the function's pure Set variant (no FIO_SET_KEY_TYPE). - */ -FIO_FUNC inline FIO_SET_OBJ_TYPE - FIO_NAME(insert)(FIO_NAME(s) * set, const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj); - -/** - * Inserts an object to the Set, rehashing if required, returning the new - * object. - * - * If the object already exists in the set, it will be destroyed and - * overwritten. - * - * When setting `old` to NULL, the function behaves the same as `overwrite`. - */ -FIO_FUNC FIO_SET_OBJ_TYPE - FIO_NAME(overwrite)(FIO_NAME(s) * set, const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj, FIO_SET_OBJ_TYPE *old); - -/** - * Removes an object from the Set, rehashing if required. - * - * Returns 0 on success and -1 if the object wasn't found. - * - * NOTE: This is the function's pure Set variant (no FIO_SET_KEY_TYPE). - */ -FIO_FUNC inline int FIO_NAME(remove)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj, - FIO_SET_OBJ_TYPE *old); - -#endif -/** - * Allows a peak at the Set's last element. - * - * Remember that objects might be destroyed if the Set is altered - * (`FIO_SET_OBJ_DESTROY` / `FIO_SET_KEY_DESTROY`). - */ -FIO_FUNC inline FIO_SET_TYPE FIO_NAME(last)(FIO_NAME(s) * set); - -/** - * Allows the Hash to be momentarily used as a stack, destroying the last - * object added (`FIO_SET_OBJ_DESTROY` / `FIO_SET_KEY_DESTROY`). - */ -FIO_FUNC inline void FIO_NAME(pop)(FIO_NAME(s) * set); - -/** Returns the number of object currently in the Set. */ -FIO_FUNC inline size_t FIO_NAME(count)(const FIO_NAME(s) * set); - -/** - * Returns a temporary theoretical Set capacity. - * This could be used for testing performance and memory consumption. - */ -FIO_FUNC inline size_t FIO_NAME(capa)(const FIO_NAME(s) * set); - -/** - * Requires that a Set contains the minimal requested theoretical capacity. - * - * Returns the actual (temporary) theoretical capacity. - */ -FIO_FUNC inline size_t FIO_NAME(capa_require)(FIO_NAME(s) * set, - size_t min_capa); - -/** - * Returns non-zero if the Set is fragmented (more than 50% holes). - */ -FIO_FUNC inline size_t FIO_NAME(is_fragmented)(const FIO_NAME(s) * set); - -/** - * Attempts to minimize memory usage by removing empty spaces caused by deleted - * items and rehashing the Set. - * - * Returns the updated Set capacity. - */ -FIO_FUNC inline size_t FIO_NAME(compact)(FIO_NAME(s) * set); - -/** Forces a rehashing of the Set. */ -FIO_FUNC void FIO_NAME(rehash)(FIO_NAME(s) * set); - -#ifndef FIO_SET_FOR_LOOP -/** - * A macro for a `for` loop that iterates over all the Set's objects (in - * order). - * - * `set` is a pointer to the Set variable and `pos` is a temporary variable - * name to be created for iteration. - * - * `pos->hash` is the hashing value and `pos->obj` is the object's data. - * - * NOTICE: Since the Set might have "holes" (objects that were removed), it is - * important to skip any `pos->hash == 0` or the equivalent of - * `FIO_SET_HASH_COMPARE(pos->hash, FIO_SET_HASH_INVALID)`. - */ -#define FIO_SET_FOR_LOOP(set, pos) -#endif - -/* ***************************************************************************** -Set / Hash Map Internal Data Structures -***************************************************************************** */ - -typedef struct FIO_NAME(_ordered_s_) { - FIO_SET_HASH_TYPE hash; - FIO_SET_TYPE obj; -} FIO_NAME(_ordered_s_); - -typedef struct FIO_NAME(_map_s_) { - FIO_SET_HASH_TYPE hash; /* another copy for memory cache locality */ - FIO_NAME(_ordered_s_) * pos; -} FIO_NAME(_map_s_); - -/* the information in the Hash Map structure should be considered READ ONLY. */ -struct FIO_NAME(s) { - uintptr_t count; - uintptr_t capa; - uintptr_t pos; - FIO_NAME(_ordered_s_) * ordered; - FIO_NAME(_map_s_) * map; - uint8_t has_collisions; - uint8_t used_bits; - uint8_t under_attack; -}; - -#undef FIO_SET_FOR_LOOP -#define FIO_SET_FOR_LOOP(set, container) \ - for (__typeof__((set)->ordered) container = (set)->ordered; \ - container && (container < ((set)->ordered + (set)->pos)); ++container) - -/* ***************************************************************************** -Set / Hash Map Internal Helpers -***************************************************************************** */ - -/** Locates an object's map position in the Set, if it exists. */ -FIO_FUNC inline FIO_NAME(_map_s_) * - FIO_NAME(_find_map_pos_)(FIO_NAME(s) * set, FIO_SET_HASH_TYPE hash_value, - FIO_SET_TYPE obj) { - if (FIO_SET_HASH_COMPARE(hash_value, FIO_SET_HASH_INVALID)) - hash_value = FIO_SET_HASH_FORCE; - if (set->map) { - /* make sure collisions don't effect seeking */ - if (set->has_collisions && set->pos != set->count) { - FIO_NAME(rehash)(set); - } - size_t full_collisions_counter = 0; - FIO_NAME(_map_s_) * pos; - /* - * Commonly, the hash is rotated, depending on it's state. - * Different bits are used for each mapping, instead of a single new bit. - */ - const uintptr_t mask = (1ULL << set->used_bits) - 1; - - uintptr_t i; - const uintptr_t hash_value_i = FIO_SET_HASH2UINTPTR(hash_value, 0); - uintptr_t hash_alt = FIO_SET_HASH2UINTPTR(hash_value, set->used_bits); - - /* O(1) access to object */ - pos = set->map + (hash_alt & mask); - if (FIO_SET_HASH_COMPARE(FIO_SET_HASH_INVALID, pos->hash)) - return pos; - if (FIO_SET_HASH_COMPARE(pos->hash, hash_value_i)) { - if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj))) - return pos; - /* full hash value collision detected */ - set->has_collisions = 1; - ++full_collisions_counter; - } - - /* Handle partial / full collisions with cuckoo steps O(x) access time */ - i = 0; - const uintptr_t limit = - FIO_SET_CUCKOO_STEPS * (set->capa > (FIO_SET_MAX_MAP_SEEK << 2) - ? FIO_SET_MAX_MAP_SEEK - : (set->capa >> 2)); - while (i < limit) { - i += FIO_SET_CUCKOO_STEPS; - pos = set->map + ((hash_alt + i) & mask); - if (FIO_SET_HASH_COMPARE(FIO_SET_HASH_INVALID, pos->hash)) - return pos; - if (FIO_SET_HASH_COMPARE(pos->hash, hash_value_i)) { - if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj))) - return pos; - /* full hash value collision detected */ - set->has_collisions = 1; - if (++full_collisions_counter >= FIO_SET_MAX_MAP_FULL_COLLISIONS) { - /* is the hash under attack? */ - FIO_LOG_WARNING( - "(fio hash map) too many full collisions - under attack?"); - set->under_attack = 1; - } - if (set->under_attack) { - return pos; - } - } - } - } - return NULL; - (void)obj; /* in cases where FIO_SET_OBJ_COMPARE does nothing */ -} -#undef FIO_SET_CUCKOO_STEPS - -/** Removes "holes" from the Set's internal Array - MUST re-hash afterwards. - */ -FIO_FUNC inline void FIO_NAME(_compact_ordered_array_)(FIO_NAME(s) * set) { - if (set->count == set->pos) - return; - FIO_NAME(_ordered_s_) *reader = set->ordered; - FIO_NAME(_ordered_s_) *writer = set->ordered; - const FIO_NAME(_ordered_s_) *end = set->ordered + set->pos; - for (; reader && (reader < end); ++reader) { - if (FIO_SET_HASH_COMPARE(reader->hash, FIO_SET_HASH_INVALID)) { - continue; - } - *writer = *reader; - ++writer; - } - /* fix any possible counting errors as well as resetting position */ - set->pos = set->count = (writer - set->ordered); -} - -/** (Re)allocates the set's internal, invalidatint the mapping (must rehash) */ -FIO_FUNC inline void FIO_NAME(_reallocate_set_mem_)(FIO_NAME(s) * set) { - const uintptr_t new_capa = 1ULL << set->used_bits; - FIO_SET_FREE(set->map, set->capa * sizeof(*set->map)); - set->map = (FIO_NAME(_map_s_) *)FIO_SET_CALLOC(sizeof(*set->map), new_capa); - set->ordered = (FIO_NAME(_ordered_s_) *)FIO_SET_REALLOC( - set->ordered, (set->capa * sizeof(*set->ordered)), - (new_capa * sizeof(*set->ordered)), (set->pos * sizeof(*set->ordered))); - if (!set->map || !set->ordered) { - perror("FATAL ERROR: couldn't allocate memory for Set data"); - exit(errno); - } - set->capa = new_capa; -} - -/** - * Inserts an object to the Set, rehashing if required, returning the new - * object's pointer. - * - * If the object already exists in the set, it will be destroyed and - * overwritten. - */ -FIO_FUNC inline FIO_SET_TYPE -FIO_NAME(_insert_or_overwrite_)(FIO_NAME(s) * set, FIO_SET_HASH_TYPE hash_value, - FIO_SET_TYPE obj, int overwrite, - FIO_SET_OBJ_TYPE *old) { - if (FIO_SET_HASH_COMPARE(hash_value, FIO_SET_HASH_INVALID)) - hash_value = FIO_SET_HASH_FORCE; - - /* automatic fragmentation protection */ - if (FIO_NAME(is_fragmented)(set)) - FIO_NAME(rehash)(set); - /* automatic capacity validation (we can never be at 100% capacity) */ - else if (set->pos >= set->capa) { - ++set->used_bits; - FIO_NAME(rehash)(set); - } - - /* locate future position */ - FIO_NAME(_map_s_) *pos = FIO_NAME(_find_map_pos_)(set, hash_value, obj); - - if (!pos) { - /* inserting a new object, with too many holes in the map */ - FIO_SET_COPY(set->ordered[set->pos].obj, obj); - set->ordered[set->pos].hash = hash_value; - ++set->pos; - ++set->count; - FIO_NAME(rehash)(set); - return set->ordered[set->pos - 1].obj; - } - - /* overwriting / new */ - if (pos->pos) { - /* overwrite existing object */ - if (!overwrite) { - FIO_SET_DESTROY(obj); - return pos->pos->obj; - } -#ifdef FIO_SET_KEY_TYPE - if (old) { - FIO_SET_OBJ_COPY((*old), pos->pos->obj.obj); - } - /* no need to recreate the key object, just the value object */ - FIO_SET_OBJ_DESTROY(pos->pos->obj.obj); - FIO_SET_OBJ_COPY(pos->pos->obj.obj, obj.obj); - return pos->pos->obj; -#else - if (old) { - FIO_SET_COPY((*old), pos->pos->obj); - } - FIO_SET_DESTROY(pos->pos->obj); -#endif - } else { - /* insert into new slot */ - pos->pos = set->ordered + set->pos; - ++set->pos; - ++set->count; - } - /* store object at position */ - pos->hash = hash_value; - pos->pos->hash = hash_value; - FIO_SET_COPY(pos->pos->obj, obj); - - return pos->pos->obj; -} - -/* ***************************************************************************** -Set / Hash Map Implementation -***************************************************************************** */ - -/** Frees all the objects in the set and deallocates any internal resources. */ -FIO_FUNC void FIO_NAME_FREE()(FIO_NAME(s) * s) { - /* destroy existing valid objects */ - const FIO_NAME(_ordered_s_) *const end = s->ordered + s->pos; - if (s->ordered && s->ordered != end) { - for (FIO_NAME(_ordered_s_) *pos = s->ordered; pos < end; ++pos) { - if (!FIO_SET_HASH_COMPARE(FIO_SET_HASH_INVALID, pos->hash)) { - FIO_SET_DESTROY(pos->obj); - } - } - } - /* free ordered array and hash mapping */ - FIO_SET_FREE(s->map, s->capa * sizeof(*s->map)); - FIO_SET_FREE(s->ordered, s->capa * sizeof(*s->ordered)); - *s = (FIO_NAME(s)){.map = NULL}; -} - -#ifdef FIO_SET_KEY_TYPE - -/* Hash Map unique implementation */ - -/** - * Locates an object in the Set, if it exists. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC FIO_SET_OBJ_TYPE FIO_NAME(find)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key) { - FIO_NAME(_map_s_) *pos = - FIO_NAME(_find_map_pos_)(set, hash_value, (FIO_SET_TYPE){.key = key}); - if (!pos || !pos->pos) { - FIO_SET_OBJ_TYPE empty; - memset(&empty, 0, sizeof(empty)); - return empty; - } - return pos->pos->obj.obj; -} - -/** - * Inserts an object to the Hash Map, rehashing if required, returning the new - * object's location using a pointer. - * - * If an object already exists in the Hash Map, it will be destroyed. - * - * If `old` is set, the existing object (if any) will be copied to the location - * pointed to by `old` before it is destroyed. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC void FIO_NAME(insert)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key, FIO_SET_OBJ_TYPE obj, - FIO_SET_OBJ_TYPE *old) { - FIO_NAME(_insert_or_overwrite_) - (set, hash_value, (FIO_SET_TYPE){.key = key, .obj = obj}, 1, old); -} - -/** - * Removes an object from the Hash Map, rehashing if required. - * - * Returns 0 on success and -1 if the object wasn't found. - * - * If `old` is set, the existing object (if any) will be copied to the location - * pointed to by `old`. - * - * NOTE: This is the function's Hash Map variant. See FIO_SET_KEY_TYPE. - */ -FIO_FUNC inline int FIO_NAME(remove)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_KEY_TYPE key, - FIO_SET_OBJ_TYPE *old) { - FIO_NAME(_map_s_) *pos = - FIO_NAME(_find_map_pos_)(set, hash_value, (FIO_SET_TYPE){.key = key}); - if (!pos || !pos->pos) - return -1; - if (old) - FIO_SET_OBJ_COPY((*old), pos->pos->obj.obj); - FIO_SET_DESTROY(pos->pos->obj); - --set->count; - pos->pos->hash = FIO_SET_HASH_INVALID; - if (pos->pos == set->pos + set->ordered - 1) { - /* removing last item inserted */ - pos->hash = FIO_SET_HASH_INVALID; /* no need for a "hole" */ - do { - --set->pos; - } while (set->pos && FIO_SET_HASH_COMPARE(set->ordered[set->pos - 1].hash, - FIO_SET_HASH_INVALID)); - } - pos->pos = NULL; /* leave pos->hash set to mark "hole" */ - return 0; -} - -#else /* FIO_SET_KEY_TYPE */ - -/* Set unique implementation */ - -/** Locates an object in the Set, if it exists. */ -FIO_FUNC FIO_SET_OBJ_TYPE FIO_NAME(find)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj) { - FIO_NAME(_map_s_) *pos = FIO_NAME(_find_map_pos_)(set, hash_value, obj); - if (!pos || !pos->pos) { - FIO_SET_OBJ_TYPE empty; - memset(&empty, 0, sizeof(empty)); - return empty; - } - return pos->pos->obj; -} - -/** - * Inserts an object to the Set, rehashing if required, returning the new - * object's pointer. - * - * If the object already exists in the set, than the new object will be - * destroyed and the old object's address will be returned. - */ -FIO_FUNC FIO_SET_OBJ_TYPE FIO_NAME(insert)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj) { - return FIO_NAME(_insert_or_overwrite_)(set, hash_value, obj, 0, NULL); -} - -/** - * Inserts an object to the Set, rehashing if required, returning the new - * object's pointer. - * - * If the object already exists in the set, it will be destroyed and - * overwritten. - * - * When setting `old` to NULL, the function behaves the same as `overwrite`. - */ -FIO_FUNC FIO_SET_OBJ_TYPE -FIO_NAME(overwrite)(FIO_NAME(s) * set, const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj, FIO_SET_OBJ_TYPE *old) { - return FIO_NAME(_insert_or_overwrite_)(set, hash_value, obj, 1, old); -} - -/** - * Removes an object from the Set, rehashing if required. - */ -FIO_FUNC int FIO_NAME(remove)(FIO_NAME(s) * set, - const FIO_SET_HASH_TYPE hash_value, - FIO_SET_OBJ_TYPE obj, FIO_SET_OBJ_TYPE *old) { - if (FIO_SET_HASH_COMPARE(hash_value, FIO_SET_HASH_INVALID)) - return -1; - FIO_NAME(_map_s_) *pos = FIO_NAME(_find_map_pos_)(set, hash_value, obj); - if (!pos || !pos->pos) - return -1; - if (old) - FIO_SET_COPY((*old), pos->pos->obj); - FIO_SET_DESTROY(pos->pos->obj); - --set->count; - pos->pos->hash = FIO_SET_HASH_INVALID; - if (pos->pos == set->pos + set->ordered - 1) { - /* removing last item inserted */ - pos->hash = FIO_SET_HASH_INVALID; /* no need for a "hole" */ - do { - --set->pos; - } while (set->pos && FIO_SET_HASH_COMPARE(set->ordered[set->pos - 1].hash, - FIO_SET_HASH_INVALID)); - } - pos->pos = NULL; /* leave pos->hash set to mark "hole" */ - return 0; -} - -#endif - -/** - * Allows a peak at the Set's last element. - * - * Remember that objects might be destroyed if the Set is altered - * (`FIO_SET_OBJ_DESTROY` / `FIO_SET_KEY_DESTROY`). - */ -FIO_FUNC inline FIO_SET_TYPE FIO_NAME(last)(FIO_NAME(s) * set) { - if (!set->ordered || !set->pos) { - FIO_SET_TYPE empty; - memset(&empty, 0, sizeof(empty)); - return empty; - } - return set->ordered[set->pos - 1].obj; -} - -/** - * Allows the Hash to be momentarily used as a stack, destroying the last - * object added (`FIO_SET_OBJ_DESTROY` / `FIO_SET_KEY_DESTROY`). - */ -FIO_FUNC void FIO_NAME(pop)(FIO_NAME(s) * set) { - if (!set->ordered || !set->pos) - return; - FIO_SET_DESTROY(set->ordered[set->pos - 1].obj); - set->ordered[set->pos - 1].hash = FIO_SET_HASH_INVALID; - --(set->count); - do { - --(set->pos); - } while (set->pos && FIO_SET_HASH_COMPARE(set->ordered[set->pos - 1].hash, - FIO_SET_HASH_INVALID)); -} - -/** Returns the number of objects currently in the Set. */ -FIO_FUNC inline size_t FIO_NAME(count)(const FIO_NAME(s) * set) { - return (size_t)set->count; -} - -/** - * Returns a temporary theoretical Set capacity. - * This could be used for testing performance and memory consumption. - */ -FIO_FUNC inline size_t FIO_NAME(capa)(const FIO_NAME(s) * set) { - return (size_t)set->capa; -} - -/** - * Requires that a Set contains the minimal requested theoretical capacity. - * - * Returns the actual (temporary) theoretical capacity. - */ -FIO_FUNC inline size_t FIO_NAME(capa_require)(FIO_NAME(s) * set, - size_t min_capa) { - if (min_capa <= FIO_NAME(capa)(set)) - return FIO_NAME(capa)(set); - set->used_bits = 2; - while (min_capa > (1ULL << set->used_bits)) { - ++set->used_bits; - } - FIO_NAME(rehash)(set); - return FIO_NAME(capa)(set); -} - -/** - * Returns non-zero if the Set is fragmented (more than 50% holes). - */ -FIO_FUNC inline size_t FIO_NAME(is_fragmented)(const FIO_NAME(s) * set) { - return ((set->pos - set->count) > (set->count >> 1)); -} - -/** - * Attempts to minimize memory usage by removing empty spaces caused by deleted - * items and rehashing the Set. - * - * Returns the updated Set capacity. - */ -FIO_FUNC inline size_t FIO_NAME(compact)(FIO_NAME(s) * set) { - FIO_NAME(_compact_ordered_array_)(set); - set->used_bits = 2; - while (set->count >= (1ULL << set->used_bits)) { - ++set->used_bits; - } - FIO_NAME(rehash)(set); - return FIO_NAME(capa)(set); -} - -/** Forces a rehashing of the Set. */ -FIO_FUNC void FIO_NAME(rehash)(FIO_NAME(s) * set) { - FIO_NAME(_compact_ordered_array_)(set); - set->has_collisions = 0; - uint8_t attempts = 0; -restart: - if (set->used_bits >= 16 && ++attempts >= 3 && set->has_collisions) { - FIO_LOG_FATAL( - "facil.io Set / Hash Map has too many collisions (%zu/%zu)." - "\n\t\tthis is a fatal implementation error," - "please report this issue at facil.io's open source project" - "\n\t\tNote: hash maps and sets should never reach this point." - "\n\t\tThey should be guarded against collision attacks.", - set->pos, set->capa); - exit(-1); - } - FIO_NAME(_reallocate_set_mem_)(set); - { - FIO_NAME(_ordered_s_) const *const end = set->ordered + set->pos; - for (FIO_NAME(_ordered_s_) *pos = set->ordered; pos < end; ++pos) { - FIO_NAME(_map_s_) *mp = - FIO_NAME(_find_map_pos_)(set, pos->hash, pos->obj); - if (!mp) { - ++set->used_bits; - goto restart; - } - mp->pos = pos; - mp->hash = pos->hash; - } - } -} - -#undef FIO_SET_OBJ_TYPE -#undef FIO_SET_OBJ_COMPARE -#undef FIO_SET_OBJ_COPY -#undef FIO_SET_OBJ_DESTROY -#undef FIO_SET_HASH_TYPE -#undef FIO_SET_HASH2UINTPTR -#undef FIO_SET_HASH_COMPARE -#undef FIO_SET_HASH_INVALID -#undef FIO_SET_KEY_TYPE -#undef FIO_SET_KEY_COPY -#undef FIO_SET_KEY_DESTROY -#undef FIO_SET_KEY_COMPARE -#undef FIO_SET_TYPE -#undef FIO_SET_COMPARE -#undef FIO_SET_COPY -#undef FIO_SET_DESTROY -#undef FIO_SET_MAX_MAP_SEEK -#undef FIO_SET_MAX_MAP_FULL_COLLISIONS -#undef FIO_SET_REALLOC -#undef FIO_SET_CALLOC -#undef FIO_SET_FREE -#undef FIO_NAME -#undef FIO_NAME_FROM_MACRO_STEP2 -#undef FIO_NAME_FROM_MACRO_STEP1 -#undef FIO_NAME_FROM_MACRO_STEP4 -#undef FIO_NAME_FROM_MACRO_STEP3 -#undef FIO_NAME_FREE -#undef FIO_SET_NAME -#undef FIO_FORCE_MALLOC_TMP - -#endif diff --git a/ext/iodine/fio_cli.c b/ext/iodine/fio_cli.c deleted file mode 100644 index 372aec3c..00000000 --- a/ext/iodine/fio_cli.c +++ /dev/null @@ -1,431 +0,0 @@ -/* -Copyright: Boaz segev, 2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include - -#include - -#include -#include -#include -#include -#include - -/* ***************************************************************************** -CLI Data Stores -***************************************************************************** */ - -typedef struct { - size_t len; - const char *data; -} cstr_s; - -#define FIO_SET_OBJ_TYPE const char * -#define FIO_SET_KEY_TYPE cstr_s -#define FIO_SET_KEY_COMPARE(o1, o2) \ - (o1.len == o2.len && \ - (o1.data == o2.data || !memcmp(o1.data, o2.data, o1.len))) -#define FIO_SET_NAME fio_cli_hash -#include - -static fio_cli_hash_s fio_aliases = FIO_SET_INIT; -static fio_cli_hash_s fio_values = FIO_SET_INIT; -static size_t fio_unnamed_count = 0; - -typedef struct { - int unnamed_min; - int unnamed_max; - int pos; - int unnamed_count; - int argc; - char const **argv; - char const *description; - char const **names; -} fio_cli_parser_data_s; - -/** this will allow the function definition fio_cli_start to avoid the MACRO */ -#define AVOID_MACRO - -#define FIO_CLI_HASH_VAL(s) \ - fio_risky_hash((s).data, (s).len, (uintptr_t)fio_cli_start) - -/* ***************************************************************************** -CLI Parsing -***************************************************************************** */ - -/* ***************************************************************************** -CLI Parsing -***************************************************************************** */ - -static void fio_cli_map_line2alias(char const *line) { - cstr_s n = {.data = line}; - while (n.data[0] == '-') { - while (n.data[n.len] && n.data[n.len] != ' ' && n.data[n.len] != ',') { - ++n.len; - } - const char *old = NULL; - fio_cli_hash_insert(&fio_aliases, FIO_CLI_HASH_VAL(n), n, (void *)line, - &old); - if (old) { - FIO_LOG_WARNING("CLI argument name conflict detected\n" - " The following two directives conflict:\n" - "\t%s\n\t%s\n", - old, line); - } - - while (n.data[n.len] && (n.data[n.len] == ' ' || n.data[n.len] == ',')) { - ++n.len; - } - n.data += n.len; - n.len = 0; - } -} - -static char const *fio_cli_get_line_type(fio_cli_parser_data_s *parser, - const char *line) { - if (!line) { - return NULL; - } - char const **pos = parser->names; - while (*pos) { - switch ((intptr_t)*pos) { - case FIO_CLI_STRING__TYPE_I: /* fallthrough */ - case FIO_CLI_BOOL__TYPE_I: /* fallthrough */ - case FIO_CLI_INT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT_HEADER__TYPE_I: /* fallthrough */ - ++pos; - continue; - } - if (line == *pos) { - goto found; - } - ++pos; - } - return NULL; -found: - switch ((size_t)pos[1]) { - case FIO_CLI_STRING__TYPE_I: /* fallthrough */ - case FIO_CLI_BOOL__TYPE_I: /* fallthrough */ - case FIO_CLI_INT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT_HEADER__TYPE_I: /* fallthrough */ - return pos[1]; - } - return NULL; -} - -static void fio_cli_set_arg(cstr_s arg, char const *value, char const *line, - fio_cli_parser_data_s *parser) { - /* handle unnamed argument */ - if (!line || !arg.len) { - if (!value) { - goto print_help; - } - if (!strcmp(value, "-?") || !strcasecmp(value, "-h") || - !strcasecmp(value, "-help") || !strcasecmp(value, "--help")) { - goto print_help; - } - cstr_s n = {.len = ++parser->unnamed_count}; - fio_cli_hash_insert(&fio_values, n.len, n, value, NULL); - if (parser->unnamed_max >= 0 && - parser->unnamed_count > parser->unnamed_max) { - arg.len = 0; - goto error; - } - return; - } - - /* validate data types */ - char const *type = fio_cli_get_line_type(parser, line); - switch ((size_t)type) { - case FIO_CLI_BOOL__TYPE_I: - if (value && value != parser->argv[parser->pos + 1]) { - goto error; - } - value = "1"; - break; - case FIO_CLI_INT__TYPE_I: - if (value) { - char const *tmp = value; - fio_atol((char **)&tmp); - if (*tmp) { - goto error; - } - } - /* fallthrough */ - case FIO_CLI_STRING__TYPE_I: - if (!value) - goto error; - if (!value[0]) - goto finish; - break; - } - - /* add values using all aliases possible */ - { - cstr_s n = {.data = line}; - while (n.data[0] == '-') { - while (n.data[n.len] && n.data[n.len] != ' ' && n.data[n.len] != ',') { - ++n.len; - } - fio_cli_hash_insert(&fio_values, FIO_CLI_HASH_VAL(n), n, value, NULL); - while (n.data[n.len] && (n.data[n.len] == ' ' || n.data[n.len] == ',')) { - ++n.len; - } - n.data += n.len; - n.len = 0; - } - } - -finish: - - /* handle additional argv progress (if value is on separate argv) */ - if (value && parser->pos < parser->argc && - value == parser->argv[parser->pos + 1]) - ++parser->pos; - return; - -error: /* handle errors*/ - /* TODO! */ - fprintf(stderr, "\n\r\x1B[31mError:\x1B[0m unknown argument %.*s %s %s\n\n", - (int)arg.len, arg.data, arg.len ? "with value" : "", - value ? (value[0] ? value : "(empty)") : "(null)"); -print_help: - fprintf(stderr, "\n%s\n", - parser->description ? parser->description - : "This application accepts any of the following " - "possible arguments:"); - /* print out each line's arguments */ - char const **pos = parser->names; - while (*pos) { - switch ((intptr_t)*pos) { - case FIO_CLI_STRING__TYPE_I: /* fallthrough */ - case FIO_CLI_BOOL__TYPE_I: /* fallthrough */ - case FIO_CLI_INT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT_HEADER__TYPE_I: - ++pos; - continue; - } - type = (char *)FIO_CLI_STRING__TYPE_I; - switch ((intptr_t)pos[1]) { - case FIO_CLI_PRINT__TYPE_I: - fprintf(stderr, "%s\n", pos[0]); - pos += 2; - continue; - case FIO_CLI_PRINT_HEADER__TYPE_I: - fprintf(stderr, "\n\x1B[4m%s\x1B[0m\n", pos[0]); - pos += 2; - continue; - - case FIO_CLI_STRING__TYPE_I: /* fallthrough */ - case FIO_CLI_BOOL__TYPE_I: /* fallthrough */ - case FIO_CLI_INT__TYPE_I: /* fallthrough */ - type = pos[1]; - } - /* print line @ pos, starting with main argument name */ - int alias_count = 0; - int first_len = 0; - size_t tmp = 0; - char const *const p = *pos; - while (p[tmp] == '-') { - while (p[tmp] && p[tmp] != ' ' && p[tmp] != ',') { - if (!alias_count) - ++first_len; - ++tmp; - } - ++alias_count; - while (p[tmp] && (p[tmp] == ' ' || p[tmp] == ',')) { - ++tmp; - } - } - switch ((size_t)type) { - case FIO_CLI_STRING__TYPE_I: - fprintf(stderr, " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m\t%s\n", first_len, - p, p + tmp); - break; - case FIO_CLI_BOOL__TYPE_I: - fprintf(stderr, " \x1B[1m%.*s\x1B[0m \t%s\n", first_len, p, p + tmp); - break; - case FIO_CLI_INT__TYPE_I: - fprintf(stderr, " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m\t%s\n", first_len, - p, p + tmp); - break; - } - /* print aliase information */ - tmp = first_len; - while (p[tmp] && (p[tmp] == ' ' || p[tmp] == ',')) { - ++tmp; - } - while (p[tmp] == '-') { - const size_t start = tmp; - while (p[tmp] && p[tmp] != ' ' && p[tmp] != ',') { - ++tmp; - } - int padding = first_len - (tmp - start); - if (padding < 0) - padding = 0; - switch ((size_t)type) { - case FIO_CLI_STRING__TYPE_I: - fprintf(stderr, - " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m%*s\t(same as " - "\x1B[1m%.*s\x1B[0m)\n", - (int)(tmp - start), p + start, padding, "", first_len, p); - break; - case FIO_CLI_BOOL__TYPE_I: - fprintf(stderr, - " \x1B[1m%.*s\x1B[0m %*s\t(same as \x1B[1m%.*s\x1B[0m)\n", - (int)(tmp - start), p + start, padding, "", first_len, p); - break; - case FIO_CLI_INT__TYPE_I: - fprintf(stderr, - " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m%*s\t(same as " - "\x1B[1m%.*s\x1B[0m)\n", - (int)(tmp - start), p + start, padding, "", first_len, p); - break; - } - } - - ++pos; - } - fprintf(stderr, "\nUse any of the following input formats:\n" - "\t-arg \t-arg=\t-arg\n" - "\n" - "Use the -h, -help or -? to get this information again.\n" - "\n"); - fio_cli_end(); - exit(0); -} - -static void fio_cli_end_promise(void *ignr_) { - /* make sure fio_cli_end is called before facil.io exists. */ - fio_cli_end(); - (void)ignr_; -} - -void fio_cli_start AVOID_MACRO(int argc, char const *argv[], int unnamed_min, - int unnamed_max, char const *description, - char const **names) { - static fio_lock_i run_once = FIO_LOCK_INIT; - if (!fio_trylock(&run_once)) - fio_state_callback_add(FIO_CALL_AT_EXIT, fio_cli_end_promise, NULL); - if (unnamed_max >= 0 && unnamed_max < unnamed_min) - unnamed_max = unnamed_min; - fio_cli_parser_data_s parser = { - .unnamed_min = unnamed_min, - .unnamed_max = unnamed_max, - .description = description, - .argc = argc, - .argv = argv, - .names = names, - .pos = 0, - }; - - if (fio_cli_hash_count(&fio_values)) { - fio_cli_end(); - } - - /* prepare aliases hash map */ - - char const **line = names; - while (*line) { - switch ((intptr_t)*line) { - case FIO_CLI_STRING__TYPE_I: /* fallthrough */ - case FIO_CLI_BOOL__TYPE_I: /* fallthrough */ - case FIO_CLI_INT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT__TYPE_I: /* fallthrough */ - case FIO_CLI_PRINT_HEADER__TYPE_I: /* fallthrough */ - ++line; - continue; - } - if (line[1] != (char *)FIO_CLI_PRINT__TYPE_I && - line[1] != (char *)FIO_CLI_PRINT_HEADER__TYPE_I) - fio_cli_map_line2alias(*line); - ++line; - } - - /* parse existing arguments */ - - while ((++parser.pos) < argc) { - char const *value = NULL; - cstr_s n = {.data = argv[parser.pos], .len = strlen(argv[parser.pos])}; - if (parser.pos + 1 < argc) { - value = argv[parser.pos + 1]; - } - const char *l = NULL; - while (n.len && - !(l = fio_cli_hash_find(&fio_aliases, FIO_CLI_HASH_VAL(n), n))) { - --n.len; - value = n.data + n.len; - } - if (n.len && value && value[0] == '=') { - ++value; - } - // fprintf(stderr, "Setting %.*s to %s\n", (int)n.len, n.data, value); - fio_cli_set_arg(n, value, l, &parser); - } - - /* Cleanup and save state for API */ - fio_cli_hash_free(&fio_aliases); - fio_unnamed_count = parser.unnamed_count; - /* test for required unnamed arguments */ - if (parser.unnamed_count < parser.unnamed_min) - fio_cli_set_arg((cstr_s){.len = 0}, NULL, NULL, &parser); -} - -void fio_cli_end(void) { - fio_cli_hash_free(&fio_values); - fio_cli_hash_free(&fio_aliases); - fio_unnamed_count = 0; -} -/* ***************************************************************************** -CLI Data Access -***************************************************************************** */ - -/** Returns the argument's value as a NUL terminated C String. */ -char const *fio_cli_get(char const *name) { - cstr_s n = {.data = name, .len = strlen(name)}; - if (!fio_cli_hash_count(&fio_values)) { - return NULL; - } - char const *val = fio_cli_hash_find(&fio_values, FIO_CLI_HASH_VAL(n), n); - return val; -} - -/** Returns the argument's value as an integer. */ -int fio_cli_get_i(char const *name) { - char const *val = fio_cli_get(name); - if (!val) - return 0; - int i = (int)fio_atol((char **)&val); - return i; -} - -/** Returns the number of unrecognized argument. */ -unsigned int fio_cli_unnamed_count(void) { - return (unsigned int)fio_unnamed_count; -} - -/** Returns the unrecognized argument using a 0 based `index`. */ -char const *fio_cli_unnamed(unsigned int index) { - if (!fio_cli_hash_count(&fio_values) || !fio_unnamed_count) { - return NULL; - } - cstr_s n = {.data = NULL, .len = index + 1}; - return fio_cli_hash_find(&fio_values, index + 1, n); -} - -/** - * Sets the argument's value as a NUL terminated C String (no copy!). - * - * Note: this does NOT copy the C strings to memory. Memory should be kept - * alive until `fio_cli_end` is called. - */ -void fio_cli_set(char const *name, char const *value) { - cstr_s n = (cstr_s){.data = name, .len = strlen(name)}; - fio_cli_hash_insert(&fio_values, FIO_CLI_HASH_VAL(n), n, value, NULL); -} diff --git a/ext/iodine/fio_cli.h b/ext/iodine/fio_cli.h deleted file mode 100644 index defd8c37..00000000 --- a/ext/iodine/fio_cli.h +++ /dev/null @@ -1,189 +0,0 @@ -/* -Copyright: Boaz segev, 2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_FIO_CLI_HELPER_H -#define H_FIO_CLI_HELPER_H - -/* support C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -CLI API -***************************************************************************** */ - -/** Indicates the CLI argument should be a String (default). */ -#define FIO_CLI_STRING(line) -/** Indicates the CLI argument is a Boolean value. */ -#define FIO_CLI_BOOL(line) -/** Indicates the CLI argument should be an Integer (numerical). */ -#define FIO_CLI_INT(line) -/** Indicates the CLI string should be printed as is. */ -#define FIO_CLI_PRINT(line) -/** Indicates the CLI string should be printed as a header. */ -#define FIO_CLI_PRINT_HEADER(line) - -/** - * This function parses the Command Line Interface (CLI), creating a temporary - * "dictionary" that allows easy access to the CLI using their names or aliases. - * - * Command line arguments may be typed. If an optional type requirement is - * provided and the provided arument fails to match the required type, execution - * will end and an error message will be printed along with a short "help". - * - * The function / macro accepts the following arguments: - * - `argc`: command line argument count. - * - `argv`: command line argument list (array). - * - `unnamed_min`: the required minimum of un-named arguments. - * - `unnamed_max`: the maximum limit of un-named arguments. - * - `description`: a C string containing the program's description. - * - named arguments list: a list of C strings describing named arguments. - * - * The following optional type requirements are: - * - * * FIO_CLI_STRING(desc_line) - (default) string argument. - * * FIO_CLI_BOOL(desc_line) - boolean argument (no value). - * * FIO_CLI_INT(desc_line) - integer argument. - * * FIO_CLI_PRINT_HEADER(desc_line) - extra header for output. - * * FIO_CLI_PRINT(desc_line) - extra information for output. - * - * Argument names MUST start with the '-' character. The first word starting - * without the '-' character will begin the description for the CLI argument. - * - * The arguments "-?", "-h", "-help" and "--help" are automatically handled - * unless overridden. - * - * Un-named arguments shouldn't be listed in the named arguments list. - * - * Example use: - * - * fio_cli_start(argc, argv, 0, 0, "this example accepts the following:", - * FIO_CLI_PRINT_HREADER("Concurrency:"), - * FIO_CLI_INT("-t -thread number of threads to run."), - * FIO_CLI_INT("-w -workers number of workers to run."), - * FIO_CLI_PRINT_HREADER("Address Binding:"), - * "-b, -address the address to bind to.", - * FIO_CLI_INT("-p,-port the port to bind to."), - * FIO_CLI_PRINT("\t\tset port to zero (0) for Unix s."), - * FIO_CLI_PRINT_HREADER("Logging:"), - * FIO_CLI_BOOL("-v -log enable logging.")); - * - * - * This would allow access to the named arguments: - * - * fio_cli_get("-b") == fio_cli_get("-address"); - * - * - * Once all the data was accessed, free the parsed data dictionary using: - * - * fio_cli_end(); - * - * It should be noted, arguments will be recognized in a number of forms, i.e.: - * - * app -t=1 -p3000 -a localhost - * - * This function is NOT thread safe. - */ -#define fio_cli_start(argc, argv, unnamed_min, unnamed_max, description, ...) \ - fio_cli_start((argc), (argv), (unnamed_min), (unnamed_max), (description), \ - (char const *[]){__VA_ARGS__, NULL}) -#define FIO_CLI_IGNORE -/** - * Never use the function directly, always use the MACRO, because the macro - * attaches a NULL marker at the end of the `names` argument collection. - */ -void fio_cli_start FIO_CLI_IGNORE(int argc, char const *argv[], int unnamed_min, - int unnamed_max, char const *description, - char const **names); -/** - * Clears the memory used by the CLI dictionary, removing all parsed data. - * - * This function is NOT thread safe. - */ -void fio_cli_end(void); - -/** Returns the argument's value as a NUL terminated C String. */ -char const *fio_cli_get(char const *name); - -/** Returns the argument's value as an integer. */ -int fio_cli_get_i(char const *name); - -/** This MACRO returns the argument's value as a boolean. */ -#define fio_cli_get_bool(name) (fio_cli_get((name)) != NULL) - -/** Returns the number of unnamed argument. */ -unsigned int fio_cli_unnamed_count(void); - -/** Returns the unnamed argument using a 0 based `index`. */ -char const *fio_cli_unnamed(unsigned int index); - -/** - * Sets the argument's value as a NUL terminated C String (no copy!). - * - * CAREFUL: This does not automatically detect aliases or type violations! it - * will only effect the specific name given, even if invalid. i.e.: - * - * fio_cli_start(argc, argv, - * "this is example accepts the following options:", - * "-p -port the port to bind to", FIO_CLI_INT; - * - * fio_cli_set("-p", "hello"); // fio_cli_get("-p") != fio_cli_get("-port"); - * - * Note: this does NOT copy the C strings to memory. Memory should be kept alive - * until `fio_cli_end` is called. - * - * This function is NOT thread safe. - */ -void fio_cli_set(char const *name, char const *value); - -/** - * This MACRO is the same as: - * - * if(!fio_cli_get(name)) { - * fio_cli_set(name, value) - * } - * - * See fio_cli_set for notes and restrictions. - */ -#define fio_cli_set_default(name, value) \ - if (!fio_cli_get((name))) \ - fio_cli_set(name, value); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -/* ***************************************************************************** -Internal Macro Implementation -***************************************************************************** */ - -/** Used internally. */ -#define FIO_CLI_STRING__TYPE_I 0x1 -#define FIO_CLI_BOOL__TYPE_I 0x2 -#define FIO_CLI_INT__TYPE_I 0x3 -#define FIO_CLI_PRINT__TYPE_I 0x4 -#define FIO_CLI_PRINT_HEADER__TYPE_I 0x5 - -#undef FIO_CLI_STRING -#undef FIO_CLI_BOOL -#undef FIO_CLI_INT -#undef FIO_CLI_PRINT -#undef FIO_CLI_PRINT_HEADER - -/** Indicates the CLI argument should be a String (default). */ -#define FIO_CLI_STRING(line) (line), ((char *)FIO_CLI_STRING__TYPE_I) -/** Indicates the CLI argument is a Boolean value. */ -#define FIO_CLI_BOOL(line) (line), ((char *)FIO_CLI_BOOL__TYPE_I) -/** Indicates the CLI argument should be an Integer (numerical). */ -#define FIO_CLI_INT(line) (line), ((char *)FIO_CLI_INT__TYPE_I) -/** Indicates the CLI string should be printed as is. */ -#define FIO_CLI_PRINT(line) (line), ((char *)FIO_CLI_PRINT__TYPE_I) -/** Indicates the CLI string should be printed as a header. */ -#define FIO_CLI_PRINT_HEADER(line) \ - (line), ((char *)FIO_CLI_PRINT_HEADER__TYPE_I) - -#endif diff --git a/ext/iodine/fio_json_parser.h b/ext/iodine/fio_json_parser.h deleted file mode 100644 index 016185e8..00000000 --- a/ext/iodine/fio_json_parser.h +++ /dev/null @@ -1,687 +0,0 @@ -#ifndef H_FIO_JSON_H -/* ***************************************************************************** - * Copyright: Boaz Segev, 2017-2019 - * License: MIT - * - * This header file is a single-file JSON naive parse. - * - * The code was extracted form the FIOBJ implementation in order to allow the - * parser to be used independantly from the rest of the facil.io library. - * - * The parser ignores missing commas and other formatting errors when possible. - * - * The parser also extends the JSON format to allow for C and Bash style - * comments as well as hex numerical formats. - ***************************************************************************** - */ -#define H_FIO_JSON_H - -#include -#include -#include -#include -#include - -#if DEBUG -#include -#endif - -#if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS) -#define __attribute__(...) -#define __has_include(...) 0 -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#elif !defined(__clang__) && !defined(__has_builtin) -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#endif - -/* ***************************************************************************** -JSON API -***************************************************************************** */ - -/* maximum allowed depth values max out at 32, since a bitmap is used */ -#if !defined(JSON_MAX_DEPTH) || JSON_MAX_DEPTH > 32 -#undef JSON_MAX_DEPTH -#define JSON_MAX_DEPTH 32 -#endif - -/** The JSON parser type. Memory must be initialized to 0 before first uses. */ -typedef struct { - /** in dictionary flag. */ - uint32_t dict; - /** level of nesting. */ - uint8_t depth; - /** in dictionary waiting for key. */ - uint8_t key; -} json_parser_s; - -/** - * Stream parsing of JSON data using a persistent parser. - * - * Returns the number of bytes consumed (0 being a valid value). - * - * Unconsumed data should be resent to the parser once more data is available. - * - * For security (due to numeral parsing concerns), a NUL byte should be placed - * at `buffer[length]`. - */ -static size_t __attribute__((unused)) -fio_json_parse(json_parser_s *parser, const char *buffer, size_t length); - -/** - * This function allows JSON formatted strings to be converted to native - * strings. - */ -static size_t __attribute__((unused)) -fio_json_unescape_str(void *dest, const char *source, size_t length); - -/* ***************************************************************************** -JSON Callacks - these must be implemented in the C file that uses the parser -***************************************************************************** */ - -/** a NULL object was detected */ -static void fio_json_on_null(json_parser_s *p); -/** a TRUE object was detected */ -static void fio_json_on_true(json_parser_s *p); -/** a FALSE object was detected */ -static void fio_json_on_false(json_parser_s *p); -/** a Numberl was detected (long long). */ -static void fio_json_on_number(json_parser_s *p, long long i); -/** a Float was detected (double). */ -static void fio_json_on_float(json_parser_s *p, double f); -/** a String was detected (int / float). update `pos` to point at ending */ -static void fio_json_on_string(json_parser_s *p, void *start, size_t length); -/** a dictionary object was detected, should return 0 unless error occurred. */ -static int fio_json_on_start_object(json_parser_s *p); -/** a dictionary object closure detected */ -static void fio_json_on_end_object(json_parser_s *p); -/** an array object was detected, should return 0 unless error occurred. */ -static int fio_json_on_start_array(json_parser_s *p); -/** an array closure was detected */ -static void fio_json_on_end_array(json_parser_s *p); -/** the JSON parsing is complete */ -static void fio_json_on_json(json_parser_s *p); -/** the JSON parsing is complete */ -static void fio_json_on_error(json_parser_s *p); - -/* ***************************************************************************** -JSON maps (arrays used to map data to simplify `if` statements) -***************************************************************************** */ - -/* -Marks as object seperators any of the following: - -* White Space: [0x09, 0x0A, 0x0D, 0x20] -* Comma ("," / 0x2C) -* NOT Colon (":" / 0x3A) -* == [0x09, 0x0A, 0x0D, 0x20, 0x2C] -The rest belong to objects, -*/ -static const uint8_t JSON_SEPERATOR[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -/* -Marks a numeral valid char (it's a permisive list): -['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'e', 'E', '+', '-', 'x', 'b', -'.'] -*/ -static const uint8_t JSON_NUMERAL[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -static const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - -static const uint8_t is_hex[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, - 0, 0, 0, 0, 0, 11, 12, 13, 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 13, - 14, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -/* -Stops seeking a String: -['\\', '"'] -*/ -static const uint8_t string_seek_stop[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -/* ***************************************************************************** -JSON String Helper - Seeking to the end of a string -***************************************************************************** */ - -/** - * finds the first occurance of either '"' or '\\'. - */ -static inline int seek2marker(uint8_t **buffer, - register const uint8_t *const limit) { - if (string_seek_stop[**buffer]) - return 1; - -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || (!__x86_64__ && !__aarch64__) - /* too short for this mess */ - if ((uintptr_t)limit <= 8 + ((uintptr_t)*buffer & (~(uintptr_t)7))) - goto finish; - /* align memory */ - if (1) { - { - const uint8_t *alignment = - (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8); - if (limit >= alignment) { - while (*buffer < alignment) { - if (string_seek_stop[**buffer]) - return 1; - *buffer += 1; - } - } - } - const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7)); -#else - const uint8_t *limit64 = (uint8_t *)limit - 7; -#endif - uint64_t wanted1 = 0x0101010101010101ULL * '"'; - uint64_t wanted2 = 0x0101010101010101ULL * '\\'; - for (; *buffer < limit64; *buffer += 8) { - const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1); - const uint64_t t1 = - ((eq1 & 0x7f7f7f7f7f7f7f7fULL) + 0x0101010101010101ULL) & - (eq1 & 0x8080808080808080ULL); - const uint64_t eq2 = ~((*((uint64_t *)*buffer)) ^ wanted2); - const uint64_t t2 = - ((eq2 & 0x7f7f7f7f7f7f7f7fULL) + 0x0101010101010101ULL) & - (eq2 & 0x8080808080808080ULL); - if ((t1 | t2)) { - break; - } - } - } -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || (!__x86_64__ && !__aarch64__) -finish: -#endif - if (*buffer + 4 <= limit) { - if (string_seek_stop[(*buffer)[0]]) { - // *buffer += 0; - return 1; - } - if (string_seek_stop[(*buffer)[1]]) { - *buffer += 1; - return 1; - } - if (string_seek_stop[(*buffer)[2]]) { - *buffer += 2; - return 1; - } - if (string_seek_stop[(*buffer)[3]]) { - *buffer += 3; - return 1; - } - *buffer += 4; - } - while (*buffer < limit) { - if (string_seek_stop[**buffer]) - return 1; - (*buffer)++; - } - return 0; -} - -static inline int seek2eos(uint8_t **buffer, - register const uint8_t *const limit) { - while (*buffer < limit) { - if (seek2marker(buffer, limit) && **buffer == '"') - return 1; - (*buffer) += 2; /* consume both the escape '\\' and the escape code. */ - } - return 0; -} - -/* ***************************************************************************** -JSON String to Numeral Helpers - allowing for stand-alone mode -***************************************************************************** */ - -#ifndef H_FACIL_IO_H /* defined in fio.h */ - -/** - * We include this in case the parser is used outside of facil.io. - */ -int64_t __attribute__((weak)) fio_atol(char **pstr) { - return strtoll((char *)*pstr, (char **)pstr, 0); -} -#pragma weak fio_atol - -/** - * We include this in case the parser is used outside of facil.io. - */ -double __attribute__((weak)) fio_atof(char **pstr) { - return strtod((char *)*pstr, (char **)pstr); -} -#pragma weak fio_atof - -#endif - -/* ***************************************************************************** -JSON Consumption (astract parsing) -***************************************************************************** */ - -/** - * Returns the number of bytes consumed. Stops as close as possible to the end - * of the buffer or once an object parsing was completed. - */ -static size_t __attribute__((unused)) -fio_json_parse(json_parser_s *parser, const char *buffer, size_t length) { - if (!length || !buffer) - return 0; - uint8_t *pos = (uint8_t *)buffer; - const uint8_t *limit = pos + length; - do { - while (pos < limit && JSON_SEPERATOR[*pos]) - ++pos; - if (pos == limit) - goto stop; - switch (*pos) { - case '"': { - uint8_t *tmp = pos + 1; - if (seek2eos(&tmp, limit) == 0) - goto stop; - if (parser->key) { - uint8_t *key = tmp + 1; - while (key < limit && JSON_SEPERATOR[*key]) - ++key; - if (key >= limit) - goto stop; - if (*key != ':') - goto error; - ++pos; - fio_json_on_string(parser, pos, (uintptr_t)(tmp - pos)); - pos = key + 1; - parser->key = 0; - continue; /* skip tests */ - } else { - ++pos; - fio_json_on_string(parser, pos, (uintptr_t)(tmp - pos)); - pos = tmp + 1; - } - break; - } - case '{': - if (parser->key) { -#if DEBUG - fprintf(stderr, "ERROR: JSON key can't be a Hash.\n"); -#endif - goto error; - } - ++parser->depth; - if (parser->depth >= JSON_MAX_DEPTH) - goto error; - parser->dict = (parser->dict << 1) | 1; - ++pos; - if (fio_json_on_start_object(parser)) - goto error; - break; - case '}': - if ((parser->dict & 1) == 0) { -#if DEBUG - fprintf(stderr, "ERROR: JSON dictionary closure error.\n"); -#endif - goto error; - } - if (!parser->key) { -#if DEBUG - fprintf(stderr, "ERROR: JSON dictionary closure missing key value.\n"); - goto error; -#endif - fio_json_on_null(parser); /* append NULL and recuperate from error. */ - } - --parser->depth; - ++pos; - parser->dict = (parser->dict >> 1); - fio_json_on_end_object(parser); - break; - case '[': - if (parser->key) { -#if DEBUG - fprintf(stderr, "ERROR: JSON key can't be an array.\n"); -#endif - goto error; - } - ++parser->depth; - if (parser->depth >= JSON_MAX_DEPTH) - goto error; - ++pos; - parser->dict = (parser->dict << 1); - if (fio_json_on_start_array(parser)) - goto error; - break; - case ']': - if ((parser->dict & 1)) - goto error; - --parser->depth; - ++pos; - parser->dict = (parser->dict >> 1); - fio_json_on_end_array(parser); - break; - case 't': - if (pos + 3 >= limit) - goto stop; - if (pos[1] == 'r' && pos[2] == 'u' && pos[3] == 'e') - fio_json_on_true(parser); - else - goto error; - pos += 4; - break; - case 'N': /* overflow */ - case 'n': - if (pos + 2 <= limit && (pos[1] | 32) == 'a' && (pos[2] | 32) == 'n') - goto numeral; - if (pos + 3 >= limit) - goto stop; - if (pos[1] == 'u' && pos[2] == 'l' && pos[3] == 'l') - fio_json_on_null(parser); - else - goto error; - pos += 4; - break; - case 'f': - if (pos + 4 >= limit) - goto stop; - if (pos + 4 < limit && pos[1] == 'a' && pos[2] == 'l' && pos[3] == 's' && - pos[4] == 'e') - fio_json_on_false(parser); - else - goto error; - pos += 5; - break; - case '-': /* overflow */ - case '0': /* overflow */ - case '1': /* overflow */ - case '2': /* overflow */ - case '3': /* overflow */ - case '4': /* overflow */ - case '5': /* overflow */ - case '6': /* overflow */ - case '7': /* overflow */ - case '8': /* overflow */ - case '9': /* overflow */ - case '.': /* overflow */ - case 'e': /* overflow */ - case 'E': /* overflow */ - case 'x': /* overflow */ - case 'i': /* overflow */ - case 'I': /* overflow */ - numeral : { - uint8_t *tmp = pos; - long long i = fio_atol((char **)&tmp); - if (tmp > limit) - goto stop; - if (!tmp || JSON_NUMERAL[*tmp]) { - tmp = pos; - double f = fio_atof((char **)&tmp); - if (tmp > limit) - goto stop; - if (!tmp || JSON_NUMERAL[*tmp]) - goto error; - fio_json_on_float(parser, f); - pos = tmp; - } else { - fio_json_on_number(parser, i); - pos = tmp; - } - break; - } - case '#': /* Ruby style comment */ - { - uint8_t *tmp = memchr(pos, '\n', (uintptr_t)(limit - pos)); - if (!tmp) - goto stop; - pos = tmp + 1; - continue; /* skip tests */ - ; - } - case '/': /* C style / Javascript style comment */ - if (pos[1] == '*') { - if (pos + 4 > limit) - goto stop; - uint8_t *tmp = pos + 3; /* avoid this: /*/ - do { - tmp = memchr(tmp, '/', (uintptr_t)(limit - tmp)); - } while (tmp && tmp[-1] != '*'); - if (!tmp) - goto stop; - pos = tmp + 1; - } else if (pos[1] == '/') { - uint8_t *tmp = memchr(pos, '\n', (uintptr_t)(limit - pos)); - if (!tmp) - goto stop; - pos = tmp + 1; - } else - goto error; - continue; /* skip tests */ - ; - default: - goto error; - } - if (parser->depth == 0) { - fio_json_on_json(parser); - goto stop; - } - parser->key = (parser->dict & 1); - } while (pos < limit); -stop: - return (size_t)((uintptr_t)pos - (uintptr_t)buffer); -error: - fio_json_on_error(parser); - return 0; -} - -/* ***************************************************************************** -JSON Unescape String -***************************************************************************** */ - -#ifdef __cplusplus -#define REGISTER -#else -#define REGISTER register -#endif - -/* converts a uint32_t to UTF-8 and returns the number of bytes written */ -static inline int utf8_from_u32(uint8_t *dest, uint32_t u) { - if (u <= 127) { - *dest = u; - return 1; - } else if (u <= 2047) { - *(dest++) = 192 | (u >> 6); - *(dest++) = 128 | (u & 63); - return 2; - } else if (u <= 65535) { - *(dest++) = 224 | (u >> 12); - *(dest++) = 128 | ((u >> 6) & 63); - *(dest++) = 128 | (u & 63); - return 3; - } - *(dest++) = 240 | ((u >> 18) & 7); - *(dest++) = 128 | ((u >> 12) & 63); - *(dest++) = 128 | ((u >> 6) & 63); - *(dest++) = 128 | (u & 63); - return 4; -} - -static void __attribute__((unused)) -fio_json_unescape_str_internal(uint8_t **dest, const uint8_t **src) { - ++(*src); - switch (**src) { - case 'b': - **dest = '\b'; - ++(*src); - ++(*dest); - return; /* from switch */ - case 'f': - **dest = '\f'; - ++(*src); - ++(*dest); - return; /* from switch */ - case 'n': - **dest = '\n'; - ++(*src); - ++(*dest); - return; /* from switch */ - case 'r': - **dest = '\r'; - ++(*src); - ++(*dest); - return; /* from switch */ - case 't': - **dest = '\t'; - ++(*src); - ++(*dest); - return; /* from switch */ - case 'u': { /* test for octal notation */ - if (is_hex[(*src)[1]] && is_hex[(*src)[2]] && is_hex[(*src)[3]] && - is_hex[(*src)[4]]) { - uint32_t t = - ((((is_hex[(*src)[1]] - 1) << 4) | (is_hex[(*src)[2]] - 1)) << 8) | - (((is_hex[(*src)[3]] - 1) << 4) | (is_hex[(*src)[4]] - 1)); - if ((*src)[5] == '\\' && (*src)[6] == 'u' && is_hex[(*src)[7]] && - is_hex[(*src)[8]] && is_hex[(*src)[9]] && is_hex[(*src)[10]]) { - /* Serrogate Pair */ - t = (t & 0x03FF) << 10; - t |= ((((((is_hex[(*src)[7]] - 1) << 4) | (is_hex[(*src)[8]] - 1)) - << 8) | - (((is_hex[(*src)[9]] - 1) << 4) | (is_hex[(*src)[10]] - 1))) & - 0x03FF); - t += 0x10000; - (*src) += 6; - } - *dest += utf8_from_u32(*dest, t); - *src += 5; - return; - } else - goto invalid_escape; - } - case 'x': { /* test for hex notation */ - if (is_hex[(*src)[1]] && is_hex[(*src)[2]]) { - **dest = ((is_hex[(*src)[1]] - 1) << 4) | (is_hex[(*src)[2]] - 1); - ++(*dest); - (*src) += 3; - return; - } else - goto invalid_escape; - } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': { /* test for octal notation */ - if ((*src)[1] >= '0' && (*src)[1] <= '7') { - **dest = (((*src)[0] - '0') << 3) | ((*src)[1] - '0'); - ++(*dest); - (*src) += 2; - break; /* from switch */ - } else - goto invalid_escape; - } - case '"': - case '\\': - case '/': - /* fallthrough */ - default: - invalid_escape: - **dest = **src; - ++(*src); - ++(*dest); - } -} - -static size_t __attribute__((unused)) -fio_json_unescape_str(void *dest, const char *source, size_t length) { - const uint8_t *reader = (uint8_t *)source; - const uint8_t *stop = reader + length; - uint8_t *writer = (uint8_t *)dest; - /* copy in chuncks unless we hit an escape marker */ - while (reader < stop) { -#if !__x86_64__ && !__aarch64__ - /* we can't leverage unaligned memory access, so we read the buffer twice */ - uint8_t *tmp = memchr(reader, '\\', (size_t)(stop - reader)); - if (!tmp) { - memmove(writer, reader, (size_t)(stop - reader)); - writer += (size_t)(stop - reader); - goto finish; - } - memmove(writer, reader, (size_t)(tmp - reader)); - writer += (size_t)(tmp - reader); - reader = tmp; -#else - const uint8_t *limit64 = (uint8_t *)stop - 7; - uint64_t wanted1 = 0x0101010101010101ULL * '\\'; - while (reader < limit64) { - const uint64_t eq1 = ~((*((uint64_t *)reader)) ^ wanted1); - const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu; - const uint64_t t1 = (eq1 & 0x8080808080808080llu); - if ((t0 & t1)) { - break; - } - *((uint64_t *)writer) = *((uint64_t *)reader); - reader += 8; - writer += 8; - } - while (reader < stop) { - if (*reader == '\\') - break; - *writer = *reader; - ++reader; - ++writer; - } - if (reader >= stop) - goto finish; -#endif - fio_json_unescape_str_internal(&writer, &reader); - } -finish: - return (size_t)((uintptr_t)writer - (uintptr_t)dest); -} - -#undef REGISTER - -#endif diff --git a/ext/iodine/fio_siphash.c b/ext/iodine/fio_siphash.c deleted file mode 100644 index b1d33e04..00000000 --- a/ext/iodine/fio_siphash.c +++ /dev/null @@ -1,157 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#include - -/* ***************************************************************************** - - NOTICE - -This code won't be linked to the final application when using fio.h and fio.c. - -The code is here only to allow the FIOBJ library to be extracted from the -facil.io framework library. - -***************************************************************************** */ - -/* ***************************************************************************** -Hashing (SipHash implementation) -***************************************************************************** */ - -#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -/* the algorithm was designed as little endian... so, byte swap 64 bit. */ -#define sip_local64(i) \ - (((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \ - (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \ - (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \ - (((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56) -#else -/* no need */ -#define sip_local64(i) (i) -#endif - -/* 64Bit left rotation, inlined. */ -#define lrot64(i, bits) \ - (((uint64_t)(i) << (bits)) | ((uint64_t)(i) >> (64 - (bits)))) - -static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x, - size_t y, uint64_t key1, uint64_t key2) { - /* initialize the 4 words */ - uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL) ^ key1; - uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL) ^ key2; - uint64_t v2 = (0x0706050403020100ULL ^ 0x6c7967656e657261ULL) ^ key1; - uint64_t v3 = (0x0f0e0d0c0b0a0908ULL ^ 0x7465646279746573ULL) ^ key2; - const uint64_t *w64 = data; - uint8_t len_mod = len & 255; - union { - uint64_t i; - uint8_t str[8]; - } word; - -#define hash_map_SipRound \ - do { \ - v2 += v3; \ - v3 = lrot64(v3, 16) ^ v2; \ - v0 += v1; \ - v1 = lrot64(v1, 13) ^ v0; \ - v0 = lrot64(v0, 32); \ - v2 += v1; \ - v0 += v3; \ - v1 = lrot64(v1, 17) ^ v2; \ - v3 = lrot64(v3, 21) ^ v0; \ - v2 = lrot64(v2, 32); \ - } while (0); - - while (len >= 8) { - word.i = sip_local64(*w64); - v3 ^= word.i; - /* Sip Rounds */ - for (size_t i = 0; i < x; ++i) { - hash_map_SipRound; - } - v0 ^= word.i; - w64 += 1; - len -= 8; - } - word.i = 0; - uint8_t *pos = word.str; - uint8_t *w8 = (void *)w64; - switch (len) { /* fallthrough is intentional */ - case 7: - pos[6] = w8[6]; - /* fallthrough */ - case 6: - pos[5] = w8[5]; - /* fallthrough */ - case 5: - pos[4] = w8[4]; - /* fallthrough */ - case 4: - pos[3] = w8[3]; - /* fallthrough */ - case 3: - pos[2] = w8[2]; - /* fallthrough */ - case 2: - pos[1] = w8[1]; - /* fallthrough */ - case 1: - pos[0] = w8[0]; - } - word.str[7] = len_mod; - - /* last round */ - v3 ^= word.i; - hash_map_SipRound; - hash_map_SipRound; - v0 ^= word.i; - /* Finalization */ - v2 ^= 0xff; - /* d iterations of SipRound */ - for (size_t i = 0; i < y; ++i) { - hash_map_SipRound; - } - hash_map_SipRound; - hash_map_SipRound; - hash_map_SipRound; - hash_map_SipRound; - /* XOR it all together */ - v0 ^= v1 ^ v2 ^ v3; -#undef hash_map_SipRound - return v0; -} - -#pragma weak fio_siphash24 -uint64_t __attribute__((weak)) -fio_siphash24(const void *data, size_t len, uint64_t key1, uint64_t key2) { - return fio_siphash_xy(data, len, 2, 4, key1, key2); -} - -#pragma weak fio_siphash13 -uint64_t __attribute__((weak)) -fio_siphash13(const void *data, size_t len, uint64_t key1, uint64_t key2) { - return fio_siphash_xy(data, len, 1, 3, key1, key2); -} - -#if DEBUG -#include -#include -#include - -void fiobj_siphash_test(void) { - fprintf(stderr, "===================================\n"); - // fio_siphash_speed_test(); - uint64_t result = 0; - clock_t start; - start = clock(); - for (size_t i = 0; i < 100000; i++) { - char *data = "The quick brown fox jumps over the lazy dog "; - __asm__ volatile("" ::: "memory"); - result += fio_siphash_xy(data, 43, 1, 3, 0, 0); - } - fprintf(stderr, "fio 100K SipHash: %lf\n", - (double)(clock() - start) / CLOCKS_PER_SEC); -} -#endif diff --git a/ext/iodine/fio_siphash.h b/ext/iodine/fio_siphash.h deleted file mode 100644 index d9c707e5..00000000 --- a/ext/iodine/fio_siphash.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef H_FIO_SIPHASH_H -#define H_FIO_SIPHASH_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include - -/** - * A SipHash variation (2-4). - */ -uint64_t fio_siphash24(const void *data, size_t len, uint64_t key1, - uint64_t key2); - -/** - * A SipHash 1-3 variation. - */ -uint64_t fio_siphash13(const void *data, size_t len, uint64_t key1, - uint64_t key2); - -/** - * The Hashing function used by dynamic facil.io objects. - * - * Currently implemented using SipHash 1-3. - */ -#define fio_siphash(data, length, k1, k2) \ - fio_siphash13((data), (length), (k1), (k2)) - -#if DEBUG -void fiobj_siphash_test(void); -#else -#define fiobj_siphash_test() -#endif - -#endif /* H_FIO_SIPHASH_H */ diff --git a/ext/iodine/fio_tls.h b/ext/iodine/fio_tls.h deleted file mode 100644 index 1dfa2c30..00000000 --- a/ext/iodine/fio_tls.h +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_FIO_TLS - -/** - * This is an SSL/TLS extension for the facil.io library. - */ -#define H_FIO_TLS - -#include - -#ifndef FIO_TLS_PRINT_SECRET -/* if true, the master key secret should be printed using FIO_LOG_DEBUG */ -#define FIO_TLS_PRINT_SECRET 0 -#endif - -/** An opaque type used for the SSL/TLS functions. */ -typedef struct fio_tls_s fio_tls_s; - -/** - * Creates a new SSL/TLS context / settings object with a default certificate - * (if any). - * - * If no server name is provided and no private key and public certificate are - * provided, an empty TLS object will be created, (maybe okay for clients). - * - * fio_tls_s * tls = fio_tls_new("www.example.com", - * "public_key.pem", - * "private_key.pem", NULL ); - */ -fio_tls_s *fio_tls_new(const char *server_name, const char *public_cert_file, - const char *private_key_file, const char *pk_password); - -/** - * Adds a certificate a new SSL/TLS context / settings object (SNI support). - * - * fio_tls_cert_add(tls, "www.example.com", - * "public_key.pem", - * "private_key.pem", NULL ); - */ -void fio_tls_cert_add(fio_tls_s *, const char *server_name, - const char *public_cert_file, - const char *private_key_file, const char *pk_password); - -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - * - * The `on_selected` callback should accept the `uuid`, the user data pointer - * passed to either `fio_tls_accept` or `fio_tls_connect` (here: - * `udata_connetcion`) and the user data pointer passed to the - * `fio_tls_alpn_add` function (`udata_tls`). - * - * The `on_cleanup` callback will be called when the TLS object is destroyed (or - * `fio_tls_alpn_add` is called again with the same protocol name). The - * `udata_tls` argument will be passed along, as is, to the callback (if set). - * - * Except for the `tls` and `protocol_name` arguments, all arguments can be - * NULL. - */ -void fio_tls_alpn_add(fio_tls_s *tls, const char *protocol_name, - void (*on_selected)(intptr_t uuid, void *udata_connection, - void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)); - -/** - * Returns the number of registered ALPN protocol names. - * - * This could be used when deciding if protocol selection should be delegated to - * the ALPN mechanism, or whether a protocol should be immediately assigned. - * - * If no ALPN protocols are registered, zero (0) is returned. - */ -uintptr_t fio_tls_alpn_count(fio_tls_s *tls); - -/** - * Adds a certificate to the "trust" list, which automatically adds a peer - * verification requirement. - * - * Note, when the fio_tls_s object is used for server connections, this will - * limit connections to clients that connect using a trusted certificate. - * - * fio_tls_trust(tls, "google-ca.pem" ); - */ -void fio_tls_trust(fio_tls_s *, const char *public_cert_file); - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * the result of `fio_accept`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void fio_tls_accept(intptr_t uuid, fio_tls_s *tls, void *udata); - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * one received by a `fio_connect` specified callback `on_connect`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void fio_tls_connect(intptr_t uuid, fio_tls_s *tls, void *udata); - -/** - * Increase the reference count for the TLS object. - * - * Decrease with `fio_tls_destroy`. - */ -void fio_tls_dup(fio_tls_s *tls); - -/** - * Destroys the SSL/TLS context / settings object and frees any related - * resources / memory. - */ -void fio_tls_destroy(fio_tls_s *tls); - -#endif diff --git a/ext/iodine/fio_tls_missing.c b/ext/iodine/fio_tls_missing.c deleted file mode 100644 index 2d0ba87c..00000000 --- a/ext/iodine/fio_tls_missing.c +++ /dev/null @@ -1,649 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifdef __MINGW32__ -// make pedantic compiler happy -typedef struct { - int bogus; -} bogus_s; - -#else -#include - -/** - * This implementation of the facil.io SSL/TLS wrapper API is the default - * implementation that will be used when no SSL/TLS library is available... - * - * ... without modification, this implementation crashes the program. - * - * The implementation can be USED AS A TEMPLATE for future implementations. - * - * This implementation is optimized for ease of development rather than memory - * consumption. - */ -#include "fio_tls.h" - -#if !defined(FIO_TLS_FOUND) /* Library compiler flags */ - -#define REQUIRE_LIBRARY() -#define FIO_TLS_WEAK - -/* TODO: delete me! */ -#undef FIO_TLS_WEAK -#define FIO_TLS_WEAK __attribute__((weak)) -#if !FIO_IGNORE_TLS_IF_MISSING -#undef REQUIRE_LIBRARY -#define REQUIRE_LIBRARY() \ - FIO_LOG_FATAL("No supported SSL/TLS library available."); \ - exit(-1); -#endif - -#ifndef FIO_TLS_TIMEOUT -#define FIO_TLS_TIMEOUT 4 -#endif - -/* STOP deleting after this line */ - -/* ***************************************************************************** -The SSL/TLS helper data types (can be left as is) -***************************************************************************** */ -#define FIO_INCLUDE_STR 1 -#define FIO_FORCE_MALLOC_TMP 1 -#include - -typedef struct { - fio_str_s private_key; - fio_str_s public_key; - fio_str_s password; -} cert_s; - -static inline int fio_tls_cert_cmp(const cert_s *dest, const cert_s *src) { - return fio_str_iseq(&dest->private_key, &src->private_key); -} -static inline void fio_tls_cert_copy(cert_s *dest, cert_s *src) { - *dest = (cert_s){ - .private_key = FIO_STR_INIT, - .public_key = FIO_STR_INIT, - .password = FIO_STR_INIT, - }; - fio_str_concat(&dest->private_key, &src->private_key); - fio_str_concat(&dest->public_key, &src->public_key); - fio_str_concat(&dest->password, &src->password); -} -static inline void fio_tls_cert_destroy(cert_s *obj) { - fio_str_free(&obj->private_key); - fio_str_free(&obj->public_key); - fio_str_free(&obj->password); -} - -#define FIO_ARY_NAME cert_ary -#define FIO_ARY_TYPE cert_s -#define FIO_ARY_COMPARE(k1, k2) (fio_tls_cert_cmp(&(k1), &(k2))) -#define FIO_ARY_COPY(dest, obj) fio_tls_cert_copy(&(dest), &(obj)) -#define FIO_ARY_DESTROY(key) fio_tls_cert_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -typedef struct { - fio_str_s pem; -} trust_s; - -static inline int fio_tls_trust_cmp(const trust_s *dest, const trust_s *src) { - return fio_str_iseq(&dest->pem, &src->pem); -} -static inline void fio_tls_trust_copy(trust_s *dest, trust_s *src) { - *dest = (trust_s){ - .pem = FIO_STR_INIT, - }; - fio_str_concat(&dest->pem, &src->pem); -} -static inline void fio_tls_trust_destroy(trust_s *obj) { - fio_str_free(&obj->pem); -} - -#define FIO_ARY_NAME trust_ary -#define FIO_ARY_TYPE trust_s -#define FIO_ARY_COMPARE(k1, k2) (fio_tls_trust_cmp(&(k1), &(k2))) -#define FIO_ARY_COPY(dest, obj) fio_tls_trust_copy(&(dest), &(obj)) -#define FIO_ARY_DESTROY(key) fio_tls_trust_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -typedef struct { - fio_str_s name; /* fio_str_s provides cache locality for small strings */ - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls); - void *udata_tls; - void (*on_cleanup)(void *udata_tls); -} alpn_s; - -static inline int fio_alpn_cmp(const alpn_s *dest, const alpn_s *src) { - return fio_str_iseq(&dest->name, &src->name); -} -static inline void fio_alpn_copy(alpn_s *dest, alpn_s *src) { - *dest = (alpn_s){ - .name = FIO_STR_INIT, - .on_selected = src->on_selected, - .udata_tls = src->udata_tls, - .on_cleanup = src->on_cleanup, - }; - fio_str_concat(&dest->name, &src->name); -} -static inline void fio_alpn_destroy(alpn_s *obj) { - if (obj->on_cleanup) - obj->on_cleanup(obj->udata_tls); - fio_str_free(&obj->name); -} - -#define FIO_SET_NAME alpn_list -#define FIO_SET_OBJ_TYPE alpn_s -#define FIO_SET_OBJ_COMPARE(k1, k2) fio_alpn_cmp(&(k1), &(k2)) -#define FIO_SET_OBJ_COPY(dest, obj) fio_alpn_copy(&(dest), &(obj)) -#define FIO_SET_OBJ_DESTROY(key) fio_alpn_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -/* ***************************************************************************** -The SSL/TLS Context type -***************************************************************************** */ - -/** An opaque type used for the SSL/TLS functions. */ -struct fio_tls_s { - size_t ref; /* Reference counter, to guards the ALPN registry */ - alpn_list_s alpn; /* ALPN is the name for the protocol selection extension */ - - /*** the next two components could be optimized away with tweaking stuff ***/ - - cert_ary_s sni; /* SNI (server name extension) stores ID certificates */ - trust_ary_s trust; /* Trusted certificate registry (peer verification) */ - - /************ TODO: implementation data fields go here ******************/ -}; - -/* ***************************************************************************** -ALPN Helpers -***************************************************************************** */ - -/** Returns a pointer to the ALPN data (callback, etc') IF exists in the TLS. */ -FIO_FUNC inline alpn_s *alpn_find(fio_tls_s *tls, char *name, size_t len) { - alpn_s tmp = {.name = FIO_STR_INIT_STATIC2(name, len)}; - alpn_list__map_s_ *pos = - alpn_list__find_map_pos_(&tls->alpn, fio_str_hash(&tmp.name), tmp); - if (!pos || !pos->pos) - return NULL; - return &pos->pos->obj; -} - -/** Adds an ALPN data object to the ALPN "list" (set) */ -FIO_FUNC inline void alpn_add( - fio_tls_s *tls, const char *protocol_name, - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)) { - alpn_s tmp = { - .name = FIO_STR_INIT_STATIC(protocol_name), - .on_selected = on_selected, - .udata_tls = udata_tls, - .on_cleanup = on_cleanup, - }; - if (fio_str_len(&tmp.name) > 255) { - FIO_LOG_ERROR("ALPN protocol names are limited to 255 bytes."); - return; - } - alpn_list_overwrite(&tls->alpn, fio_str_hash(&tmp.name), tmp, NULL); - tmp.on_cleanup = NULL; - fio_alpn_destroy(&tmp); -} - -/** Returns a pointer to the default (first) ALPN object in the TLS (if any). */ -FIO_FUNC inline alpn_s *alpn_default(fio_tls_s *tls) { - if (!tls || !alpn_list_count(&tls->alpn) || !tls->alpn.ordered) - return NULL; - return &tls->alpn.ordered[0].obj; -} - -typedef struct { - alpn_s alpn; - intptr_t uuid; - void *udata_connection; -} alpn_task_s; - -FIO_FUNC inline void alpn_select___task(void *t_, void *ignr_) { - alpn_task_s *t = t_; - if (fio_is_valid(t->uuid)) - t->alpn.on_selected(t->uuid, t->udata_connection, t->alpn.udata_tls); - fio_free(t); - (void)ignr_; -} - -/** Schedules the ALPN protocol callback. */ -FIO_FUNC inline void alpn_select(alpn_s *alpn, intptr_t uuid, - void *udata_connection) { - if (!alpn || !alpn->on_selected) - return; - alpn_task_s *t = fio_malloc(sizeof(*t)); - *t = (alpn_task_s){ - .alpn = *alpn, - .uuid = uuid, - .udata_connection = udata_connection, - }; - /* move task out of the socket's lock */ - fio_defer(alpn_select___task, t, NULL); -} - -/* ***************************************************************************** -SSL/TLS Context (re)-building - TODO: add implementation details -***************************************************************************** */ - -/** Called when the library specific data for the context should be destroyed */ -static void fio_tls_destroy_context(fio_tls_s *tls) { - /* TODO: Library specific implementation */ - FIO_LOG_DEBUG("destroyed TLS context %p", (void *)tls); -} - -/** Called when the library specific data for the context should be built */ -static void fio_tls_build_context(fio_tls_s *tls) { - fio_tls_destroy_context(tls); - /* TODO: Library specific implementation */ - - /* Certificates */ - FIO_ARY_FOR(&tls->sni, pos) { - fio_str_info_s k = fio_str_info(&pos->private_key); - fio_str_info_s p = fio_str_info(&pos->public_key); - fio_str_info_s pw = fio_str_info(&pos->password); - if (p.len && k.len) { - /* TODO: attache certificate */ - (void)pw; - } else { - /* TODO: self signed certificate */ - } - } - - /* ALPN Protocols */ - FIO_SET_FOR_LOOP(&tls->alpn, pos) { - fio_str_info_s name = fio_str_info(&pos->obj.name); - (void)name; - // map to pos->callback; - } - - /* Peer Verification / Trust */ - if (trust_ary_count(&tls->trust)) { - /* TODO: enable peer verification */ - - /* TODO: Add each ceriticate in the PEM to the trust "store" */ - FIO_ARY_FOR(&tls->trust, pos) { - fio_str_info_s pem = fio_str_info(&pos->pem); - (void)pem; - } - } - - FIO_LOG_DEBUG("(re)built TLS context %p", (void *)tls); -} - -/* ***************************************************************************** -SSL/TLS RW Hooks - TODO: add implementation details -***************************************************************************** */ - -/* TODO: this is an example implementation - fix for specific library. */ - -#define TLS_BUFFER_LENGTH (1 << 15) -typedef struct { - fio_tls_s *tls; - size_t len; - uint8_t alpn_ok; - char buffer[TLS_BUFFER_LENGTH]; -} buffer_s; - -/** - * Implement reading from a file descriptor. Should behave like the file - * system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_read(intptr_t uuid, void *udata, void *buf, - size_t count) { - ssize_t ret = read(fio_uuid2fd(uuid), buf, count); - if (ret > 0) { - FIO_LOG_DEBUG("Read %zd bytes from %p", ret, (void *)uuid); - } - return ret; - (void)udata; -} - -/** - * When implemented, this function will be called to flush any data remaining - * in the internal buffer. - * - * The function should return the number of bytes remaining in the internal - * buffer (0 is a valid response) or -1 (on error). - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_flush(intptr_t uuid, void *udata) { - buffer_s *buffer = udata; - if (!buffer->len) { - FIO_LOG_DEBUG("Flush empty for %p", (void *)uuid); - return 0; - } - ssize_t r = write(fio_uuid2fd(uuid), buffer->buffer, buffer->len); - if (r < 0) - return -1; - if (r == 0) { - errno = ECONNRESET; - return -1; - } - size_t len = buffer->len - r; - if (len) - memmove(buffer->buffer, buffer->buffer + r, len); - buffer->len = len; - FIO_LOG_DEBUG("Sent %zd bytes to %p", r, (void *)uuid); - return r; -} - -/** - * Implement writing to a file descriptor. Should behave like the file system - * `write` call. - * - * If an internal buffer is implemented and it is full, errno should be set to - * EWOULDBLOCK and the function should return -1. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_write(intptr_t uuid, void *udata, const void *buf, - size_t count) { - buffer_s *buffer = udata; - size_t can_copy = TLS_BUFFER_LENGTH - buffer->len; - if (can_copy > count) - can_copy = count; - if (!can_copy) - goto would_block; - memcpy(buffer->buffer + buffer->len, buf, can_copy); - buffer->len += can_copy; - FIO_LOG_DEBUG("Copied %zu bytes to %p", can_copy, (void *)uuid); - fio_tls_flush(uuid, udata); - return can_copy; -would_block: - errno = EWOULDBLOCK; - return -1; -} - -/** - * The `close` callback should close the underlying socket / file descriptor. - * - * If the function returns a non-zero value, it will be called again after an - * attempt to flush the socket and any pending outgoing buffer. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - * */ -static ssize_t fio_tls_before_close(intptr_t uuid, void *udata) { - FIO_LOG_DEBUG("The `before_close` callback was called for %p", (void *)uuid); - return 1; - (void)udata; -} -/** - * Called to perform cleanup after the socket was closed. - * */ -static void fio_tls_cleanup(void *udata) { - buffer_s *buffer = udata; - /* make sure the ALPN callback was called, just in case cleanup is required */ - if (!buffer->alpn_ok) { - alpn_select(alpn_default(buffer->tls), -1, NULL /* ALPN udata */); - } - fio_tls_destroy(buffer->tls); /* manage reference count */ - fio_free(udata); -} - -static fio_rw_hook_s FIO_TLS_HOOKS = { - .read = fio_tls_read, - .write = fio_tls_write, - .before_close = fio_tls_before_close, - .flush = fio_tls_flush, - .cleanup = fio_tls_cleanup, -}; - -static size_t fio_tls_handshake(intptr_t uuid, void *udata) { - /*TODO: test for handshake completion */ - if (0 /*handshake didn't complete */) - return 0; - if (fio_rw_hook_replace_unsafe(uuid, &FIO_TLS_HOOKS, udata) == 0) { - FIO_LOG_DEBUG("Completed TLS handshake for %p", (void *)uuid); - /* - * make sure the connection is re-added to the reactor... - * in case, while waiting for ALPN, it was suspended for missing a protocol. - */ - fio_force_event(uuid, FIO_EVENT_ON_DATA); - } else { - FIO_LOG_DEBUG("Something went wrong during TLS handshake for %p", - (void *)uuid); - } - return 1; -} - -static ssize_t fio_tls_read4handshake(intptr_t uuid, void *udata, void *buf, - size_t count) { - FIO_LOG_DEBUG("TLS handshake from read %p", (void *)uuid); - if (fio_tls_handshake(uuid, udata)) - return fio_tls_read(uuid, udata, buf, count); - errno = EWOULDBLOCK; - return -1; -} - -static ssize_t fio_tls_write4handshake(intptr_t uuid, void *udata, - const void *buf, size_t count) { - FIO_LOG_DEBUG("TLS handshake from write %p", (void *)uuid); - if (fio_tls_handshake(uuid, udata)) - return fio_tls_write(uuid, udata, buf, count); - errno = EWOULDBLOCK; - return -1; -} - -static ssize_t fio_tls_flush4handshake(intptr_t uuid, void *udata) { - FIO_LOG_DEBUG("TLS handshake from flush %p", (void *)uuid); - if (fio_tls_handshake(uuid, udata)) - return fio_tls_flush(uuid, udata); - /* TODO: return a positive value only if handshake requires a write */ - return 1; -} -static fio_rw_hook_s FIO_TLS_HANDSHAKE_HOOKS = { - .read = fio_tls_read4handshake, - .write = fio_tls_write4handshake, - .before_close = fio_tls_before_close, - .flush = fio_tls_flush4handshake, - .cleanup = fio_tls_cleanup, -}; - -static inline void fio_tls_attach2uuid(intptr_t uuid, fio_tls_s *tls, - void *udata, uint8_t is_server) { - fio_atomic_add(&tls->ref, 1); /* manage reference count */ - /* TODO: this is only an example implementation - fix for specific library */ - if (is_server) { - /* Server mode (accept) */ - FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (server mode).", - (void *)uuid); - } else { - /* Client mode (connect) */ - FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (client mode).", - (void *)uuid); - } - /* common implementation (TODO) */ - buffer_s *connection_data = fio_malloc(sizeof(*connection_data)); - FIO_ASSERT_ALLOC(connection_data); - fio_rw_hook_set(uuid, &FIO_TLS_HANDSHAKE_HOOKS, - connection_data); /* 32Kb buffer */ - alpn_select(alpn_default(tls), uuid, udata); - connection_data->alpn_ok = 1; -} - -/* ***************************************************************************** -SSL/TLS API implementation - this can be pretty much used as is... -***************************************************************************** */ - -/** - * Creates a new SSL/TLS context / settings object with a default certificate - * (if any). - */ -fio_tls_s *FIO_TLS_WEAK fio_tls_new(const char *server_name, const char *cert, - const char *key, const char *pk_password) { - REQUIRE_LIBRARY(); - fio_tls_s *tls = calloc(sizeof(*tls), 1); - tls->ref = 1; - fio_tls_cert_add(tls, server_name, key, cert, pk_password); - return tls; -} - -/** - * Adds a certificate a new SSL/TLS context / settings object. - */ -void FIO_TLS_WEAK fio_tls_cert_add(fio_tls_s *tls, const char *server_name, - const char *cert, const char *key, - const char *pk_password) { - REQUIRE_LIBRARY(); - cert_s c = { - .private_key = FIO_STR_INIT, - .public_key = FIO_STR_INIT, - .password = FIO_STR_INIT_STATIC2(pk_password, - (pk_password ? strlen(pk_password) : 0)), - }; - if (key && cert) { - if (fio_str_readfile(&c.private_key, key, 0, 0).data == NULL) - goto file_missing; - if (fio_str_readfile(&c.public_key, cert, 0, 0).data == NULL) - goto file_missing; - cert_ary_push(&tls->sni, c); - } else if (server_name) { - /* Self-Signed TLS Certificates */ - c.private_key = FIO_STR_INIT_STATIC(server_name); - cert_ary_push(&tls->sni, c); - } - fio_tls_cert_destroy(&c); - fio_tls_build_context(tls); - return; -file_missing: - FIO_LOG_FATAL("TLS certificate file missing for either %s or %s or both.", - key, cert); - exit(-1); -} - -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - * - * The callback should accept the `uuid`, the user data pointer passed to either - * `fio_tls_accept` or `fio_tls_connect` (here: `udata_connetcion`) and the user - * data pointer passed to the `fio_tls_alpn_add` function (`udata_tls`). - * - * The `on_cleanup` callback will be called when the TLS object is destroyed (or - * `fio_tls_alpn_add` is called again with the same protocol name). The - * `udata_tls` argumrnt will be passed along, as is, to the callback (if set). - * - * Except for the `tls` and `protocol_name` arguments, all arguments can be - * NULL. - */ -void FIO_TLS_WEAK fio_tls_alpn_add( - fio_tls_s *tls, const char *protocol_name, - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)) { - REQUIRE_LIBRARY(); - alpn_add(tls, protocol_name, on_selected, udata_tls, on_cleanup); - fio_tls_build_context(tls); -} - -/** - * Returns the number of registered ALPN protocol names. - * - * This could be used when deciding if protocol selection should be delegated to - * the ALPN mechanism, or whether a protocol should be immediately assigned. - * - * If no ALPN protocols are registered, zero (0) is returned. - */ -uintptr_t FIO_TLS_WEAK fio_tls_alpn_count(fio_tls_s *tls) { - return tls ? alpn_list_count(&tls->alpn) : 0; -} - -/** - * Adds a certificate to the "trust" list, which automatically adds a peer - * verification requirement. - * - * fio_tls_trust(tls, "google-ca.pem" ); - */ -void FIO_TLS_WEAK fio_tls_trust(fio_tls_s *tls, const char *public_cert_file) { - REQUIRE_LIBRARY(); - trust_s c = { - .pem = FIO_STR_INIT, - }; - if (!public_cert_file) - return; - if (fio_str_readfile(&c.pem, public_cert_file, 0, 0).data == NULL) - goto file_missing; - trust_ary_push(&tls->trust, c); - fio_tls_trust_destroy(&c); - fio_tls_build_context(tls); - return; -file_missing: - FIO_LOG_FATAL("TLS certificate file missing for %s ", public_cert_file); - exit(-1); -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * the result of `fio_accept`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, fio_tls_s *tls, void *udata) { - REQUIRE_LIBRARY(); - fio_timeout_set(uuid, FIO_TLS_TIMEOUT); - fio_tls_attach2uuid(uuid, tls, udata, 1); -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer (i.e., - * one received by a `fio_connect` specified callback `on_connect`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, fio_tls_s *tls, void *udata) { - REQUIRE_LIBRARY(); - fio_tls_attach2uuid(uuid, tls, udata, 0); -} - -/** - * Increase the reference count for the TLS object. - * - * Decrease with `fio_tls_destroy`. - */ -void FIO_TLS_WEAK fio_tls_dup(fio_tls_s *tls) { fio_atomic_add(&tls->ref, 1); } - -/** - * Destroys the SSL/TLS context / settings object and frees any related - * resources / memory. - */ -void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) { - if (!tls) - return; - REQUIRE_LIBRARY(); - if (fio_atomic_sub(&tls->ref, 1)) - return; - fio_tls_destroy_context(tls); - alpn_list_free(&tls->alpn); - cert_ary_free(&tls->sni); - trust_ary_free(&tls->trust); - free(tls); -} - -#endif /* Library compiler flags */ -#endif diff --git a/ext/iodine/fio_tls_openssl.c b/ext/iodine/fio_tls_openssl.c deleted file mode 100644 index fdeb90bd..00000000 --- a/ext/iodine/fio_tls_openssl.c +++ /dev/null @@ -1,1056 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifdef __MINGW32__ -// make pedantic compiler happy -typedef struct { - int bogus; -} bogus_s; - -#else -#include - -/** - * This implementation of the facil.io SSL/TLS wrapper API wraps the OpenSSL API - * to provide TLS 1.2 and TLS 1.3 to facil.io applications. - * - * The implementation requires `HAVE_OPENSSL` to be set. - */ -#include "fio_tls.h" - -#if HAVE_OPENSSL -#include -#include -#include - -#define REQUIRE_LIBRARY() -#define FIO_TLS_WEAK - -/* ***************************************************************************** -The SSL/TLS helper data types (can be left as is) -***************************************************************************** */ -#define FIO_INCLUDE_STR 1 -#define FIO_FORCE_MALLOC_TMP 1 -#include - -#ifndef FIO_TLS_TIMEOUT -#define FIO_TLS_TIMEOUT 4 -#endif - -typedef struct { - fio_str_s private_key; - fio_str_s public_key; - fio_str_s password; -} cert_s; - -static inline int fio_tls_cert_cmp(const cert_s *dest, const cert_s *src) { - return fio_str_iseq(&dest->private_key, &src->private_key); -} -static inline void fio_tls_cert_copy(cert_s *dest, cert_s *src) { - *dest = (cert_s){ - .private_key = FIO_STR_INIT, - .public_key = FIO_STR_INIT, - .password = FIO_STR_INIT, - }; - fio_str_concat(&dest->private_key, &src->private_key); - fio_str_concat(&dest->public_key, &src->public_key); - fio_str_concat(&dest->password, &src->password); -} -static inline void fio_tls_cert_destroy(cert_s *obj) { - fio_str_free(&obj->private_key); - fio_str_free(&obj->public_key); - fio_str_free(&obj->password); -} - -#define FIO_ARY_NAME cert_ary -#define FIO_ARY_TYPE cert_s -#define FIO_ARY_COMPARE(k1, k2) (fio_tls_cert_cmp(&(k1), &(k2))) -#define FIO_ARY_COPY(dest, obj) fio_tls_cert_copy(&(dest), &(obj)) -#define FIO_ARY_DESTROY(key) fio_tls_cert_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -typedef struct { - fio_str_s pem; -} trust_s; - -static inline int fio_tls_trust_cmp(const trust_s *dest, const trust_s *src) { - return fio_str_iseq(&dest->pem, &src->pem); -} -static inline void fio_tls_trust_copy(trust_s *dest, trust_s *src) { - *dest = (trust_s){ - .pem = FIO_STR_INIT, - }; - fio_str_concat(&dest->pem, &src->pem); -} -static inline void fio_tls_trust_destroy(trust_s *obj) { - fio_str_free(&obj->pem); -} - -#define FIO_ARY_NAME trust_ary -#define FIO_ARY_TYPE trust_s -#define FIO_ARY_COMPARE(k1, k2) (fio_tls_trust_cmp(&(k1), &(k2))) -#define FIO_ARY_COPY(dest, obj) fio_tls_trust_copy(&(dest), &(obj)) -#define FIO_ARY_DESTROY(key) fio_tls_trust_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -typedef struct { - fio_str_s name; /* fio_str_s provides cache locality for small strings */ - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls); - void *udata_tls; - void (*on_cleanup)(void *udata_tls); -} alpn_s; - -static inline int fio_alpn_cmp(const alpn_s *dest, const alpn_s *src) { - return fio_str_iseq(&dest->name, &src->name); -} -static inline void fio_alpn_copy(alpn_s *dest, alpn_s *src) { - *dest = (alpn_s){ - .name = FIO_STR_INIT, - .on_selected = src->on_selected, - .udata_tls = src->udata_tls, - .on_cleanup = src->on_cleanup, - }; - fio_str_concat(&dest->name, &src->name); -} -static inline void fio_alpn_destroy(alpn_s *obj) { - if (obj->on_cleanup) - obj->on_cleanup(obj->udata_tls); - fio_str_free(&obj->name); -} - -#define FIO_SET_NAME alpn_list -#define FIO_SET_OBJ_TYPE alpn_s -#define FIO_SET_OBJ_COMPARE(k1, k2) fio_alpn_cmp(&(k1), &(k2)) -#define FIO_SET_OBJ_COPY(dest, obj) fio_alpn_copy(&(dest), &(obj)) -#define FIO_SET_OBJ_DESTROY(key) fio_alpn_destroy(&(key)) -#define FIO_FORCE_MALLOC_TMP 1 -#include - -/* ***************************************************************************** -The SSL/TLS type -***************************************************************************** */ - -/** An opaque type used for the SSL/TLS functions. */ -struct fio_tls_s { - size_t ref; /* Reference counter, to guards the ALPN registry */ - alpn_list_s alpn; /* ALPN is the name for the protocol selection extension */ - - /*** the next two components could be optimized away with tweaking stuff ***/ - - cert_ary_s sni; /* SNI (server name extension) stores ID certificates */ - trust_ary_s trust; /* Trusted certificate registry (peer verification) */ - - /************ TODO: implementation data fields go here ******************/ - - SSL_CTX *ctx; /* The Open SSL context (updated each time). */ - unsigned char *alpn_str; /* the computed server-format ALPN string */ - int alpn_len; -}; - -/* ***************************************************************************** -ALPN Helpers -***************************************************************************** */ - -/** Returns a pointer to the ALPN data (callback, etc') IF exists in the TLS. */ -FIO_FUNC inline alpn_s *alpn_find(fio_tls_s *tls, char *name, size_t len) { - alpn_s tmp = {.name = FIO_STR_INIT_STATIC2(name, len)}; - alpn_list__map_s_ *pos = - alpn_list__find_map_pos_(&tls->alpn, fio_str_hash(&tmp.name), tmp); - if (!pos || !pos->pos) - return NULL; - return &pos->pos->obj; -} - -/** Adds an ALPN data object to the ALPN "list" (set) */ -FIO_FUNC inline void alpn_add( - fio_tls_s *tls, const char *protocol_name, - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)) { - alpn_s tmp = { - .name = FIO_STR_INIT_STATIC(protocol_name), - .on_selected = on_selected, - .udata_tls = udata_tls, - .on_cleanup = on_cleanup, - }; - if (fio_str_len(&tmp.name) > 255) { - FIO_LOG_ERROR("ALPN protocol names are limited to 255 bytes."); - return; - } - alpn_list_overwrite(&tls->alpn, fio_str_hash(&tmp.name), tmp, NULL); - tmp.on_cleanup = NULL; - fio_alpn_destroy(&tmp); -} - -/** Returns a pointer to the default (first) ALPN object in the TLS (if any). */ -FIO_FUNC inline alpn_s *alpn_default(fio_tls_s *tls) { - if (!tls || !alpn_list_count(&tls->alpn) || !tls->alpn.ordered) - return NULL; - return &tls->alpn.ordered[0].obj; -} - -typedef struct { - alpn_s alpn; - intptr_t uuid; - void *udata_connection; -} alpn_task_s; - -FIO_FUNC inline void alpn_select___task(void *t_, void *ignr_) { - alpn_task_s *t = t_; - if (fio_is_valid(t->uuid)) { - fio_timeout_set(t->uuid, 0); // remove TLS timeout - t->alpn.on_selected(t->uuid, t->udata_connection, t->alpn.udata_tls); - } else { - t->alpn.on_selected(-1, t->udata_connection, t->alpn.udata_tls); - } - fio_free(t); - (void)ignr_; -} - -/** Schedules the ALPN protocol callback. */ -FIO_FUNC inline void alpn_select(alpn_s *alpn, intptr_t uuid, - void *udata_connection) { - if (!alpn || !alpn->on_selected) - return; - alpn_task_s *t = fio_malloc(sizeof(*t)); - *t = (alpn_task_s){ - .alpn = *alpn, - .uuid = uuid, - .udata_connection = udata_connection, - }; - fio_defer(alpn_select___task, t, NULL); -} - -/* ***************************************************************************** -OpenSSL Helpers -***************************************************************************** */ - -static EVP_PKEY *fio_tls_pkey = NULL; - -static void fio_tls_clear_root_key(void *key) { - EVP_PKEY_free(key); - fio_tls_pkey = NULL; -} - -static void fio_tls_make_root_key(void) { - static fio_lock_i lock = FIO_LOCK_INIT; - fio_lock(&lock); - if (fio_tls_pkey) - goto finish; - /* create private key, free it at exit */ - FIO_LOG_DEBUG("calculating a new TLS private key... might take a while."); - - fio_tls_pkey = EVP_PKEY_new(); - FIO_ASSERT(fio_tls_pkey, "OpenSSL failed to create private key."); - - /* TODO: replace RSA with something else? is there something else? */ - RSA *rsa = RSA_new(); - BIGNUM *e = BN_new(); - BN_clear(e); - BN_add_word(e, 65537); - FIO_ASSERT_ALLOC(e); - FIO_ASSERT(RSA_generate_key_ex(rsa, 2048, e, NULL), - "OpenSSL failed to create RSA key."); - BN_free(e); - EVP_PKEY_assign_RSA(fio_tls_pkey, rsa); - fio_state_callback_add(FIO_CALL_AT_EXIT, fio_tls_clear_root_key, - fio_tls_pkey); -finish: - fio_unlock(&lock); -} - -static X509 *fio_tls_create_self_signed(char *server_name) { - X509 *cert = X509_new(); - static uint32_t counter = 0; - FIO_ASSERT(cert, - "OpenSSL failed to allocate memory for self-signed ceritifcate."); - fio_tls_make_root_key(); - - /* serial number */ - fio_atomic_add(&counter, 1); - ASN1_INTEGER_set(X509_get_serialNumber(cert), counter); - - /* validity (180 days) */ - X509_gmtime_adj(X509_get_notBefore(cert), 0); - X509_gmtime_adj(X509_get_notAfter(cert), 15552000L); - - /* set (public) key */ - X509_set_pubkey(cert, fio_tls_pkey); - - /* set identity details */ - X509_NAME *s = X509_get_subject_name(cert); - size_t srv_name_len = strlen(server_name); - X509_NAME_add_entry_by_txt(s, "O", MBSTRING_ASC, (unsigned char *)server_name, - srv_name_len, -1, 0); - X509_NAME_add_entry_by_txt(s, "CN", MBSTRING_ASC, - (unsigned char *)server_name, srv_name_len, -1, 0); - X509_NAME_add_entry_by_txt(s, "CA", MBSTRING_ASC, - (unsigned char *)server_name, srv_name_len, -1, 0); - X509_set_issuer_name(cert, s); - - /* sign certificate */ - FIO_ASSERT(X509_sign(cert, fio_tls_pkey, EVP_sha512()), - "OpenSSL failed to signe self-signed certificate"); - // FILE *fp = fopen("tmp.pem", "ab+"); - // if (fp) { - // PEM_write_X509(fp, cert); - // fclose(fp); - // } - - return cert; -} - -/* ***************************************************************************** -SSL/TLS Context (re)-building -***************************************************************************** */ - -#define TLS_BUFFER_LENGTH (1 << 15) -typedef struct { - SSL *ssl; - fio_tls_s *tls; - void *alpn_arg; - intptr_t uuid; - uint8_t is_server; - volatile uint8_t alpn_ok; -} fio_tls_connection_s; - -static void fio_tls_alpn_fallback(fio_tls_connection_s *c) { - alpn_s *alpn = alpn_default(c->tls); - if (!alpn || !alpn->on_selected) - return; - /* set protocol to default protocol */ - FIO_LOG_DEBUG("TLS ALPN handshake missing, falling back on %s for %p", - fio_str_info(&alpn->name).data, (void *)c->uuid); - alpn_select(alpn, c->uuid, c->alpn_arg); -} -static int fio_tls_alpn_selector_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - void *tls_) { - fio_tls_s *tls = tls_; - alpn_s *alpn; - /* TODO: select ALPN and call on_selected */ - fio_tls_connection_s *c = SSL_get_ex_data(ssl, 0); - c->alpn_ok = 1; - - if (alpn_list_count(&tls->alpn) == 0) - return SSL_TLSEXT_ERR_NOACK; - const unsigned char *end = in + inlen; - while (in < end) { - uint8_t l = in[0]; - alpn = alpn_find(tls, (char *)in + 1, l); - in += l + 1; - if (!alpn) - continue; - fio_str_info_s info = fio_str_info(&alpn->name); - *out = (unsigned char *)info.data; - *outlen = (unsigned char)info.len; - FIO_LOG_DEBUG("TLS ALPN set to: %s for %p", info.data, (void *)c->uuid); - alpn_select(alpn, c->uuid, c->alpn_arg); - return SSL_TLSEXT_ERR_OK; - } - /* set protocol to default protocol */ - alpn = alpn_default(tls); - alpn_select(alpn, c->uuid, c->alpn_arg); - FIO_LOG_DEBUG( - "TLS ALPN handshake failed, falling back on default (%s) for %p", - fio_str_data(&alpn->name), (void *)c->uuid); - return SSL_TLSEXT_ERR_NOACK; - (void)ssl; - (void)out; - (void)outlen; - (void)in; - (void)inlen; - (void)tls; -} - -/** Called when the library specific data for the context should be destroyed */ -static void fio_tls_destroy_context(fio_tls_s *tls) { - /* TODO: Library specific implementation */ - SSL_CTX_free(tls->ctx); - free(tls->alpn_str); - - tls->ctx = NULL; - tls->alpn_str = NULL; - tls->alpn_len = 0; - FIO_LOG_DEBUG("destroyed TLS context for OpenSSL %p", (void *)tls); -} - -static int fio_tls_pem_passwd_cb(char *buf, int size, int rwflag, - void *password) { - fio_str_info_s *p = password; - if (!p || !p->len || !size) - return 0; - int len = (size <= (int)p->len) ? (size - 1) : (int)p->len; - memcpy(buf, p->data, len); - buf[len] = 0; - return len; - (void)rwflag; -} - -/** Called when the library specific data for the context should be built */ -static void fio_tls_build_context(fio_tls_s *tls) { - fio_tls_destroy_context(tls); - /* TODO: Library specific implementation */ - - /* create new context */ - tls->ctx = SSL_CTX_new(TLS_method()); - SSL_CTX_set_mode(tls->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); - /* see: https://caniuse.com/#search=tls */ - SSL_CTX_set_min_proto_version(tls->ctx, TLS1_2_VERSION); - SSL_CTX_set_options(tls->ctx, SSL_OP_NO_COMPRESSION); - - /* attach certificates */ - FIO_ARY_FOR(&tls->sni, pos) { - fio_str_info_s keys[4] = { - fio_str_info(&pos->private_key), fio_str_info(&pos->public_key), - fio_str_info(&pos->password), - /* empty password slot for public key */ - }; - if (keys[0].len && keys[1].len) { - if (1) { - /* Extract private key from private key file */ - BIO *bio = BIO_new_mem_buf(keys[0].data, keys[0].len); - if (bio) { - EVP_PKEY *k = PEM_read_bio_PrivateKey( - bio, NULL, fio_tls_pem_passwd_cb, keys + 2); - if (k) { - FIO_LOG_DEBUG("TLS read private key from PEM file."); - SSL_CTX_use_PrivateKey(tls->ctx, k); - } - BIO_free(bio); - } - } - /* Certificate Files loaded */ - for (int ki = 0; ki < 2; ++ki) { - /* Extract as much data as possible from each file */ - BIO *bio = BIO_new_mem_buf(keys[ki].data, keys[ki].len); - FIO_ASSERT(bio, "OpenSSL error allocating BIO."); - STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio( - bio, NULL, fio_tls_pem_passwd_cb, keys + ki + 2); - if (inf) { - for (int i = 0; i < sk_X509_INFO_num(inf); ++i) { - /* for each element in PEM */ - X509_INFO *tmp = sk_X509_INFO_value(inf, i); - if (tmp->x509) { - FIO_LOG_DEBUG("TLS adding certificate from PEM file."); - SSL_CTX_use_certificate(tls->ctx, tmp->x509); - } - if (tmp->x_pkey) { - FIO_LOG_DEBUG("TLS adding private key from PEM file."); - SSL_CTX_use_PrivateKey(tls->ctx, tmp->x_pkey->dec_pkey); - } - } - sk_X509_INFO_pop_free(inf, X509_INFO_free); - } else { - /* TODO: attempt DER format? */ - // X509 *c; - // EVP_PKEY *k; - // const uint8_t *pdata = (uint8_t *)&keys[ki].data; - // d2i_X509(&c, &pdata, keys[ki].len); - // pdata = (uint8_t *)&keys[ki].data; - // d2i_AutoPrivateKey(&k, &pdata, keys[ki].len); - } - BIO_free(bio); - } - } else if (keys[0].len) { - /* Self Signed Certificates, only if server name is provided. */ - SSL_CTX_use_certificate(tls->ctx, - fio_tls_create_self_signed(keys[0].data)); - SSL_CTX_use_PrivateKey(tls->ctx, fio_tls_pkey); - } - } - - /* setup ALPN support */ - if (1) { - size_t alpn_pos = 0; - /* looping twice is better than malloc fragmentation. */ - FIO_SET_FOR_LOOP(&tls->alpn, pos) { - fio_str_info_s s = fio_str_info(&pos->obj.name); - if (!s.len) - continue; - alpn_pos += s.len + 1; - } - tls->alpn_str = malloc((alpn_pos | 15) + 1); /* round up to 16 + padding */ - alpn_pos = 0; - FIO_SET_FOR_LOOP(&tls->alpn, pos) { - fio_str_info_s s = fio_str_info(&pos->obj.name); - if (!s.len) - continue; - tls->alpn_str[alpn_pos++] = (uint8_t)s.len; - memcpy(tls->alpn_str + alpn_pos, s.data, s.len); - alpn_pos += s.len; - } - tls->alpn_len = alpn_pos; - SSL_CTX_set_alpn_select_cb(tls->ctx, fio_tls_alpn_selector_cb, tls); - SSL_CTX_set_alpn_protos(tls->ctx, tls->alpn_str, tls->alpn_len); - } - - /* Peer Verification / Trust */ - if (trust_ary_count(&tls->trust)) { - /* TODO: enable peer verification */ - X509_STORE *store = X509_STORE_new(); - SSL_CTX_set_cert_store(tls->ctx, store); - SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_PEER, NULL); - /* TODO: Add each ceriticate in the PEM to the trust "store" */ - FIO_ARY_FOR(&tls->trust, pos) { - fio_str_info_s pem = fio_str_info(&pos->pem); - BIO *bio = BIO_new_mem_buf(pem.data, pem.len); - FIO_ASSERT(bio, "OpenSSL error allocating BIO."); - STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); - if (inf) { - for (int i = 0; i < sk_X509_INFO_num(inf); ++i) { - /* for each element in PEM */ - X509_INFO *tmp = sk_X509_INFO_value(inf, i); - if (tmp->x509) { - FIO_LOG_DEBUG("TLS trusting certificate from PEM file."); - X509_STORE_add_cert(store, tmp->x509); - } - if (tmp->crl) { - X509_STORE_add_crl(store, tmp->crl); - } - } - sk_X509_INFO_pop_free(inf, X509_INFO_free); - } - BIO_free(bio); - } - } - - FIO_LOG_DEBUG("(re)built TLS context for OpenSSL %p", (void *)tls); -} - -/* ***************************************************************************** -SSL/TLS RW Hooks -***************************************************************************** */ - -static void fio_tls_delayed_close(void *uuid, void *ignr_) { - fio_close((intptr_t)uuid); - (void)ignr_; -} - -/* TODO: this is an example implementation - fix for specific library. */ - -/** - * Implement reading from a file descriptor. Should behave like the file - * system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_read(intptr_t uuid, void *udata, void *buf, - size_t count) { - fio_tls_connection_s *c = udata; - ssize_t ret = SSL_read(c->ssl, buf, count); - if (ret > 0) - return ret; - ret = SSL_get_error(c->ssl, ret); - switch (ret) { - case SSL_ERROR_SSL: /* overflow */ - case SSL_ERROR_ZERO_RETURN: - return 0; /* EOF */ - case SSL_ERROR_SYSCALL: /* allow errno to inform us */ - break; /* return -1 */ - case SSL_ERROR_NONE: /* overflow */ - case SSL_ERROR_WANT_CONNECT: /* overflow */ - case SSL_ERROR_WANT_ACCEPT: /* overflow */ - case SSL_ERROR_WANT_X509_LOOKUP: /* overflow */ - case SSL_ERROR_WANT_WRITE: /* overflow */ - case SSL_ERROR_WANT_READ: /* overflow */ -#ifdef SSL_ERROR_WANT_ASYNC - case SSL_ERROR_WANT_ASYNC: /* overflow */ -#endif - default: - errno = EWOULDBLOCK; - break; - } - return -1; - (void)uuid; -} - -/** - * When implemented, this function will be called to flush any data remaining - * in the internal buffer. - * - * The function should return the number of bytes remaining in the internal - * buffer (0 is a valid response) or -1 (on error). - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_flush(intptr_t uuid, void *udata) { - (void)uuid; - (void)udata; - return 0; -} - -/** - * Implement writing to a file descriptor. Should behave like the file system - * `write` call. - * - * If an internal buffer is implemented and it is full, errno should be set to - * EWOULDBLOCK and the function should return -1. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - */ -static ssize_t fio_tls_write(intptr_t uuid, void *udata, const void *buf, - size_t count) { - fio_tls_connection_s *c = udata; - ssize_t ret = SSL_write(c->ssl, buf, count); - if (ret > 0) - return ret; - ret = SSL_get_error(c->ssl, ret); - switch (ret) { - case SSL_ERROR_SSL: /* overflow */ - case SSL_ERROR_ZERO_RETURN: - return 0; /* EOF */ - case SSL_ERROR_SYSCALL: /* allow errno to inform us */ - break; /* return -1 */ - case SSL_ERROR_NONE: /* overflow */ - case SSL_ERROR_WANT_CONNECT: /* overflow */ - case SSL_ERROR_WANT_ACCEPT: /* overflow */ - case SSL_ERROR_WANT_X509_LOOKUP: /* overflow */ - case SSL_ERROR_WANT_WRITE: /* overflow */ - case SSL_ERROR_WANT_READ: /* overflow */ -#ifdef SSL_ERROR_WANT_ASYNC - case SSL_ERROR_WANT_ASYNC: /* overflow */ -#endif - default: - errno = EWOULDBLOCK; - break; - } - return -1; - (void)uuid; -} - -/** - * The `close` callback should close the underlying socket / file descriptor. - * - * If the function returns a non-zero value, it will be called again after an - * attempt to flush the socket and any pending outgoing buffer. - * - * Note: facil.io library functions MUST NEVER be called by any r/w hook, or a - * deadlock might occur. - * */ -static ssize_t fio_tls_before_close(intptr_t uuid, void *udata) { - fio_tls_connection_s *c = udata; - SSL_shutdown(c->ssl); - return 1; - (void)uuid; -} -/** - * Called to perform cleanup after the socket was closed. - * */ -static void fio_tls_cleanup(void *udata) { - fio_tls_connection_s *c = udata; - if (!c->alpn_ok) { - alpn_select(alpn_default(c->tls), -1, c->alpn_arg); - } - SSL_free(c->ssl); - FIO_LOG_DEBUG("TLS cleanup for %p", (void *)c->uuid); - fio_tls_destroy(c->tls); /* manage reference count */ - free(udata); -} - -static fio_rw_hook_s FIO_TLS_HOOKS = { - .read = fio_tls_read, - .write = fio_tls_write, - .flush = fio_tls_flush, - .before_close = fio_tls_before_close, - .cleanup = fio_tls_cleanup, -}; - -#define FIO_TLS_HANDSHAKE_ERROR 0 -#define FIO_TLS_HANDSHAKE_OK 1 -#define FIO_TLS_HANDSHAKE_NEED_READ 2 -#define FIO_TLS_HANDSHAKE_NEED_WRITE 4 - -static size_t fio_tls_handshake(intptr_t uuid, void *udata) { - size_t status = FIO_TLS_HANDSHAKE_ERROR; - fio_tls_connection_s *c = udata; - - int ri; - if (c->is_server) { - ri = SSL_accept(c->ssl); - } else { - ri = SSL_connect(c->ssl); - } - if (ri != 1) { - ri = SSL_get_error(c->ssl, ri); - switch (ri) { - case SSL_ERROR_NONE: - // FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_NONE", - // (void *)uuid); - status = FIO_TLS_HANDSHAKE_NEED_READ | FIO_TLS_HANDSHAKE_NEED_WRITE; - return status; - case SSL_ERROR_WANT_WRITE: - // FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_WANT_WRITE", - // (void *)uuid); - status = FIO_TLS_HANDSHAKE_NEED_WRITE; - return status; - case SSL_ERROR_WANT_READ: - // FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_WANT_READ", - // (void *)uuid); - status = FIO_TLS_HANDSHAKE_NEED_READ; - return status; - case SSL_ERROR_SYSCALL: - if (errno) { - FIO_LOG_DEBUG( - "SSL_accept/SSL_connect %p error: SSL_ERROR_SYSCALL, errno: %s", - (void *)uuid, strerror(errno)); - } - break; - case SSL_ERROR_SSL: - FIO_LOG_DEBUG( - "SSL_accept/SSL_connect %p error: SSL_ERROR_SSL (non SSL attempt?)", - (void *)uuid); - break; - case SSL_ERROR_ZERO_RETURN: - FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_ZERO_RETURN", - (void *)uuid); - break; - case SSL_ERROR_WANT_CONNECT: - FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_CONNECT", - (void *)uuid); - break; - case SSL_ERROR_WANT_ACCEPT: - FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_ACCEPT", - (void *)uuid); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - FIO_LOG_DEBUG( - "SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_X509_LOOKUP", - (void *)uuid); - break; -#ifdef SSL_ERROR_WANT_ASYNC - case SSL_ERROR_WANT_ASYNC: - FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_ASYNC", - (void *)uuid); - break; -#endif -#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB - case SSL_ERROR_WANT_CLIENT_HELLO_CB: - FIO_LOG_DEBUG( - "SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_CLIENT_HELLO_CB", - (void *)uuid); - break; -#endif - default: - FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: unknown (%d).", - (void *)uuid, ri); - break; - } - fio_rw_hook_replace_unsafe(uuid, &FIO_TLS_HOOKS, udata); - fio_defer(fio_tls_delayed_close, (void *)uuid, NULL); - return status; - } - if (!c->alpn_ok) { - c->alpn_ok = 1; - if (c->is_server) { - fio_tls_alpn_fallback(c); - } else { - const unsigned char *proto; - unsigned int proto_len; - SSL_get0_alpn_selected(c->ssl, &proto, &proto_len); - alpn_s *alpn = NULL; - if (proto_len > 0) { - alpn = alpn_find(c->tls, (char *)proto, proto_len); - } - if (!alpn) { - alpn = alpn_default(c->tls); - FIO_LOG_DEBUG("ALPN missing for TLS client %p", (void *)uuid); - } - if (alpn) - FIO_LOG_DEBUG("setting ALPN %s for TLS client %p", - fio_str_data(&alpn->name), (void *)uuid); - alpn_select(alpn, c->uuid, c->alpn_arg); - } - } - if (fio_rw_hook_replace_unsafe(uuid, &FIO_TLS_HOOKS, udata) == 0) { - FIO_LOG_DEBUG("Completed TLS handshake for %p", (void *)uuid); - } else { - FIO_LOG_DEBUG("Something went wrong during TLS handshake for %p", - (void *)uuid); - return status; - } - /* make sure the connection is re-added to the reactor */ - fio_force_event(uuid, FIO_EVENT_ON_DATA); - /* log session ID for WireShark */ -#if FIO_TLS_PRINT_SECRET - if (FIO_LOG_LEVEL >= FIO_LOG_LEVEL_DEBUG) { - unsigned char buff[SSL_MAX_MASTER_KEY_LENGTH + 2]; - size_t ret = SSL_SESSION_get_master_key(SSL_get_session(c->ssl), buff, - SSL_MAX_MASTER_KEY_LENGTH + 1); - buff[ret] = 0; - unsigned char buff2[(SSL_MAX_MASTER_KEY_LENGTH + 2) << 1]; - for (size_t i = 0; i < ret; ++i) { - buff2[i] = ((buff[i] >> 4) >= 10) ? ('A' + (buff[i] >> 4) - 10) - : ('0' + (buff[i] >> 4)); - buff2[i + 1] = ((buff[i] & 15) >= 10) ? ('A' + (buff[i] & 15) - 10) - : ('0' + (buff[i] & 15)); - } - buff2[(ret << 1)] = 0; - FIO_LOG_DEBUG("OpenSSL Master Key for uuid %p:\n\t\t%s", (void *)uuid, - buff2); - } -#endif - status = FIO_TLS_HANDSHAKE_OK; - return status; -} - -static ssize_t fio_tls_read4handshake(intptr_t uuid, void *udata, void *buf, - size_t count) { - // FIO_LOG_DEBUG("TLS handshake from read %p", (void *)uuid); - size_t s = fio_tls_handshake(uuid, udata); - if (s == FIO_TLS_HANDSHAKE_OK) - return fio_tls_read(uuid, udata, buf, count); - if (!s) - return 0; - errno = EWOULDBLOCK; - return -1; -} - -static ssize_t fio_tls_write4handshake(intptr_t uuid, void *udata, - const void *buf, size_t count) { - // FIO_LOG_DEBUG("TLS handshake from write %p", (void *)uuid); - size_t s = fio_tls_handshake(uuid, udata); - if (s == FIO_TLS_HANDSHAKE_OK) - return fio_tls_write(uuid, udata, buf, count); - if (!s) - return 0; - errno = EWOULDBLOCK; - return -1; -} - -static ssize_t fio_tls_flush4handshake(intptr_t uuid, void *udata) { - // FIO_LOG_DEBUG("TLS handshake from flush %p", (void *)uuid); - size_t s = fio_tls_handshake(uuid, udata); - if (s == FIO_TLS_HANDSHAKE_OK) { - return fio_tls_flush(uuid, udata); - } - if (!s) - return 0; - errno = 0; - return s | FIO_TLS_HANDSHAKE_NEED_WRITE; -} - -static fio_rw_hook_s FIO_TLS_HANDSHAKE_HOOKS = { - .read = fio_tls_read4handshake, - .write = fio_tls_write4handshake, - .before_close = fio_tls_before_close, - .flush = fio_tls_flush4handshake, - .cleanup = fio_tls_cleanup, -}; -static inline void fio_tls_attach2uuid(intptr_t uuid, fio_tls_s *tls, - void *udata, uint8_t is_server) { - fio_atomic_add(&tls->ref, 1); - /* create SSL connection context from global context */ - fio_tls_connection_s *c = malloc(sizeof(*c)); - FIO_ASSERT_ALLOC(c); - *c = (fio_tls_connection_s){ - .alpn_arg = udata, - .tls = tls, - .uuid = uuid, - .ssl = SSL_new(tls->ctx), - .is_server = is_server, - .alpn_ok = 0, - }; - FIO_ASSERT_ALLOC(c->ssl); - /* set facil.io data in the SSL object */ - SSL_set_ex_data(c->ssl, 0, (void *)c); - /* attach socket - TODO: Switch to BIO socket */ - BIO *bio = BIO_new_socket(fio_uuid2fd(uuid), 0); - BIO_up_ref(bio); - SSL_set0_rbio(c->ssl, bio); - SSL_set0_wbio(c->ssl, bio); - /* set RW hooks */ - fio_rw_hook_set(uuid, &FIO_TLS_HANDSHAKE_HOOKS, c); - if (is_server) { - /* Server mode (accept) */ - FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (server mode).", - (void *)uuid); - SSL_set_accept_state(c->ssl); - } else { - /* Client mode (connect) */ - FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (client mode).", - (void *)uuid); - SSL_set_connect_state(c->ssl); - } - fio_force_event(uuid, FIO_EVENT_ON_READY); -} - -/* ***************************************************************************** -SSL/TLS API implementation - this can be pretty much used as is... -***************************************************************************** */ - -/** - * Creates a new SSL/TLS context / settings object with a default certificate - * (if any). - */ -fio_tls_s *FIO_TLS_WEAK fio_tls_new(const char *server_name, const char *cert, - const char *key, const char *pk_password) { - REQUIRE_LIBRARY(); - fio_tls_s *tls = calloc(sizeof(*tls), 1); - tls->ref = 1; - fio_tls_cert_add(tls, server_name, key, cert, pk_password); - return tls; -} - -/** - * Adds a certificate a new SSL/TLS context / settings object. - */ -void FIO_TLS_WEAK fio_tls_cert_add(fio_tls_s *tls, const char *server_name, - const char *cert, const char *key, - const char *pk_password) { - REQUIRE_LIBRARY(); - cert_s c = { - .private_key = FIO_STR_INIT, - .public_key = FIO_STR_INIT, - .password = FIO_STR_INIT_STATIC2(pk_password, - (pk_password ? strlen(pk_password) : 0)), - }; - if (key && cert) { - if (fio_str_readfile(&c.private_key, key, 0, 0).data == NULL) - goto file_missing; - if (fio_str_readfile(&c.public_key, cert, 0, 0).data == NULL) - goto file_missing; - cert_ary_push(&tls->sni, c); - } else if (server_name) { - /* Self-Signed TLS Certificates */ - c.private_key = FIO_STR_INIT_STATIC(server_name); - cert_ary_push(&tls->sni, c); - } - fio_tls_cert_destroy(&c); - fio_tls_build_context(tls); - return; -file_missing: - FIO_LOG_FATAL("TLS certificate file missing for either %s or %s or both.", - key, cert); - exit(-1); -} - -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - * - * The callback should accept the `uuid`, the user data pointer passed to - * either `fio_tls_accept` or `fio_tls_connect` (here: `udata_connetcion`) and - * the user data pointer passed to the `fio_tls_alpn_add` function - * (`udata_tls`). - * - * The `on_cleanup` callback will be called when the TLS object is destroyed - * (or `fio_tls_alpn_add` is called again with the same protocol name). The - * `udata_tls` argumrnt will be passed along, as is, to the callback (if set). - * - * Except for the `tls` and `protocol_name` arguments, all arguments can be - * NULL. - */ -void FIO_TLS_WEAK fio_tls_alpn_add( - fio_tls_s *tls, const char *protocol_name, - void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)) { - REQUIRE_LIBRARY(); - alpn_add(tls, protocol_name, on_selected, udata_tls, on_cleanup); - fio_tls_build_context(tls); -} - -/** - * Returns the number of registered ALPN protocol names. - * - * This could be used when deciding if protocol selection should be delegated - * to the ALPN mechanism, or whether a protocol should be immediately - * assigned. - * - * If no ALPN protocols are registered, zero (0) is returned. - */ -uintptr_t FIO_TLS_WEAK fio_tls_alpn_count(fio_tls_s *tls) { - return tls ? alpn_list_count(&tls->alpn) : 0; -} - -/** - * Adds a certificate to the "trust" list, which automatically adds a peer - * verification requirement. - * - * fio_tls_trust(tls, "google-ca.pem" ); - */ -void FIO_TLS_WEAK fio_tls_trust(fio_tls_s *tls, const char *public_cert_file) { - REQUIRE_LIBRARY(); - trust_s c = { - .pem = FIO_STR_INIT, - }; - if (!public_cert_file) - return; - if (fio_str_readfile(&c.pem, public_cert_file, 0, 0).data == NULL) - goto file_missing; - trust_ary_push(&tls->trust, c); - fio_tls_trust_destroy(&c); - fio_tls_build_context(tls); - return; -file_missing: - FIO_LOG_FATAL("TLS certificate file missing for %s ", public_cert_file); - exit(-1); -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer - * (i.e., the result of `fio_accept`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, fio_tls_s *tls, void *udata) { - REQUIRE_LIBRARY(); - fio_timeout_set(uuid, FIO_TLS_TIMEOUT); - fio_tls_attach2uuid(uuid, tls, udata, 1); -} - -/** - * Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified - * context / settings object. - * - * The `uuid` should be a socket UUID that is already connected to a peer - * (i.e., one received by a `fio_connect` specified callback `on_connect`). - * - * The `udata` is an opaque user data pointer that is passed along to the - * protocol selected (if any protocols were added using `fio_tls_alpn_add`). - */ -void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, fio_tls_s *tls, void *udata) { - REQUIRE_LIBRARY(); - fio_tls_attach2uuid(uuid, tls, udata, 0); -} - -/** - * Increase the reference count for the TLS object. - * - * Decrease with `fio_tls_destroy`. - */ -void FIO_TLS_WEAK fio_tls_dup(fio_tls_s *tls) { fio_atomic_add(&tls->ref, 1); } - -/** - * Destroys the SSL/TLS context / settings object and frees any related - * resources / memory. - */ -void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) { - if (!tls) - return; - REQUIRE_LIBRARY(); - if (fio_atomic_sub(&tls->ref, 1)) - return; - fio_tls_destroy_context(tls); - alpn_list_free(&tls->alpn); - cert_ary_free(&tls->sni); - trust_ary_free(&tls->trust); - free(tls); - FIO_LOG_DEBUG("freed TLS context %p", (void *)tls); -} - -#endif /* Library compiler flags */ -#endif diff --git a/ext/iodine/fio_tmpfile.h b/ext/iodine/fio_tmpfile.h deleted file mode 100644 index 72414b71..00000000 --- a/ext/iodine/fio_tmpfile.h +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT -*/ -#ifndef H_FIO_TMPFILE_H -/** a simple helper to create temporary files and file names */ -#define H_FIO_TMPFILE_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include - -#include -#include -#include -#include - -#ifdef __MINGW32__ -#include -#endif - -static inline int fio_tmpfile(void) { - // create a temporary file to contain the data. - int fd = 0; -#ifdef __MINGW32__ - char name_template[] = "fio"; - TCHAR temp_path[(MAX_PATH-14)]; - TCHAR temp_filename[MAX_PATH]; - GetTempPath(MAX_PATH - 14, temp_path); - GetTempFileNameA(temp_path, name_template, 0, temp_filename); - fd = _open(temp_filename, _O_CREAT | _O_RDWR); - _chmod(temp_filename, _S_IREAD | _S_IWRITE); -#elif defined(P_tmpdir) - if (P_tmpdir[sizeof(P_tmpdir) - 1] == '/') { - char name_template[] = P_tmpdir "facil_io_tmpfile_XXXXXXXX"; - fd = mkstemp(name_template); - } else { - char name_template[] = P_tmpdir "/facil_io_tmpfile_XXXXXXXX"; - fd = mkstemp(name_template); - } -#else - char name_template[] = "/tmp/facil_io_tmpfile_XXXXXXXX"; - fd = mkstemp(name_template); -#endif - return fd; -} -#endif diff --git a/ext/iodine/fiobj.h b/ext/iodine/fiobj.h deleted file mode 100644 index b3a3e08b..00000000 --- a/ext/iodine/fiobj.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#ifndef H_FIOBJ_H -#define H_FIOBJ_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifdef H_FACIL_IO_H -#include -#endif - -#if DEBUG -FIO_INLINE void fiobj_test(void) { - fprintf(stderr, "\n=== FIOBJ Tests ===\n\n"); - fiobj_test_string(); - fiobj_test_numbers(); - fiobj_test_array(); - fiobj_test_hash(); - fiobj_test_core(); - fiobj_data_test(); - fiobj_test_json(); - fiobj_mustache_test(); - fiobj_siphash_test(); - fprintf(stderr, "=== FIOBJ Done ===\n\n"); -} -#else -FIO_INLINE void fiobj_test(void) { - fprintf(stderr, "ERROR: tesing functions only defined with DEBUG=1\n"); - exit(-1); -} -#endif -#undef FIO_INLINE -#endif diff --git a/ext/iodine/fiobj4fio.h b/ext/iodine/fiobj4fio.h deleted file mode 100644 index 86415d9d..00000000 --- a/ext/iodine/fiobj4fio.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef H_FIOBJ4SOCK_H -#define H_FIOBJ4SOCK_H -/** - * Defines a helper for using fiobj with the sock library. - */ - -#include -#include - -static void fiobj4sock_dealloc(void *o) { fiobj_free((FIOBJ)o); } - -/** send a FIOBJ object through a socket. */ -static inline __attribute__((unused)) ssize_t fiobj_send_free(intptr_t uuid, - FIOBJ o) { - fio_str_info_s s = fiobj_obj2cstr(o); - return fio_write2(uuid, .data.buffer = (void *)(o), - .offset = (uintptr_t)(((intptr_t)s.data) - ((intptr_t)(o))), - .length = s.len, .after.dealloc = fiobj4sock_dealloc); -} - -#endif diff --git a/ext/iodine/fiobj_ary.c b/ext/iodine/fiobj_ary.c deleted file mode 100644 index 2517dc86..00000000 --- a/ext/iodine/fiobj_ary.c +++ /dev/null @@ -1,333 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#include -#include - -#define FIO_ARY_NAME fio_ary__ -#define FIO_ARY_TYPE FIOBJ -#define FIO_ARY_TYPE_INVALID FIOBJ_INVALID -#define FIO_ARY_TYPE_COMPARE(a, b) (fiobj_iseq((a), (b))) -#define FIO_ARY_INVALID FIOBJ_INVALID -#include - -#include - -/* ***************************************************************************** -Array Type -***************************************************************************** */ - -typedef struct { - fiobj_object_header_s head; - fio_ary___s ary; -} fiobj_ary_s; - -#define obj2ary(o) ((fiobj_ary_s *)(o)) - -/* ***************************************************************************** -VTable -***************************************************************************** */ - -static void fiobj_ary_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg) { - FIO_ARY_FOR((&obj2ary(o)->ary), i) { task(*i, arg); } - fio_ary___free(&obj2ary(o)->ary); - fio_free(FIOBJ2PTR(o)); -} - -static size_t fiobj_ary_each1(FIOBJ o, size_t start_at, - int (*task)(FIOBJ obj, void *arg), void *arg) { - return fio_ary___each(&obj2ary(o)->ary, start_at, task, arg); -} - -static size_t fiobj_ary_is_eq(const FIOBJ self, const FIOBJ other) { - fio_ary___s *a = &obj2ary(self)->ary; - fio_ary___s *b = &obj2ary(other)->ary; - if (fio_ary___count(a) != fio_ary___count(b)) - return 0; - return 1; -} - -/** Returns the number of elements in the Array. */ -size_t fiobj_ary_count(const FIOBJ ary) { - assert(FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - return fio_ary___count(&obj2ary(ary)->ary); -} - -static size_t fiobj_ary_is_true(const FIOBJ ary) { - return fiobj_ary_count(ary) > 0; -} - -fio_str_info_s fiobject___noop_to_str(const FIOBJ o); -intptr_t fiobject___noop_to_i(const FIOBJ o); -double fiobject___noop_to_f(const FIOBJ o); - -const fiobj_object_vtable_s FIOBJECT_VTABLE_ARRAY = { - .class_name = "Array", - .dealloc = fiobj_ary_dealloc, - .is_eq = fiobj_ary_is_eq, - .is_true = fiobj_ary_is_true, - .count = fiobj_ary_count, - .each = fiobj_ary_each1, - .to_i = fiobject___noop_to_i, - .to_f = fiobject___noop_to_f, - .to_str = fiobject___noop_to_str, -}; - -/* ***************************************************************************** -Allocation -***************************************************************************** */ - -static inline FIOBJ fiobj_ary_alloc(size_t capa) { - fiobj_ary_s *ary = fio_malloc(sizeof(*ary)); - if (!ary) { - perror("ERROR: fiobj array couldn't allocate memory"); - exit(errno); - } - *ary = (fiobj_ary_s){ - .head = - { - .ref = 1, - .type = FIOBJ_T_ARRAY, - }, - }; - if (capa) - fio_ary_____require_on_top(&ary->ary, capa); - return (FIOBJ)ary; -} - -/** Creates a mutable empty Array object. Use `fiobj_free` when done. */ -FIOBJ fiobj_ary_new(void) { return fiobj_ary_alloc(0); } -/** Creates a mutable empty Array object with the requested capacity. */ -FIOBJ fiobj_ary_new2(size_t capa) { return fiobj_ary_alloc(capa); } - -/* ***************************************************************************** -Array direct entry access API -***************************************************************************** */ - -/** Returns the current, temporary, array capacity (it's dynamic). */ -size_t fiobj_ary_capa(FIOBJ ary) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - return fio_ary___capa(&obj2ary(ary)->ary); -} - -/** - * Returns a TEMPORARY pointer to the beginning of the array. - * - * This pointer can be used for sorting and other direct access operations as - * long as no other actions (insertion/deletion) are performed on the array. - */ -FIOBJ *fiobj_ary2ptr(FIOBJ ary) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - return (FIOBJ *)(obj2ary(ary)->ary.arry + obj2ary(ary)->ary.start); -} - -/** - * Returns a temporary object owned by the Array. - * - * Negative values are retrieved from the end of the array. i.e., `-1` - * is the last item. - */ -FIOBJ fiobj_ary_index(FIOBJ ary, int64_t pos) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - return fio_ary___get(&obj2ary(ary)->ary, pos); -} - -/** - * Sets an object at the requested position. - */ -void fiobj_ary_set(FIOBJ ary, FIOBJ obj, int64_t pos) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - FIOBJ old = FIOBJ_INVALID; - fio_ary___set(&obj2ary(ary)->ary, pos, obj, &old); - fiobj_free(old); -} - -/* ***************************************************************************** -Array push / shift API -***************************************************************************** */ - -/** - * Pushes an object to the end of the Array. - */ -void fiobj_ary_push(FIOBJ ary, FIOBJ obj) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - fio_ary___push(&obj2ary(ary)->ary, obj); -} - -/** Pops an object from the end of the Array. */ -FIOBJ fiobj_ary_pop(FIOBJ ary) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - FIOBJ ret = FIOBJ_INVALID; - fio_ary___pop(&obj2ary(ary)->ary, &ret); - return ret; -} - -/** - * Unshifts an object to the beginning of the Array. This could be - * expensive. - */ -void fiobj_ary_unshift(FIOBJ ary, FIOBJ obj) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - fio_ary___unshift(&obj2ary(ary)->ary, obj); -} - -/** Shifts an object from the beginning of the Array. */ -FIOBJ fiobj_ary_shift(FIOBJ ary) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - FIOBJ ret = FIOBJ_INVALID; - fio_ary___shift(&obj2ary(ary)->ary, &ret); - return ret; -} - -/* ***************************************************************************** -Array Find / Remove / Replace -***************************************************************************** */ - -/** - * Replaces the object at a specific position, returning the old object - - * remember to `fiobj_free` the old object. - */ -FIOBJ fiobj_ary_replace(FIOBJ ary, FIOBJ obj, int64_t pos) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - FIOBJ old = fiobj_ary_index(ary, pos); - fiobj_ary_set(ary, obj, pos); - return old; -} - -/** - * Finds the index of a specifide object (if any). Returns -1 if the object - * isn't found. - */ -int64_t fiobj_ary_find(FIOBJ ary, FIOBJ data) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - return (int64_t)fio_ary___find(&obj2ary(ary)->ary, data); -} - -/** - * Removes the object at the index (if valid), changing the index of any - * following objects. - * - * Returns 0 on success or -1 (if no object or out of bounds). - */ -int fiobj_ary_remove(FIOBJ ary, int64_t pos) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - FIOBJ old = FIOBJ_INVALID; - if (fio_ary___remove(&obj2ary(ary)->ary, (intptr_t)pos, &old)) { - return -1; - } - fiobj_free(old); - return 0; -} - -/** - * Removes the first instance of an object from the Array (if any), changing the - * index of any following objects. - * - * Returns 0 on success or -1 (if the object wasn't found). - */ -int fiobj_ary_remove2(FIOBJ ary, FIOBJ data) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - if (-1 == fio_ary___remove2(&obj2ary(ary)->ary, data, &data)) { - return -1; - } - fiobj_free(data); - return 0; -} - -/* ***************************************************************************** -Array compacting (untested) -***************************************************************************** */ - -/** - * Removes any NULL *pointers* from an Array, keeping all Objects (including - * explicit NULL objects) in the array. - * - * This action is O(n) where n in the length of the array. - * It could get expensive. - */ -void fiobj_ary_compact(FIOBJ ary) { - assert(ary && FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)); - fio_ary___compact(&obj2ary(ary)->ary); -} - -/* ***************************************************************************** -Simple Tests -***************************************************************************** */ - -#if DEBUG -void fiobj_test_array(void) { - fprintf(stderr, "=== Testing Array\n"); -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, "* " __VA_ARGS__); \ - fprintf(stderr, "Testing failed.\n"); \ - exit(-1); \ - } - FIOBJ a = fiobj_ary_new2(4); - TEST_ASSERT(FIOBJ_TYPE_IS(a, FIOBJ_T_ARRAY), "Array type isn't an array!\n"); - TEST_ASSERT(fiobj_ary_capa(a) > 4, "Array capacity ignored!\n"); - fiobj_ary_push(a, fiobj_null()); - TEST_ASSERT(fiobj_ary2ptr(a)[0] == fiobj_null(), - "Array direct access failed!\n"); - fiobj_ary_push(a, fiobj_true()); - fiobj_ary_push(a, fiobj_false()); - TEST_ASSERT(fiobj_ary_count(a) == 3, "Array count isn't 3\n"); - fiobj_ary_set(a, fiobj_true(), 63); - TEST_ASSERT(fiobj_ary_count(a) == 64, "Array count isn't 64 (%zu)\n", - fiobj_ary_count(a)); - TEST_ASSERT(fiobj_ary_index(a, 0) == fiobj_null(), - "Array index retrival error for fiobj_null\n"); - TEST_ASSERT(fiobj_ary_index(a, 1) == fiobj_true(), - "Array index retrival error for fiobj_true\n"); - TEST_ASSERT(fiobj_ary_index(a, 2) == fiobj_false(), - "Array index retrival error for fiobj_false\n"); - TEST_ASSERT(fiobj_ary_index(a, 3) == 0, - "Array index retrival error for NULL\n"); - TEST_ASSERT(fiobj_ary_index(a, 63) == fiobj_true(), - "Array index retrival error for index 63\n"); - TEST_ASSERT(fiobj_ary_index(a, -1) == fiobj_true(), - "Array index retrival error for index -1\n"); - fiobj_ary_compact(a); - TEST_ASSERT(fiobj_ary_index(a, -1) == fiobj_true(), - "Array index retrival error for index -1\n"); - TEST_ASSERT(fiobj_ary_count(a) == 4, "Array compact error\n"); - fiobj_ary_unshift(a, fiobj_false()); - TEST_ASSERT(fiobj_ary_count(a) == 5, "Array unshift error\n"); - TEST_ASSERT(fiobj_ary_shift(a) == fiobj_false(), "Array shift value error\n"); - TEST_ASSERT(fiobj_ary_replace(a, fiobj_true(), -2) == fiobj_false(), - "Array replace didn't return correct value\n"); - - FIO_ARY_FOR(&obj2ary(a)->ary, pos) { - if (*pos) { - fprintf(stderr, "%lu) %s\n", pos - obj2ary(a)->ary.arry, - fiobj_obj2cstr(*pos).data); - } - } - - TEST_ASSERT(fiobj_ary_index(a, -2) == fiobj_true(), - "Array index retrival error for index -2 (should be true)\n"); - TEST_ASSERT(fiobj_ary_count(a) == 4, "Array size error\n"); - fiobj_ary_remove(a, -2); - TEST_ASSERT(fiobj_ary_count(a) == 3, "Array remove error\n"); - - FIO_ARY_FOR(&obj2ary(a)->ary, pos) { - if (*pos) { - fprintf(stderr, "%lu) %s\n", pos - obj2ary(a)->ary.arry, - fiobj_obj2cstr(*pos).data); - } - } - - fiobj_ary_remove2(a, fiobj_true()); - TEST_ASSERT(fiobj_ary_count(a) == 2, "Array remove2 error\n"); - TEST_ASSERT(fiobj_ary_index(a, 0) == fiobj_null(), - "Array index 0 should be null - %s\n", - fiobj_obj2cstr(fiobj_ary_index(a, 0)).data); - TEST_ASSERT(fiobj_ary_index(a, 1) == fiobj_true(), - "Array index 0 should be true - %s\n", - fiobj_obj2cstr(fiobj_ary_index(a, 0)).data); - - fiobj_free(a); - fprintf(stderr, "* passed.\n"); -} -#endif diff --git a/ext/iodine/fiobj_ary.h b/ext/iodine/fiobj_ary.h deleted file mode 100644 index 9f741a60..00000000 --- a/ext/iodine/fiobj_ary.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef FIOBJ_ARRAY_H -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -/** -A dynamic Array type for the fiobj_s dynamic type system. -*/ -#define FIOBJ_ARRAY_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -Array creation API -***************************************************************************** */ - -/** Creates a mutable empty Array object. Use `fiobj_free` when done. */ -FIOBJ fiobj_ary_new(void); - -/** Creates a mutable empty Array object with the requested capacity. */ -FIOBJ fiobj_ary_new2(size_t capa); - -/* ***************************************************************************** -Array direct entry access API -***************************************************************************** */ - -/** Returns the number of elements in the Array. */ -size_t fiobj_ary_count(FIOBJ ary); - -/** Returns the current, temporary, array capacity (it's dynamic). */ -size_t fiobj_ary_capa(FIOBJ ary); - -/** - * Returns a TEMPORARY pointer to the beginning of the array. - * - * This pointer can be used for sorting and other direct access operations as - * long as no other actions (insertion/deletion) are performed on the array. - */ -FIOBJ *fiobj_ary2ptr(FIOBJ ary); - -/** - * Returns a temporary object owned by the Array. - * - * Wrap this function call within `fiobj_dup` to get a persistent handle. i.e.: - * - * fiobj_dup(fiobj_ary_index(array, 0)); - * - * Negative values are retrieved from the end of the array. i.e., `-1` - * is the last item. - */ -FIOBJ fiobj_ary_index(FIOBJ ary, int64_t pos); -/** alias for `fiobj_ary_index` */ -#define fiobj_ary_entry(a, p) fiobj_ary_index((a), (p)) - -/** - * Sets an object at the requested position. - */ -void fiobj_ary_set(FIOBJ ary, FIOBJ obj, int64_t pos); - -/* ***************************************************************************** -Array push / shift API -***************************************************************************** */ - -/** - * Pushes an object to the end of the Array. - */ -void fiobj_ary_push(FIOBJ ary, FIOBJ obj); - -/** Pops an object from the end of the Array. */ -FIOBJ fiobj_ary_pop(FIOBJ ary); - -/** - * Unshifts an object to the beginning of the Array. This could be - * expensive. - */ -void fiobj_ary_unshift(FIOBJ ary, FIOBJ obj); - -/** Shifts an object from the beginning of the Array. */ -FIOBJ fiobj_ary_shift(FIOBJ ary); - -/* ***************************************************************************** -Array Find / Remove / Replace -***************************************************************************** */ - -/** - * Replaces the object at a specific position, returning the old object - - * remember to `fiobj_free` the old object. - */ -FIOBJ fiobj_ary_replace(FIOBJ ary, FIOBJ obj, int64_t pos); - -/** - * Finds the index of a specifide object (if any). Returns -1 if the object - * isn't found. - */ -int64_t fiobj_ary_find(FIOBJ ary, FIOBJ data); - -/** - * Removes the object at the index (if valid), changing the index of any - * following objects. - * - * Returns 0 on success or -1 (if no object or out of bounds). - */ -int fiobj_ary_remove(FIOBJ ary, int64_t pos); - -/** - * Removes the first instance of an object from the Array (if any), changing the - * index of any following objects. - * - * Returns 0 on success or -1 (if the object wasn't found). - */ -int fiobj_ary_remove2(FIOBJ ary, FIOBJ data); - -/* ***************************************************************************** -Array compacting (untested) -***************************************************************************** */ - -/** - * Removes any NULL *pointers* from an Array, keeping all Objects (including - * explicit NULL objects) in the array. - * - * This action is O(n) where n in the length of the array. - * It could get expensive. - */ -void fiobj_ary_compact(FIOBJ ary); - -#if DEBUG -void fiobj_test_array(void); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/ext/iodine/fiobj_data.c b/ext/iodine/fiobj_data.c deleted file mode 100644 index dcaa147b..00000000 --- a/ext/iodine/fiobj_data.c +++ /dev/null @@ -1,1185 +0,0 @@ -#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) || \ - defined(__CYGWIN__) || defined(__MINGW32__) /* require POSIX */ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -/** - * A dynamic type for reading / writing to a local file, a temporary file or an - * in-memory string. - * - * Supports basic reak, write, seek, puts and gets operations. - * - * Writing is always performed at the end of the stream / memory buffer, - * ignoring the current seek position. - */ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -/* ***************************************************************************** -Numbers Type -***************************************************************************** */ - -typedef struct { - fiobj_object_header_s head; - uint8_t *buffer; /* reader buffer */ - union { - FIOBJ parent; - void (*dealloc)(void *); /* buffer deallocation function */ - size_t fpos; /* the file reader's position */ - } source; - size_t capa; /* total buffer capacity / slice offset */ - size_t len; /* length of valid data in buffer */ - size_t pos; /* position of reader */ - int fd; /* file descriptor (-1 if invalid). */ -} fiobj_data_s; - -#define obj2io(o) ((fiobj_data_s *)(o)) - -/* ***************************************************************************** -Object required VTable and functions -***************************************************************************** */ - -#define REQUIRE_MEM(mem) \ - do { \ - if ((mem) == NULL) { \ - perror("FATAL ERROR: fiobj IO couldn't allocate memory"); \ - exit(errno); \ - } \ - } while (0) - -static void fiobj_data_copy_buffer(FIOBJ o) { - obj2io(o)->capa = (((obj2io(o)->len) >> 12) + 1) << 12; - void *tmp = fio_malloc(obj2io(o)->capa); - REQUIRE_MEM(tmp); - memcpy(tmp, obj2io(o)->buffer, obj2io(o)->len); - if (obj2io(o)->source.dealloc) - obj2io(o)->source.dealloc(obj2io(o)->buffer); - obj2io(o)->source.dealloc = fio_free; - obj2io(o)->buffer = tmp; -} - -static void fiobj_data_copy_parent(FIOBJ o) { - switch (obj2io(obj2io(o)->source.parent)->fd) { - case -1: - obj2io(o)->buffer = fio_malloc(obj2io(o)->len + 1); - FIO_ASSERT_ALLOC(obj2io(o)->buffer); - memcpy(obj2io(o)->buffer, - obj2io(obj2io(o)->source.parent)->buffer + obj2io(o)->capa, - obj2io(o)->len); - obj2io(o)->buffer[obj2io(o)->len] = 0; - obj2io(o)->capa = obj2io(o)->len; - obj2io(o)->fd = -1; - fiobj_free(obj2io(o)->source.parent); - obj2io(o)->source.dealloc = fio_free; - return; - default: - obj2io(o)->fd = fio_tmpfile(); - if (obj2io(o)->fd < 0) { - perror("FATAL ERROR: (fiobj_data) can't create temporary file"); - exit(errno); - } - fio_str_info_s data; - size_t pos = 0; - do { - ssize_t written; - data = fiobj_data_pread(obj2io(o)->source.parent, pos + obj2io(o)->capa, - 4096); - if (data.len + pos > obj2io(o)->len) - data.len = obj2io(o)->len - pos; - retry_int: -#ifdef __MINGW32__ - written = pwrite(obj2io(o)->fd, data.data, data.len, 0); -#else - written = write(obj2io(o)->fd, data.data, data.len); -#endif - if (written < 0) { - if (errno == EINTR) - goto retry_int; - perror("FATAL ERROR: (fiobj_data) can't write to temporary file"); - exit(errno); - } - pos += written; - } while (data.len == 4096); - fiobj_free(obj2io(o)->source.parent); - obj2io(o)->capa = 0; - obj2io(o)->len = pos; - obj2io(o)->source.fpos = obj2io(o)->pos; - obj2io(o)->pos = 0; - obj2io(o)->buffer = NULL; - break; - } -} - -static inline void fiobj_data_pre_write(FIOBJ o, uintptr_t length) { - switch (obj2io(o)->fd) { - case -1: - if (obj2io(o)->source.dealloc != fio_free) { - fiobj_data_copy_buffer(o); - } - break; - case -2: - fiobj_data_copy_parent(o); - break; - } - if (obj2io(o)->capa >= obj2io(o)->len + length) - return; - /* add rounded pages (4096) to capacity */ - obj2io(o)->capa = (((obj2io(o)->len + length) >> 12) + 1) << 12; - obj2io(o)->buffer = fio_realloc(obj2io(o)->buffer, obj2io(o)->capa); - REQUIRE_MEM(obj2io(o)->buffer); -} - -static inline int64_t fiobj_data_get_fd_size(const FIOBJ o) { - struct stat stat; -retry: - if (fstat(obj2io(o)->fd, &stat)) { - if (errno == EINTR) - goto retry; - return -1; - } - return stat.st_size; -} - -static FIOBJ fiobj_data_alloc(void *buffer, int fd) { - fiobj_data_s *io = fio_malloc(sizeof(*io)); - REQUIRE_MEM(io); - *io = (fiobj_data_s){ - .head = {.ref = 1, .type = FIOBJ_T_DATA}, - .buffer = buffer, - .fd = fd, - }; - return (FIOBJ)io; -} - -static void fiobj_data_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), - void *arg) { - switch (obj2io(o)->fd) { - case -1: - if (obj2io(o)->source.dealloc && obj2io(o)->buffer) - obj2io(o)->source.dealloc(obj2io(o)->buffer); - break; - case -2: - fiobj_free(obj2io(o)->source.parent); - break; - default: - close(obj2io(o)->fd); - fio_free(obj2io(o)->buffer); - break; - } - fio_free((void *)o); - (void)task; - (void)arg; -} - -static intptr_t fiobj_data_i(const FIOBJ o) { - switch (obj2io(o)->fd) { - case -1: - case -2: - return obj2io(o)->len; - break; - default: - return fiobj_data_get_fd_size(o); - } -} - -static size_t fiobj_data_is_true(const FIOBJ o) { return fiobj_data_i(o) > 0; } - -static fio_str_info_s fio_io2str(const FIOBJ o) { - switch (obj2io(o)->fd) { - case -1: - return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, - .len = obj2io(o)->len}; - break; - case -2: - return fiobj_data_pread(obj2io(o)->source.parent, obj2io(o)->capa, - obj2io(o)->len); - break; - } - int64_t i = fiobj_data_get_fd_size(o); - if (i <= 0) - return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, - .len = obj2io(o)->len}; - obj2io(o)->len = 0; - obj2io(o)->pos = 0; - fiobj_data_pre_write((FIOBJ)o, i + 1); - if (pread(obj2io(o)->fd, obj2io(o)->buffer, i, 0) != i) - return (fio_str_info_s){.data = NULL, .len = 0}; - obj2io(o)->buffer[i] = 0; - return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, .len = i}; -} - -static size_t fiobj_data_iseq(const FIOBJ self, const FIOBJ other) { - int64_t len; - return ((len = fiobj_data_i(self)) == fiobj_data_i(other) && - !memcmp(fio_io2str(self).data, fio_io2str(other).data, (size_t)len)); -} - -uintptr_t fiobject___noop_count(FIOBJ o); -double fiobject___noop_to_f(FIOBJ o); - -const fiobj_object_vtable_s FIOBJECT_VTABLE_DATA = { - .class_name = "IO", - .dealloc = fiobj_data_dealloc, - .to_i = fiobj_data_i, - .to_str = fio_io2str, - .is_eq = fiobj_data_iseq, - .is_true = fiobj_data_is_true, - .to_f = fiobject___noop_to_f, - .count = fiobject___noop_count, -}; - -/* ***************************************************************************** -Seeking for characters in a string -***************************************************************************** */ - -#if FIO_MEMCHAR - -/** - * This seems to be faster on some systems, especially for smaller distances. - * - * On newer systems, `memchr` should be faster. - */ -static inline int swallow_ch(uint8_t **buffer, register uint8_t *const limit, - const uint8_t c) { - if (**buffer == c) - return 1; - -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) - /* too short for this mess */ - if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7))) - goto finish; - - /* align memory */ - { - const uint8_t *alignment = - (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8); - if (limit >= alignment) { - while (*buffer < alignment) { - if (**buffer == c) { - (*buffer)++; - return 1; - } - *buffer += 1; - } - } - } - const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7)); -#else - const uint8_t *limit64 = (uint8_t *)limit - 7; -#endif - uint64_t wanted1 = 0x0101010101010101ULL * c; - for (; *buffer < limit64; *buffer += 8) { - const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1); - const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu; - const uint64_t t1 = (eq1 & 0x8080808080808080llu); - if ((t0 & t1)) { - break; - } - } -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) -finish: -#endif - while (*buffer < limit) { - if (**buffer == c) { - (*buffer)++; - return 1; - } - (*buffer)++; - } - - return 0; -} -#else - -static inline int swallow_ch(uint8_t **buffer, uint8_t *const limit, - const uint8_t c) { - if (limit - *buffer == 0) - return 0; - void *tmp = memchr(*buffer, c, limit - (*buffer)); - if (tmp) { - *buffer = tmp; - (*buffer)++; - return 1; - } - *buffer = (uint8_t *)limit; - return 0; -} - -#endif - -/* ***************************************************************************** -Creating the IO object -***************************************************************************** */ - -/** Creates a new local in-memory IO object */ -FIOBJ fiobj_data_newstr(void) { - FIOBJ o = fiobj_data_alloc(fio_malloc(4096), -1); - REQUIRE_MEM(obj2io(o)->buffer); - obj2io(o)->capa = 4096; - obj2io(o)->source.dealloc = fio_free; - return o; -} - -/** - * Creates a IO object from an existing buffer. The buffer will be deallocated - * using the provided `dealloc` function pointer. Use a NULL `dealloc` function - * pointer if the buffer is static and shouldn't be freed. - */ -FIOBJ fiobj_data_newstr2(void *buffer, uintptr_t length, - void (*dealloc)(void *)) { - FIOBJ o = fiobj_data_alloc(buffer, -1); - obj2io(o)->capa = length; - obj2io(o)->len = length; - obj2io(o)->source.dealloc = dealloc; - return o; -} - -/** Creates a new local file IO object */ -FIOBJ fiobj_data_newfd(int fd) { - FIOBJ o = fiobj_data_alloc(fio_malloc(4096), fd); - REQUIRE_MEM(obj2io(o)->buffer); - obj2io(o)->source.fpos = 0; - return o; -} - -/** Creates a new local tempfile IO object */ -FIOBJ fiobj_data_newtmpfile(void) { - // create a temporary file to contain the data. - int fd = fio_tmpfile(); - if (fd == -1) - return 0; - return fiobj_data_newfd(fd); -} - -/** Creates a slice from an existing Data object. */ -FIOBJ fiobj_data_slice(FIOBJ parent, intptr_t offset, uintptr_t length) { - /* cut from the end */ - if (offset < 0) { - size_t parent_len = fiobj_data_len(parent); - offset = parent_len + 1 + offset; - } - if (offset < 0) - offset = 0; - while (obj2io(parent)->fd == -2) { - /* don't slice a slice... climb the parent chain. */ - offset += obj2io(parent)->capa; - parent = obj2io(parent)->source.parent; - } - size_t parent_len = fiobj_data_len(parent); - if (parent_len <= (size_t)offset) { - length = 0; - offset = parent_len; - } else if (parent_len < offset + length) { - length = parent_len - offset; - } - /* make the object */ - FIOBJ o = fiobj_data_alloc(NULL, -2); - obj2io(o)->capa = offset; - obj2io(o)->len = length; - obj2io(o)->source.parent = fiobj_dup(parent); - return o; -} - -/* ***************************************************************************** -Saving the IO object -***************************************************************************** */ - -static int fiobj_data_save_str(FIOBJ o, const char *filename) { - int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); - if (target == -1) - return -1; - errno = 0; - size_t total = 0; - do { - ssize_t act = - write(target, obj2io(o)->buffer + total, obj2io(o)->len - total); - if (act < 0) - goto error; - total += act; - } while (total < obj2io(o)->len); - close(target); - return 0; -error: - close(target); - unlink(filename); - return -1; -} - -static int fiobj_data_save_file(FIOBJ o, const char *filename) { - int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); - if (target == -1) - return -1; - errno = 0; - char buf[1024]; - size_t total = 0; - do { - ssize_t act = pread(obj2io(o)->fd, buf, 1024, total); - if (act == 0) - break; - if (act < 0) - goto error; - ssize_t act2 = write(target, buf, act); - if (act2 < act) - goto error; - total += act2; - } while (1); - close(target); - return 0; -error: - close(target); - unlink(filename); - return -1; -} - -static int fiobj_data_save_slice(FIOBJ o, const char *filename) { - int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); - if (target == -1) - return -1; - errno = 0; - fio_str_info_s tmp; - size_t total = 0; - do { - tmp = fiobj_data_pread(obj2io(o)->source.parent, obj2io(o)->capa + total, - 4096); - if (tmp.len == 0) - break; - if (total + tmp.len > obj2io(o)->len) - tmp.len = obj2io(o)->len - total; - if (tmp.len) { - ssize_t act2 = write(target, tmp.data, tmp.len); - if (act2 < 0 || (size_t)act2 < tmp.len) - goto error; - total += act2; - } - } while (tmp.len == 4096); - close(target); - return 0; -error: - close(target); - unlink(filename); - return -1; -} - -/** Creates a new local file IO object */ -int fiobj_data_save(FIOBJ o, const char *filename) { - switch (obj2io(o)->fd) { - case -1: - return fiobj_data_save_str(o, filename); - break; - case -2: - return fiobj_data_save_slice(o, filename); - break; - default: - return fiobj_data_save_file(o, filename); - } -} - -/* ***************************************************************************** -Reading API -***************************************************************************** */ - -/** Reads up to `length` bytes */ -static fio_str_info_s fiobj_data_read_str(FIOBJ io, intptr_t length) { - if (obj2io(io)->pos == obj2io(io)->len) { - /* EOF */ - return (fio_str_info_s){.data = NULL, .len = 0}; - } - - if (length <= 0) { - /* read to EOF - length */ - length = (obj2io(io)->len - obj2io(io)->pos) + length; - } - - if (length <= 0) { - /* We are at EOF - length or beyond */ - return (fio_str_info_s){.data = NULL, .len = 0}; - } - - /* reading length bytes */ - register size_t pos = obj2io(io)->pos; - obj2io(io)->pos = pos + length; - if (obj2io(io)->pos > obj2io(io)->len) - obj2io(io)->pos = obj2io(io)->len; - return (fio_str_info_s){ - .data = (char *)(obj2io(io)->buffer + pos), - .len = (obj2io(io)->pos - pos), - }; -} - -/** Reads up to `length` bytes */ -static fio_str_info_s fiobj_data_read_slice(FIOBJ io, intptr_t length) { - if (obj2io(io)->pos == obj2io(io)->len) { - /* EOF */ - return (fio_str_info_s){.data = NULL, .len = 0}; - } - if (length <= 0) { - /* read to EOF - length */ - length = (obj2io(io)->len - obj2io(io)->pos) + length; - } - - if (length <= 0) { - /* We are at EOF - length or beyond */ - return (fio_str_info_s){.data = NULL, .len = 0}; - } - register size_t pos = obj2io(io)->pos; - obj2io(io)->pos = pos + length; - if (obj2io(io)->pos > obj2io(io)->len) - obj2io(io)->pos = obj2io(io)->len; - return fiobj_data_pread(obj2io(io)->source.parent, pos + obj2io(io)->capa, - (obj2io(io)->pos - pos)); -} - -/** Reads up to `length` bytes */ -static fio_str_info_s fiobj_data_read_file(FIOBJ io, intptr_t length) { - uintptr_t fsize = fiobj_data_get_fd_size(io); - - if (length <= 0) { - /* read to EOF - length */ - length = (fsize - obj2io(io)->source.fpos) + length; - } - - if (length <= 0) { - /* We are at EOF - length or beyond */ - errno = 0; - return (fio_str_info_s){.data = NULL, .len = 0}; - } - - /* reading length bytes */ - if (length + obj2io(io)->pos <= obj2io(io)->len) { - /* the data already exists in the buffer */ - // fprintf(stderr, "in_buffer...\n"); - fio_str_info_s data = {.data = - (char *)(obj2io(io)->buffer + obj2io(io)->pos), - .len = (uintptr_t)length}; - obj2io(io)->pos += length; - obj2io(io)->source.fpos += length; - return data; - } else { - /* read the data into the buffer - internal counting gets invalidated */ - // fprintf(stderr, "populate buffer...\n"); - obj2io(io)->len = 0; - obj2io(io)->pos = 0; - fiobj_data_pre_write(io, length); - ssize_t l; - retry_int: - l = pread(obj2io(io)->fd, obj2io(io)->buffer, length, - obj2io(io)->source.fpos); - if (l == -1 && errno == EINTR) - goto retry_int; - if (l == -1 || l == 0) - return (fio_str_info_s){.data = NULL, .len = 0}; - obj2io(io)->source.fpos += l; - return (fio_str_info_s){.data = (char *)obj2io(io)->buffer, .len = l}; - } -} - -/** - * Reads up to `length` bytes and returns a temporary(!) C string object. - * - * The C string object will be invalidate the next time a function call to the - * IO object is made. - */ -fio_str_info_s fiobj_data_read(FIOBJ io, intptr_t length) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { - errno = EFAULT; - return (fio_str_info_s){.data = NULL, .len = 0}; - } - errno = 0; - switch (obj2io(io)->fd) { - case -1: - return fiobj_data_read_str(io, length); - break; - case -2: - return fiobj_data_read_slice(io, length); - break; - default: - return fiobj_data_read_file(io, length); - } -} - -/* ***************************************************************************** -Tokenize (read2ch) -***************************************************************************** */ - -static fio_str_info_s fiobj_data_read2ch_str(FIOBJ io, uint8_t token) { - if (obj2io(io)->pos == obj2io(io)->len) /* EOF */ - return (fio_str_info_s){.data = NULL, .len = 0}; - - uint8_t *pos = obj2io(io)->buffer + obj2io(io)->pos; - uint8_t *lim = obj2io(io)->buffer + obj2io(io)->len; - swallow_ch(&pos, lim, token); - fio_str_info_s ret = (fio_str_info_s){ - .data = (char *)obj2io(io)->buffer + obj2io(io)->pos, - .len = (uintptr_t)(pos - obj2io(io)->buffer) - obj2io(io)->pos, - }; - obj2io(io)->pos = (uintptr_t)(pos - obj2io(io)->buffer); - return ret; -} - -static fio_str_info_s fiobj_data_read2ch_slice(FIOBJ io, uint8_t token) { - if (obj2io(io)->pos == obj2io(io)->len) /* EOF */ - return (fio_str_info_s){.data = NULL, .len = 0}; - size_t old_pos = obj2io(obj2io(io)->source.parent)->pos; - obj2io(obj2io(io)->source.parent)->pos = obj2io(io)->capa + obj2io(io)->pos; - fio_str_info_s tmp = fiobj_data_read2ch(obj2io(io)->source.parent, token); - obj2io(obj2io(io)->source.parent)->pos = old_pos; - if (tmp.len + obj2io(io)->pos > obj2io(io)->len) { - /* EOF */ - tmp.len = obj2io(io)->len - obj2io(io)->pos; - obj2io(io)->pos = obj2io(io)->len; - return tmp; - } - return tmp; -} - -static fio_str_info_s fiobj_data_read2ch_file(FIOBJ io, uint8_t token) { - uint8_t *pos = obj2io(io)->buffer + obj2io(io)->pos; - uint8_t *lim = obj2io(io)->buffer + obj2io(io)->len; - if (pos != lim && swallow_ch(&pos, lim, token)) { - /* newline found in existing buffer */ - const uintptr_t delta = - (uintptr_t)(pos - (obj2io(io)->buffer + obj2io(io)->pos)); - obj2io(io)->pos += delta; - obj2io(io)->source.fpos += delta; - return (fio_str_info_s){ - .data = - (char *)(delta ? ((obj2io(io)->buffer + obj2io(io)->pos) - delta) - : NULL), - .len = delta, - }; - } - - obj2io(io)->pos = 0; - obj2io(io)->len = 0; - - while (1) { - ssize_t tmp; - fiobj_data_pre_write(io, 4096); /* read a page at a time */ - retry_int: - tmp = pread(obj2io(io)->fd, obj2io(io)->buffer + obj2io(io)->len, 4096, - obj2io(io)->source.fpos + obj2io(io)->len); - if (tmp < 0 && errno == EINTR) - goto retry_int; - if (tmp < 0 || (tmp == 0 && obj2io(io)->len == 0)) { - return (fio_str_info_s){.data = NULL, .len = 0}; - } - if (tmp == 0) { - obj2io(io)->source.fpos += obj2io(io)->len; - return (fio_str_info_s){.data = (char *)obj2io(io)->buffer, - .len = obj2io(io)->len}; - } - obj2io(io)->len += tmp; - pos = obj2io(io)->buffer; - lim = obj2io(io)->buffer + obj2io(io)->len; - if (swallow_ch(&pos, lim, token)) { - const uintptr_t delta = - (uintptr_t)(pos - (obj2io(io)->buffer + obj2io(io)->pos)); - obj2io(io)->pos = delta; - obj2io(io)->source.fpos += delta; - return (fio_str_info_s){ - .data = (char *)obj2io(io)->buffer, - .len = delta, - }; - } - } -} - -/** - * Reads until the `token` byte is encountered or until the end of the stream. - * - * Returns a temporary(!) C string including the end of line marker. - * - * Careful when using this call on large file streams, as the whole file - * stream might be loaded into the memory. - * - * The C string object will be invalidate the next time a function call to the - * IO object is made. - */ -fio_str_info_s fiobj_data_read2ch(FIOBJ io, uint8_t token) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { - errno = EFAULT; - return (fio_str_info_s){.data = NULL, .len = 0}; - } - switch (obj2io(io)->fd) { - case -1: - return fiobj_data_read2ch_str(io, token); - break; - case -2: - return fiobj_data_read2ch_slice(io, token); - break; - default: - return fiobj_data_read2ch_file(io, token); - } -} - -/* ***************************************************************************** -Position / Seeking -***************************************************************************** */ - -/** - * Returns the current reading position. - */ -intptr_t fiobj_data_pos(FIOBJ io) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) - return -1; - switch (obj2io(io)->fd) { - case -1: /* fallthrough */ - case -2: - return obj2io(io)->pos; - break; - default: - return obj2io(io)->source.fpos; - } -} - -/** - * Returns the length of the stream. - */ -intptr_t fiobj_data_len(FIOBJ io) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) - return -1; - return fiobj_data_i(io); -} - -/** - * Moves the reading position to the requested position. - */ -void fiobj_data_seek(FIOBJ io, intptr_t position) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) - return; - switch (obj2io(io)->fd) { - case -1: /* fallthrough */ - case -2: - /* String / Slice code */ - if (position == 0) { - obj2io(io)->pos = 0; - return; - } - if (position > 0) { - if ((uintptr_t)position > obj2io(io)->len) - position = obj2io(io)->len; - obj2io(io)->pos = position; - return; - } - position = (0 - position); - if ((uintptr_t)position > obj2io(io)->len) - position = 0; - else - position = obj2io(io)->len - position; - obj2io(io)->pos = position; - return; - break; - default: - /* File code */ - obj2io(io)->pos = 0; - obj2io(io)->len = 0; - - if (position == 0) { - obj2io(io)->source.fpos = 0; - return; - } - int64_t len = fiobj_data_get_fd_size(io); - if (len < 0) - len = 0; - if (position > 0) { - if (position > len) - position = len; - - obj2io(io)->source.fpos = position; - return; - } - position = (0 - position); - if (position > len) - position = 0; - else - position = len - position; - obj2io(io)->source.fpos = position; - return; - } -} - -/* ***************************************************************************** -`fiobj_data_pread` -***************************************************************************** */ -// switch(obj2io(o)->fd) { -// case -1: -// break; -// case -2: -// break; -// default: -// } - -static fio_str_info_s fiobj_data_pread_str(FIOBJ io, intptr_t start_at, - uintptr_t length) { - if (start_at < 0) - start_at = obj2io(io)->len + start_at; - if (start_at < 0) - start_at = 0; - if ((size_t)start_at > obj2io(io)->len) - start_at = obj2io(io)->len; - if (length + start_at > obj2io(io)->len) - length = obj2io(io)->len - start_at; - if (length == 0) - return (fio_str_info_s){ - .data = NULL, - .len = 0, - }; - return (fio_str_info_s){ - .data = (char *)obj2io(io)->buffer + start_at, - .len = length, - }; -} -static fio_str_info_s fiobj_data_pread_slice(FIOBJ io, intptr_t start_at, - uintptr_t length) { - if (start_at < 0) - start_at = obj2io(io)->len + start_at; - if (start_at < 0) - start_at = 0; - if ((size_t)start_at > obj2io(io)->len) - start_at = obj2io(io)->len; - if (length + start_at > obj2io(io)->len) - length = obj2io(io)->len - start_at; - if (length == 0) - return (fio_str_info_s){ - .data = NULL, - .len = 0, - }; - return fiobj_data_pread(obj2io(io)->source.parent, start_at, length); -} - -static fio_str_info_s fiobj_data_pread_file(FIOBJ io, intptr_t start_at, - uintptr_t length) { - const int64_t size = fiobj_data_get_fd_size(io); - if (start_at < 0) - start_at = size + start_at; - if (start_at < 0) - start_at = 0; - if (length + start_at > (uint64_t)size) - length = size - start_at; - if (length == 0) { - /* free memory once there's no more data to read */ - obj2io(io)->capa = 0; - fio_free(obj2io(io)->buffer); - obj2io(io)->buffer = NULL; - return (fio_str_info_s){ - .data = NULL, - .len = 0, - }; - } - obj2io(io)->len = 0; - obj2io(io)->pos = 0; - fiobj_data_pre_write(io, length + 1); - ssize_t tmp = pread(obj2io(io)->fd, obj2io(io)->buffer, length, start_at); - if (tmp <= 0) { - return (fio_str_info_s){ - .data = NULL, - .len = 0, - }; - } - obj2io(io)->buffer[tmp] = 0; - return (fio_str_info_s){ - .data = (char *)obj2io(io)->buffer, - .len = tmp, - }; -} -/** - * Reads up to `length` bytes starting at `start_at` position and returns a - * temporary(!) C string object. The reading position is ignored and - * unchanged. - * - * The C string object will be invalidate the next time a function call to the - * IO object is made. - */ -fio_str_info_s fiobj_data_pread(FIOBJ io, intptr_t start_at, uintptr_t length) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { - errno = EFAULT; - return (fio_str_info_s){ - .data = NULL, - .len = 0, - }; - } - - errno = 0; - switch (obj2io(io)->fd) { - case -1: - return fiobj_data_pread_str(io, start_at, length); - break; - case -2: - return fiobj_data_pread_slice(io, start_at, length); - break; - default: - return fiobj_data_pread_file(io, start_at, length); - } -} - -/* ***************************************************************************** -Writing API -***************************************************************************** */ - -/** - * Makes sure the IO object isn't attached to a static or external string. - * - * If the IO object is attached to a static or external string, the data will be - * copied to a new memory block. - */ -void fiobj_data_assert_dynamic(FIOBJ io) { - if (!io) { - errno = ENFILE; - return; - } - assert(FIOBJ_TYPE(io) == FIOBJ_T_DATA); - fiobj_data_pre_write(io, 0); - return; -} - -/** - * Writes `length` bytes at the end of the IO stream, ignoring the reading - * position. - * - * Behaves and returns the same value as the system call `write`. - */ -intptr_t fiobj_data_write(FIOBJ io, void *buffer, uintptr_t length) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA) || (!buffer && length)) { - errno = EFAULT; - return -1; - } - errno = 0; - /* Unslice slices */ - if (obj2io(io)->fd == -2) - fiobj_data_assert_dynamic(io); - if (obj2io(io)->fd == -1) { - /* String Code */ - fiobj_data_pre_write(io, length + 1); - memcpy(obj2io(io)->buffer + obj2io(io)->len, buffer, length); - obj2io(io)->len = obj2io(io)->len + length; - obj2io(io)->buffer[obj2io(io)->len] = 0; - return length; - } - /* File Code */ - return pwrite(obj2io(io)->fd, buffer, length, fiobj_data_get_fd_size(io)); -} - -/** - * Writes `length` bytes at the end of the IO stream, ignoring the reading - * position, adding an EOL marker ("\r\n") to the end of the stream. - * - * Behaves and returns the same value as the system call `write`. - */ -intptr_t fiobj_data_puts(FIOBJ io, void *buffer, uintptr_t length) { - if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA) || (!buffer && length)) { - errno = EFAULT; - return -1; - } - /* Unslice slices */ - if (obj2io(io)->fd == -2) - fiobj_data_assert_dynamic(io); - - if (obj2io(io)->fd == -1) { - /* String Code */ - fiobj_data_pre_write(io, length + 2); - if (length) { - memcpy(obj2io(io)->buffer + obj2io(io)->len, buffer, length); - } - obj2io(io)->len = obj2io(io)->len + length + 2; - obj2io(io)->buffer[obj2io(io)->len - 2] = '\r'; - obj2io(io)->buffer[obj2io(io)->len - 1] = '\n'; - return length + 2; - } - /* File Code */ - uintptr_t end = fiobj_data_get_fd_size(io); - ssize_t t1 = 0, t2 = 0; - - if (length) { - t1 = pwrite(obj2io(io)->fd, buffer, length, end); - if (t1 < 0) - return t1; - end += t1; - } - t2 = pwrite(obj2io(io)->fd, buffer, length, end); - if (t2 < 0) - return t1; - return t1 + t2; -} - -#if DEBUG - -void fiobj_data_test(void) { - char *filename = NULL; - FIOBJ text; - fio_str_info_s s1, s2; - fprintf(stderr, "=== testing fiobj_data\n"); - if (filename) { - text = fiobj_str_buf(0); - fiobj_str_readfile(text, filename, 0, 0); - } else - text = fiobj_str_new("Line 1\r\nLine 2\nLine 3 unended", 29); - FIOBJ strio = fiobj_data_newstr(); - fprintf(stderr, "* `newstr` passed.\n"); - FIOBJ fdio = fiobj_data_newtmpfile(); - fprintf(stderr, "* `newtmpfile` passed.\n"); - fiobj_data_write(fdio, fiobj_obj2cstr(text).data, fiobj_obj2cstr(text).len); - fiobj_data_write(strio, fiobj_obj2cstr(text).data, fiobj_obj2cstr(text).len); - FIOBJ sliceio = fiobj_data_slice(fdio, 8, 7); - - s1 = fiobj_data_read(sliceio, 4096); - if (s1.len != 7 || memcmp(s1.data, fiobj_data_pread(strio, 8, 7).data, 7)) { - fprintf(stderr, "* `fiobj_data_slice` operation FAILED!\n"); - fprintf(stderr, "* `fiobj_data_slice` s1.len = %zu s1.data = %s!\n", s1.len, - s1.data); - exit(-1); - } - s1 = fiobj_data_read(sliceio, 4096); - if (s1.len || s1.data) { - fprintf(stderr, "* `fiobj_data_read` operation overflow - FAILED!\n"); - exit(-1); - } - if (fiobj_obj2cstr(strio).len != fiobj_obj2cstr(text).len || - fiobj_obj2cstr(fdio).len != fiobj_obj2cstr(text).len) { - fprintf(stderr, "* `write` operation FAILED!\n"); - exit(-1); - } - s1 = fiobj_data_gets(strio); - s2 = fiobj_data_gets(fdio); - fprintf(stderr, "str(%d): %.*s", (int)s1.len, (int)s1.len, s1.data); - fprintf(stderr, "fd(%d): %.*s", (int)s2.len, (int)s2.len, s2.data); - if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { - fprintf(stderr, - "* `gets` operation FAILED! (non equal data):\n" - "%d bytes vs. %d bytes\n" - "%.*s vs %.*s\n", - (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, - s2.data); - exit(-1); - } else - fprintf(stderr, "* `gets` operation passed (equal data).\n"); - - if (!filename) { - intptr_t last_pos = fiobj_data_pos(fdio); - fiobj_data_seek(sliceio, 0); - s1 = fiobj_data_gets(sliceio); - s2 = fiobj_data_gets(fdio); - fiobj_data_seek(fdio, last_pos); - if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { - fprintf(stderr, - "* slice `gets` operation FAILED! (non equal data):\n" - "%d bytes vs. %d bytes\n" - "%.*s vs %.*s\n", - (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, - s2.data); - exit(-1); - } - } - - s1 = fiobj_data_read(strio, 3); - s2 = fiobj_data_read(fdio, 3); - if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { - fprintf(stderr, - "* `read` operation FAILED! (non equal data):\n" - "%d bytes vs. %d bytes\n" - "%.*s vs %.*s\n", - (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, - s2.data); - exit(-1); - } else - fprintf(stderr, "* `read` operation passed (equal data).\n"); - if (!filename) { - s1 = fiobj_data_gets(strio); - s2 = fiobj_data_gets(fdio); - s1 = fiobj_data_gets(strio); - s2 = fiobj_data_gets(fdio); - if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { - fprintf(stderr, - "* EOF `gets` operation FAILED! (non equal data):\n" - "%d bytes vs. %d bytes\n" - "%.*s vs %.*s\n", - (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, - s2.data); - exit(-1); - } else - fprintf(stderr, "* EOF `gets` operation passed (equal data).\n"); - s1 = fiobj_data_gets(strio); - s2 = fiobj_data_gets(fdio); - if (s1.data || s2.data) { - fprintf(stderr, - "* EOF `gets` was not EOF?!\n" - "str(%d): %.*s\n" - "fd(%d): %.*s\n", - (int)s1.len, (int)s1.len, s1.data, (int)s2.len, (int)s2.len, - s2.data); - exit(-1); - } - } - fiobj_free(text); - fiobj_free(strio); - fiobj_free(fdio); - - { - fiobj_data_seek(sliceio, 0); - s1 = fiobj_data_read(sliceio, 4096); - if (s1.len != (size_t)fiobj_data_len(sliceio) || !s1.data) { - fprintf(stderr, "* `fiobj_data_slice` data lost? FAILED!\n"); - fprintf(stderr, - "* `fiobj_data_slice` s1.len = %zu (out of %zu) s1.data = %s!\n", - s1.len, (size_t)fiobj_data_len(sliceio), s1.data); - exit(-1); - } - size_t old_len = fiobj_data_len(sliceio); - fiobj_data_write(sliceio, "hi", 2); - fiobj_data_seek(sliceio, 0); - s1 = fiobj_data_read(sliceio, 4096); - if (s1.len != old_len + 2 || !s1.data || s1.data[s1.len - 1] != 'i') { - fprintf(stderr, "* `fiobj_data_write` for Slice data lost? FAILED!\n"); - fprintf(stderr, - "* `fiobj_data_slice` s1.len = %zu (out of %zu) s1.data = %s!\n", - s1.len, (size_t)fiobj_data_len(sliceio), s1.data); - exit(-1); - } - } - fiobj_free(sliceio); - - fprintf(stderr, "* passed.\n"); -} - -#endif - -#else /* require POSIX */ -#include - -/** Creates a new local in-memory IO object */ -FIOBJ fiobj_data_newstr(void) { return FIOBJ_INVALID; } - -/** - * Creates a IO object from an existing buffer. The buffer will be deallocated - * using the provided `dealloc` function pointer. Use a NULL `dealloc` function - * pointer if the buffer is static and shouldn't be freed. - */ -FIOBJ fiobj_data_newstr2(void *buffer, uintptr_t length, - void (*dealloc)(void *)) { - return FIOBJ_INVALID; -} - -/** Creates a new local tempfile IO object */ -FIOBJ fiobj_data_newtmpfile(void) { return FIOBJ_INVALID; } - -/** Creates a new local file IO object */ -FIOBJ fiobj_data_newfd(int fd) { return FIOBJ_INVALID; } - -int fiobj_data_save(FIOBJ io, const char *filename) { return -1; } - -#endif /* require POSIX */ diff --git a/ext/iodine/fiobj_data.h b/ext/iodine/fiobj_data.h deleted file mode 100644 index a1405ac6..00000000 --- a/ext/iodine/fiobj_data.h +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#if !defined(H_FIOBJ_IO_H) && (defined(__unix__) || defined(__APPLE__) || \ - defined(__linux__) || defined(__CYGWIN__) || \ - defined(__MINGW32__)) - -/** - * A dynamic type for reading / writing to a local file, a temporary file or an - * in-memory string. - * - * Supports basic reak, write, seek, puts and gets operations. - * - * Writing is always performed at the end of the stream / memory buffer, - * ignoring the current seek position. - */ -#define H_FIOBJ_IO_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -Creating the Data Stream object -***************************************************************************** */ - -/** Creates a new local in-memory Data Stream object */ -FIOBJ fiobj_data_newstr(void); - -/** - * Creates a Data object from an existing buffer. The buffer will be deallocated - * using the provided `dealloc` function pointer. Use a NULL `dealloc` function - * pointer if the buffer is static and shouldn't be freed. - */ -FIOBJ fiobj_data_newstr2(void *buffer, uintptr_t length, - void (*dealloc)(void *)); - -/** Creates a new local tempfile Data Stream object */ -FIOBJ fiobj_data_newtmpfile(void); - -/** Creates a new local file Data Stream object */ -FIOBJ fiobj_data_newfd(int fd); - -/** Creates a slice from an existing Data object. */ -FIOBJ fiobj_data_slice(FIOBJ parent, intptr_t offset, uintptr_t length); - -/* ***************************************************************************** -Saving the Data Stream object -***************************************************************************** */ - -/** Creates a new local file Data Stream object */ -int fiobj_data_save(FIOBJ io, const char *filename); - -/* ***************************************************************************** -Reading API -***************************************************************************** */ - -/** - * Reads up to `length` bytes and returns a temporary(!) buffer object (not NUL - * terminated). - * - * If `length` is zero or negative, it will be computed from the end of the - * input backwards (0 == EOF). - * - * The C string object will be invalidate the next time a function call to the - * Data Stream object is made. - */ -fio_str_info_s fiobj_data_read(FIOBJ io, intptr_t length); - -/** - * Reads until the `token` byte is encountered or until the end of the stream. - * - * Returns a temporary(!) C string including the end of line marker. - * - * Careful when using this call on large file streams, as the whole file - * stream might be loaded into the memory. - * - * The C string object will be invalidate the next time a function call to the - * Data Stream object is made. - */ -fio_str_info_s fiobj_data_read2ch(FIOBJ io, uint8_t token); - -/** - * Reads a line (until the '\n' byte is encountered) or until the end of the - * available data. - * - * Returns a temporary(!) buffer object (not NUL terminated) including the end - * of line marker. - * - * Careful when using this call on large file streams, as the whole file stream - * might be loaded into the memory. - * - * The C string object will be invalidate the next time a function call to the - * Data Stream object is made. - */ -#define fiobj_data_gets(io) fiobj_data_read2ch((io), '\n'); - -/** - * Returns the current reading position. Returns -1 on error. - */ -intptr_t fiobj_data_pos(FIOBJ io); - -/** - * Returns the length of the stream. - */ -intptr_t fiobj_data_len(FIOBJ io); - -/** - * Moves the reading position to the requested position. - */ -void fiobj_data_seek(FIOBJ io, intptr_t position); - -/** - * Reads up to `length` bytes starting at `start_at` position and returns a - * temporary(!) buffer object (not NUL terminated) string object. The reading - * position is ignored and unchanged. - * - * The C string object will be invalidate the next time a function call to the - * Data Stream object is made. - */ -fio_str_info_s fiobj_data_pread(FIOBJ io, intptr_t start_at, uintptr_t length); - -/* ***************************************************************************** -Writing API -***************************************************************************** */ - -/** - * Writes `length` bytes at the end of the Data Stream stream, ignoring the - * reading position. - * - * Behaves and returns the same value as the system call `write`. - */ -intptr_t fiobj_data_write(FIOBJ io, void *buffer, uintptr_t length); - -/** - * Writes `length` bytes at the end of the Data Stream stream, ignoring the - * reading position, adding an EOL marker ("\r\n") to the end of the stream. - * - * Behaves and returns the same value as the system call `write`. - */ -intptr_t fiobj_data_puts(FIOBJ io, void *buffer, uintptr_t length); - -/** - * Makes sure the Data Stream object isn't attached to a static or external - * string. - * - * If the Data Stream object is attached to a static or external string, the - * data will be copied to a new memory block. - * - * If the Data Stream object is a slice from another Data Stream object, the - * data will be copied and the type of Data Stream object (memory vs. tmpfile) - * will be inherited. - */ -void fiobj_data_assert_dynamic(FIOBJ io); - -#if DEBUG -void fiobj_data_test(void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ext/iodine/fiobj_hash.c b/ext/iodine/fiobj_hash.c deleted file mode 100644 index c6f0a5c1..00000000 --- a/ext/iodine/fiobj_hash.c +++ /dev/null @@ -1,409 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -#include - -#include -#include - -#define FIO_SET_CALLOC(size, count) fio_calloc((size), (count)) -#define FIO_SET_REALLOC(ptr, original_size, size, valid_data_length) \ - fio_realloc2((ptr), (size), (valid_data_length)) -#define FIO_SET_FREE(ptr, size) fio_free((ptr)) - -#define FIO_SET_NAME fio_hash__ -#define FIO_SET_KEY_TYPE FIOBJ -#define FIO_SET_KEY_COMPARE(o1, o2) \ - ((o2) == ((FIOBJ)-1) || (o1) == ((FIOBJ)-1) || fiobj_iseq((o1), (o2))) -#define FIO_SET_KEY_COPY(dest, obj) ((dest) = fiobj_dup((obj))) -#define FIO_SET_KEY_DESTROY(obj) \ - do { \ - fiobj_free((obj)); \ - (obj) = FIOBJ_INVALID; \ - } while (0) -#define FIO_SET_OBJ_TYPE FIOBJ -#define FIO_SET_OBJ_COMPARE(o1, o2) fiobj_iseq((o1), (o2)) -#define FIO_SET_OBJ_COPY(dest, obj) ((dest) = fiobj_dup(obj)) -#define FIO_SET_OBJ_DESTROY(obj) \ - do { \ - fiobj_free((obj)); \ - (obj) = FIOBJ_INVALID; \ - } while (0) - -#include - -#include - -#include - -/* ***************************************************************************** -Hash types -***************************************************************************** */ -typedef struct { - fiobj_object_header_s head; - fio_hash___s hash; -} fiobj_hash_s; - -#define obj2hash(o) ((fiobj_hash_s *)(FIOBJ2PTR(o))) - -void fiobj_hash_rehash(FIOBJ h) { - assert(h && FIOBJ_TYPE_IS(h, FIOBJ_T_HASH)); - fio_hash___rehash(&obj2hash(h)->hash); -} - -/* ***************************************************************************** -Hash alloc + VTable -***************************************************************************** */ - -static void fiobj_hash_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), - void *arg) { - FIO_SET_FOR_LOOP(&obj2hash(o)->hash, i) { - if (i->obj.key) - task((FIOBJ)i->obj.obj, arg); - fiobj_free((FIOBJ)i->obj.key); - i->obj.key = FIOBJ_INVALID; - i->obj.obj = FIOBJ_INVALID; - } - obj2hash(o)->hash.count = 0; - fio_hash___free(&obj2hash(o)->hash); - fio_free(FIOBJ2PTR(o)); -} - -static pthread_key_t each_at_key; -static pthread_once_t each_at_key_once = PTHREAD_ONCE_INIT; -static void init_each_at_key(void) { - pthread_key_create(&each_at_key, free); -} -static void init_each_at_key_ptr(void) { - FIOBJ *eak = malloc(sizeof(FIOBJ)); - FIO_ASSERT_ALLOC(eak); - *eak = FIOBJ_INVALID; - pthread_setspecific(each_at_key, eak); -} - -static size_t fiobj_hash_each1(FIOBJ o, size_t start_at, - int (*task)(FIOBJ obj, void *arg), void *arg) { - assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH)); - pthread_once(&each_at_key_once, init_each_at_key); - FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key); - if (!each_at_key_ptr) { - init_each_at_key_ptr(); - each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key); - } - FIOBJ old_each_at_key = *each_at_key_ptr; - fio_hash___s *hash = &obj2hash(o)->hash; - size_t count = 0; - if (hash->count == hash->pos) { - /* no holes in the hash, we can work as we please. */ - for (count = start_at; count < hash->count; ++count) { - *each_at_key_ptr = hash->ordered[count].obj.key; - if (task((FIOBJ)hash->ordered[count].obj.obj, arg) == -1) { - ++count; - goto end; - } - } - } else { - size_t pos = 0; - for (; pos < start_at && pos < hash->pos; ++pos) { - /* counting */ - if (hash->ordered[pos].obj.key == FIOBJ_INVALID) - ++start_at; - else - ++count; - } - for (; pos < hash->pos; ++pos) { - /* performing */ - if (hash->ordered[pos].obj.key == FIOBJ_INVALID) - continue; - ++count; - *each_at_key_ptr = hash->ordered[pos].obj.key; - if (task((FIOBJ)hash->ordered[pos].obj.obj, arg) == -1) - break; - } - } -end: - *each_at_key_ptr = old_each_at_key; - return count; -} - -FIOBJ fiobj_hash_key_in_loop(void) { - pthread_once(&each_at_key_once, init_each_at_key); - FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key); - if (!each_at_key_ptr) { - init_each_at_key_ptr(); - each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key); - } - return *each_at_key_ptr; -} - -static size_t fiobj_hash_is_eq(const FIOBJ self, const FIOBJ other) { - if (fio_hash___count(&obj2hash(self)->hash) != - fio_hash___count(&obj2hash(other)->hash)) - return 0; - return 1; -} - -/** Returns the number of elements in the Array. */ -size_t fiobj_hash_count(const FIOBJ o) { - assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH)); - return fio_hash___count(&obj2hash(o)->hash); -} - -intptr_t fiobj_hash2num(const FIOBJ o) { return (intptr_t)fiobj_hash_count(o); } - -static size_t fiobj_hash_is_true(const FIOBJ o) { - return fiobj_hash_count(o) != 0; -} - -fio_str_info_s fiobject___noop_to_str(const FIOBJ o); -intptr_t fiobject___noop_to_i(const FIOBJ o); -double fiobject___noop_to_f(const FIOBJ o); - -const fiobj_object_vtable_s FIOBJECT_VTABLE_HASH = { - .class_name = "Hash", - .dealloc = fiobj_hash_dealloc, - .is_eq = fiobj_hash_is_eq, - .count = fiobj_hash_count, - .each = fiobj_hash_each1, - .is_true = fiobj_hash_is_true, - .to_str = fiobject___noop_to_str, - .to_i = fiobj_hash2num, - .to_f = fiobject___noop_to_f, -}; - -/* ***************************************************************************** -Hash API -***************************************************************************** */ - -/** - * Creates a mutable empty Hash object. Use `fiobj_free` when done. - * - * Notice that these Hash objects are designed for smaller collections and - * retain order of object insertion. - */ -FIOBJ fiobj_hash_new(void) { - fiobj_hash_s *h = fio_malloc(sizeof(*h)); - FIO_ASSERT_ALLOC(h); - *h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH}, - .hash = FIO_SET_INIT}; - return (FIOBJ)h | FIOBJECT_HASH_FLAG; -} - -/** - * Creates a mutable empty Hash object with an initial capacity of `capa`. Use - * `fiobj_free` when done. - * - * Notice that these Hash objects are designed for smaller collections and - * retain order of object insertion. - */ -FIOBJ fiobj_hash_new2(size_t capa) { - fiobj_hash_s *h = fio_malloc(sizeof(*h)); - FIO_ASSERT_ALLOC(h); - *h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH}, - .hash = FIO_SET_INIT}; - fio_hash___capa_require(&h->hash, capa); - return (FIOBJ)h | FIOBJECT_HASH_FLAG; -} - -/** - * Returns a temporary theoretical Hash map capacity. - * This could be used for testing performance and memory consumption. - */ -size_t fiobj_hash_capa(const FIOBJ hash) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - return fio_hash___capa(&obj2hash(hash)->hash); -} - -/** - * Sets a key-value pair in the Hash, duplicating the Symbol and **moving** - * the ownership of the object to the Hash. - * - * Returns -1 on error. - */ -int fiobj_hash_set(FIOBJ hash, FIOBJ key, FIOBJ obj) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - if (FIOBJ_TYPE_IS(key, FIOBJ_T_STRING)) - fiobj_str_freeze(key); - fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, NULL); - fiobj_free(obj); /* take ownership - free the user's reference. */ - return 0; -} - -/** - * Allows the Hash to be used as a stack. - * - * If a pointer `key` is provided, it will receive ownership of the key - * (remember to free). - * - * Returns FIOBJ_INVALID on error. - * - * Returns and object if successful (remember to free). - */ -FIOBJ fiobj_hash_pop(FIOBJ hash, FIOBJ *key) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - FIOBJ old; - if (fio_hash___count(&obj2hash(hash)->hash)) - return FIOBJ_INVALID; - old = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).obj); - if (key) - *key = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).key); - fio_hash___pop(&obj2hash(hash)->hash); - return old; -} - -/** - * Replaces the value in a key-value pair, returning the old value (and it's - * ownership) to the caller. - * - * A return value of NULL indicates that no previous object existed (but a new - * key-value pair was created. - * - * Errors are silently ignored. - */ -FIOBJ fiobj_hash_replace(FIOBJ hash, FIOBJ key, FIOBJ obj) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - FIOBJ old = FIOBJ_INVALID; - fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, &old); - fiobj_free(obj); /* take ownership - free the user's reference. */ - return old; -} - -/** - * Removes a key-value pair from the Hash, if it exists. - */ -FIOBJ fiobj_hash_remove(FIOBJ hash, FIOBJ key) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - FIOBJ old = FIOBJ_INVALID; - fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, &old); - return old; -} - -/** - * Removes a key-value pair from the Hash, if it exists, returning the old - * object (instead of freeing it). - */ -FIOBJ fiobj_hash_remove2(FIOBJ hash, uint64_t hash_value) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - FIOBJ old = FIOBJ_INVALID; - fio_hash___remove(&obj2hash(hash)->hash, hash_value, -1, &old); - return old; -} - -/** - * Deletes a key-value pair from the Hash, if it exists, freeing the - * associated object. - * - * Returns -1 on type error or if the object never existed. - */ -int fiobj_hash_delete(FIOBJ hash, FIOBJ key) { - return fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, - NULL); -} - -/** - * Deletes a key-value pair from the Hash, if it exists, freeing the - * associated object. - * - * This function takes a `uintptr_t` Hash value (see `fio_siphash`) to - * perform a lookup in the HashMap, which is slightly faster than the other - * variations. - * - * Returns -1 on type error or if the object never existed. - */ -int fiobj_hash_delete2(FIOBJ hash, uint64_t key_hash) { - return fio_hash___remove(&obj2hash(hash)->hash, key_hash, -1, NULL); -} - -/** - * Returns a temporary handle to the object associated with the Symbol, NULL - * if none. - */ -FIOBJ fiobj_hash_get(const FIOBJ hash, FIOBJ key) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key); - ; -} - -/** - * Returns a temporary handle to the object associated hashed key value. - * - * This function takes a `uintptr_t` Hash value (see `fio_siphash`) to - * perform a lookup in the HashMap. - * - * Returns NULL if no object is associated with this hashed key value. - */ -FIOBJ fiobj_hash_get2(const FIOBJ hash, uint64_t key_hash) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - return fio_hash___find(&obj2hash(hash)->hash, key_hash, -1); - ; -} - -/** - * Returns 1 if the key (Symbol) exists in the Hash, even if value is NULL. - */ -int fiobj_hash_haskey(const FIOBJ hash, FIOBJ key) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key) != - FIOBJ_INVALID; -} - -/** - * Empties the Hash. - */ -void fiobj_hash_clear(const FIOBJ hash) { - assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH)); - fio_hash___free(&obj2hash(hash)->hash); -} - -/* ***************************************************************************** -Simple Tests -***************************************************************************** */ - -#if DEBUG -void fiobj_test_hash(void) { - fprintf(stderr, "=== Testing Hash\n"); -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, "* " __VA_ARGS__); \ - fprintf(stderr, "Testing failed.\n"); \ - exit(-1); \ - } - FIOBJ o = fiobj_hash_new(); - FIOBJ str_key = fiobj_str_new("Hello World!", 12); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH), "Type identification error!\n"); - TEST_ASSERT(fiobj_hash_count(o) == 0, "Hash should be empty!\n"); - fiobj_hash_set(o, str_key, fiobj_true()); - TEST_ASSERT(fiobj_str_write(str_key, "should fail...", 13) == 0, - "wrote to frozen string?"); - TEST_ASSERT(fiobj_obj2cstr(str_key).len == 12, - "String was mutated (not frozen)!\n"); - TEST_ASSERT(fiobj_hash_get(o, str_key) == fiobj_true(), - "full compare didn't get value back"); - TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == fiobj_true(), - "hash compare didn't get value back"); - - FIOBJ o2 = fiobj_hash_new2(3); - TEST_ASSERT(obj2hash(o2)->hash.capa >= 3, - "Hash capacity should be larger than 3! %zu != 4\n", - (size_t)obj2hash(o2)->hash.capa); - fiobj_hash_set(o2, str_key, fiobj_true()); - TEST_ASSERT(fiobj_hash_is_eq(o, o2), "Hashes not equal at core! %zu != %zu\n", - fiobj_hash_count(o), fiobj_hash_count(o2)); - TEST_ASSERT(fiobj_iseq(o, o2), "Hashes not equal!\n"); - TEST_ASSERT(obj2hash(o2)->hash.capa > 3, - "Hash capacity should be larger than 3! %zu != 4\n", - (size_t)obj2hash(o2)->hash.capa); - - fiobj_hash_delete(o, str_key); - - TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == 0, - "item wasn't deleted!"); - fiobj_free( - str_key); /* note that a copy will remain in the Hash until rehashing. */ - fiobj_free(o); - fiobj_free(o2); - fprintf(stderr, "* passed.\n"); -} -#endif diff --git a/ext/iodine/fiobj_hash.h b/ext/iodine/fiobj_hash.h deleted file mode 100644 index 63965018..00000000 --- a/ext/iodine/fiobj_hash.h +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#ifndef H_FIOBJ_HASH_H -/** - * The facil.io Hash object is an ordered Hash Table implementation. - * - * By compromising some of the HashMap's collision resistance (comparing only - * the Hash values rather than comparing key data), memory comparison can be - * avoided and performance increased. - * - * By being ordered it's possible to iterate over key-value pairs in the order - * in which they were added to the Hash table, making it possible to output JSON - * in a controlled manner. - */ -#define H_FIOBJ_HASH_H - -#include - -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* MUST be a power of 2 */ -#define HASH_INITIAL_CAPACITY 16 - -/** attempts to rehash the hashmap. */ -void fiobj_hash_rehash(FIOBJ h); - -/* ***************************************************************************** -Hash Creation -***************************************************************************** */ - -/** - * Creates a mutable empty Hash object. Use `fiobj_free` when done. - * - * Notice that these Hash objects are optimized for smaller collections and - * retain order of object insertion. - */ -FIOBJ fiobj_hash_new(void); - -/** - * Creates a mutable empty Hash object with an initial capacity of `capa`. Use - * `fiobj_free` when done. - * - * This allows optimizations for larger (or smaller) collections. - */ -FIOBJ fiobj_hash_new2(size_t capa); - -/* ***************************************************************************** -Hash properties and state -***************************************************************************** */ - -/** - * Returns a temporary theoretical Hash map capacity. - * This could be used for testing performance and memory consumption. - */ -size_t fiobj_hash_capa(const FIOBJ hash); - -/** Returns the number of elements in the Hash. */ -size_t fiobj_hash_count(const FIOBJ hash); - -/** Returns the key for the object in the current `fiobj_each` loop (if any). */ -FIOBJ fiobj_hash_key_in_loop(void); - -/* ***************************************************************************** -Populating the Hash -***************************************************************************** */ - -/** - * Sets a key-value pair in the Hash, duplicating the Symbol and **moving** - * the ownership of the object to the Hash. - * - * Returns -1 on error. - */ -int fiobj_hash_set(FIOBJ hash, FIOBJ key, FIOBJ obj); - -/** - * Allows the Hash to be used as a stack. - * - * If a pointer `key` is provided, it will receive ownership of the key - * (remember to free). - * - * Returns FIOBJ_INVALID on error. - * - * Returns and object if successful (remember to free). - */ -FIOBJ fiobj_hash_pop(FIOBJ hash, FIOBJ *key); - -/** - * Replaces the value in a key-value pair, returning the old value (and it's - * ownership) to the caller. - * - * A return value of FIOBJ_INVALID indicates that no previous object existed - * (but a new key-value pair was created. - * - * Errors are silently ignored. - * - * Remember to free the returned object. - */ -FIOBJ fiobj_hash_replace(FIOBJ hash, FIOBJ key, FIOBJ obj); - -/** - * Removes a key-value pair from the Hash, if it exists, returning the old - * object (instead of freeing it). - */ -FIOBJ fiobj_hash_remove(FIOBJ hash, FIOBJ key); - -/** - * Removes a key-value pair from the Hash, if it exists, returning the old - * object (instead of freeing it). - */ -FIOBJ fiobj_hash_remove2(FIOBJ hash, uint64_t key_hash); - -/** - * Deletes a key-value pair from the Hash, if it exists, freeing the - * associated object. - * - * Returns -1 on type error or if the object never existed. - */ -int fiobj_hash_delete(FIOBJ hash, FIOBJ key); - -/** - * Deletes a key-value pair from the Hash, if it exists, freeing the - * associated object. - * - * This function takes a `uint64_t` Hash value (see `fio_siphash`) to - * perform a lookup in the HashMap, which is slightly faster than the other - * variations. - * - * Returns -1 on type error or if the object never existed. - */ -int fiobj_hash_delete2(FIOBJ hash, uint64_t key_hash); - -/** - * Returns a temporary handle to the object associated with the Symbol, - * FIOBJ_INVALID if none. - */ -FIOBJ fiobj_hash_get(const FIOBJ hash, FIOBJ key); - -/** - * Returns a temporary handle to the object associated hashed key value. - * - * This function takes a `uint64_t` Hash value (see `fio_siphash`) to - * perform a lookup in the HashMap, which is slightly faster than the other - * variations. - * - * Returns FIOBJ_INVALID if no object is associated with this hashed key value. - */ -FIOBJ fiobj_hash_get2(const FIOBJ hash, uint64_t key_hash); - -/** - * Returns 1 if the key (Symbol) exists in the Hash, even if it's value is NULL. - */ -int fiobj_hash_haskey(const FIOBJ hash, FIOBJ key); - -/** - * Empties the Hash. - */ -void fiobj_hash_clear(const FIOBJ hash); - -#if DEBUG -void fiobj_test_hash(void); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/ext/iodine/fiobj_json.c b/ext/iodine/fiobj_json.c deleted file mode 100644 index 0709fb56..00000000 --- a/ext/iodine/fiobj_json.c +++ /dev/null @@ -1,622 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#include -#define FIO_ARY_NAME fio_json_stack -#define FIO_ARY_TYPE FIOBJ -#include - -#include - -#include -#include -#include -#include -#include - -/* ***************************************************************************** -JSON API -***************************************************************************** */ - -/** - * Parses JSON, setting `pobj` to point to the new Object. - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len); -/* Formats an object into a JSON string. Remember to `fiobj_free`. */ -FIOBJ fiobj_obj2json(FIOBJ, uint8_t); - -/* ***************************************************************************** -FIOBJ Parser -***************************************************************************** */ - -typedef struct { - json_parser_s p; - FIOBJ key; - FIOBJ top; - FIOBJ target; - fio_json_stack_s stack; - uint8_t is_hash; -} fiobj_json_parser_s; - -/* ***************************************************************************** -FIOBJ Callacks -***************************************************************************** */ - -static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) { - if (p->top) { - if (p->is_hash) { - if (p->key) { - fiobj_hash_set(p->top, p->key, o); - fiobj_free(p->key); - p->key = FIOBJ_INVALID; - } else { - p->key = o; - } - } else { - fiobj_ary_push(p->top, o); - } - } else { - p->top = o; - } -} - -/** a NULL object was detected */ -static void fio_json_on_null(json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_null()); -} -/** a TRUE object was detected */ -static void fio_json_on_true(json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true()); -} -/** a FALSE object was detected */ -static void fio_json_on_false(json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false()); -} -/** a Numberl was detected (long long). */ -static void fio_json_on_number(json_parser_s *p, long long i) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_num_new(i)); -} -/** a Float was detected (double). */ -static void fio_json_on_float(json_parser_s *p, double f) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_float_new(f)); -} -/** a String was detected (int / float). update `pos` to point at ending */ -static void fio_json_on_string(json_parser_s *p, void *start, size_t length) { - FIOBJ str = fiobj_str_buf(length); - fiobj_str_resize( - str, fio_json_unescape_str(fiobj_obj2cstr(str).data, start, length)); - fiobj_json_add2parser((fiobj_json_parser_s *)p, str); -} -/** a dictionary object was detected */ -static int fio_json_on_start_object(json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - if (pr->target) { - /* push NULL, don't free the objects */ - fio_json_stack_push(&pr->stack, pr->top); - pr->top = pr->target; - pr->target = FIOBJ_INVALID; - } else { - FIOBJ hash = fiobj_hash_new(); - fiobj_json_add2parser(pr, hash); - fio_json_stack_push(&pr->stack, pr->top); - pr->top = hash; - } - pr->is_hash = 1; - return 0; -} -/** a dictionary object closure detected */ -static void fio_json_on_end_object(json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - if (pr->key) { - FIO_LOG_WARNING("(JSON parsing) malformed JSON, " - "ignoring dangling Hash key."); - fiobj_free(pr->key); - pr->key = FIOBJ_INVALID; - } - pr->top = FIOBJ_INVALID; - fio_json_stack_pop(&pr->stack, &pr->top); - pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH); -} -/** an array object was detected */ -static int fio_json_on_start_array(json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - if (pr->target) - return -1; - FIOBJ ary = fiobj_ary_new(); - fiobj_json_add2parser(pr, ary); - fio_json_stack_push(&pr->stack, pr->top); - pr->top = ary; - pr->is_hash = 0; - return 0; -} -/** an array closure was detected */ -static void fio_json_on_end_array(json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - pr->top = FIOBJ_INVALID; - fio_json_stack_pop(&pr->stack, &pr->top); - pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH); -} -/** the JSON parsing is complete */ -static void fio_json_on_json(json_parser_s *p) { - // fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - // FIO_ARY_FOR(&pr->stack, pos) { fiobj_free((FIOBJ)pos.obj); } - // fio_json_stack_free(&pr->stack); - (void)p; /* nothing special... right? */ -} -/** the JSON parsing is complete */ -static void fio_json_on_error(json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; -#if DEBUG - FIO_LOG_DEBUG("JSON on error called."); -#endif - fiobj_free((FIOBJ)fio_json_stack_get(&pr->stack, 0)); - fiobj_free(pr->key); - fio_json_stack_free(&pr->stack); - *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID}; -} - -/* ***************************************************************************** -JSON formatting -***************************************************************************** */ - -/** Writes a JSON friendly version of the src String */ -static void write_safe_str(FIOBJ dest, const FIOBJ str) { - fio_str_info_s s = fiobj_obj2cstr(str); - fio_str_info_s t = fiobj_obj2cstr(dest); - t.data[t.len] = '"'; - t.len++; - fiobj_str_resize(dest, t.len); - t = fiobj_obj2cstr(dest); - const uint8_t *restrict src = (const uint8_t *)s.data; - size_t len = s.len; - uint64_t end = t.len; - /* make sure we have some room */ - size_t added = 0; - size_t capa = fiobj_str_capa(dest); - if (capa <= end + s.len + 64) { - if (0) { - capa = (((capa >> 12) + 1) << 12) - 1; - capa = fiobj_str_capa_assert(dest, capa); - } else { - capa = fiobj_str_capa_assert(dest, (end + s.len + 64)); - } - fio_str_info_s tmp = fiobj_obj2cstr(dest); - t = tmp; - } - while (len) { - char *restrict writer = (char *)t.data; - while (len && src[0] > 32 && src[0] != '"' && src[0] != '\\') { - len--; - writer[end++] = *(src++); - } - if (!len) - break; - switch (src[0]) { - case '\b': - writer[end++] = '\\'; - writer[end++] = 'b'; - added++; - break; /* from switch */ - case '\f': - writer[end++] = '\\'; - writer[end++] = 'f'; - added++; - break; /* from switch */ - case '\n': - writer[end++] = '\\'; - writer[end++] = 'n'; - added++; - break; /* from switch */ - case '\r': - writer[end++] = '\\'; - writer[end++] = 'r'; - added++; - break; /* from switch */ - case '\t': - writer[end++] = '\\'; - writer[end++] = 't'; - added++; - break; /* from switch */ - case '"': - case '\\': - case '/': - writer[end++] = '\\'; - writer[end++] = src[0]; - added++; - break; /* from switch */ - default: - if (src[0] <= 31) { - /* MUST escape all control values less than 32 */ - writer[end++] = '\\'; - writer[end++] = 'u'; - writer[end++] = '0'; - writer[end++] = '0'; - writer[end++] = hex_chars[src[0] >> 4]; - writer[end++] = hex_chars[src[0] & 15]; - added += 4; - } else - writer[end++] = src[0]; - break; /* from switch */ - } - src++; - len--; - if (added >= 48 && capa <= end + len + 64) { - writer[end] = 0; - fiobj_str_resize(dest, end); - fiobj_str_capa_assert(dest, (end + len + 64)); - t = fiobj_obj2cstr(dest); - writer = (char *)t.data; - capa = t.capa; - added = 0; - } - } - t.data[end++] = '"'; - fiobj_str_resize(dest, end); -} - -typedef struct { - FIOBJ dest; - FIOBJ parent; - fio_json_stack_s *stack; - uintptr_t count; - uint8_t pretty; -} obj2json_data_s; - -static int fiobj_obj2json_task(FIOBJ o, void *data_) { - obj2json_data_s *data = data_; - uint8_t add_seperator = 1; - if (fiobj_hash_key_in_loop()) { - write_safe_str(data->dest, fiobj_hash_key_in_loop()); - fiobj_str_write(data->dest, ":", 1); - } - switch (FIOBJ_TYPE(o)) { - case FIOBJ_T_NUMBER: - case FIOBJ_T_NULL: - case FIOBJ_T_TRUE: - case FIOBJ_T_FALSE: - case FIOBJ_T_FLOAT: - fiobj_str_join(data->dest, o); - --data->count; - break; - - case FIOBJ_T_DATA: - case FIOBJ_T_UNKNOWN: - case FIOBJ_T_STRING: - write_safe_str(data->dest, o); - --data->count; - break; - - case FIOBJ_T_ARRAY: - --data->count; - fio_json_stack_push(data->stack, data->parent); - fio_json_stack_push(data->stack, (FIOBJ)data->count); - data->parent = o; - data->count = fiobj_ary_count(o); - fiobj_str_write(data->dest, "[", 1); - add_seperator = 0; - break; - - case FIOBJ_T_HASH: - --data->count; - fio_json_stack_push(data->stack, data->parent); - fio_json_stack_push(data->stack, (FIOBJ)data->count); - data->parent = o; - data->count = fiobj_hash_count(o); - fiobj_str_write(data->dest, "{", 1); - add_seperator = 0; - break; - } - if (data->pretty) { - fiobj_str_capa_assert(data->dest, - fiobj_obj2cstr(data->dest).len + - (fio_json_stack_count(data->stack) * 5)); - while (!data->count && data->parent) { - if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) { - fiobj_str_write(data->dest, "}", 1); - } else { - fiobj_str_write(data->dest, "]", 1); - } - add_seperator = 1; - data->count = 0; - fio_json_stack_pop(data->stack, &data->count); - data->parent = FIOBJ_INVALID; - fio_json_stack_pop(data->stack, &data->parent); - } - - if (add_seperator && data->parent) { - fiobj_str_write(data->dest, ",\n", 2); - uintptr_t indent = fio_json_stack_count(data->stack) - 1; - fiobj_str_capa_assert(data->dest, - fiobj_obj2cstr(data->dest).len + (indent * 2)); - fio_str_info_s buf = fiobj_obj2cstr(data->dest); - while (indent--) { - buf.data[buf.len++] = ' '; - buf.data[buf.len++] = ' '; - } - fiobj_str_resize(data->dest, buf.len); - } - } else { - fiobj_str_capa_assert(data->dest, - fiobj_obj2cstr(data->dest).len + - (fio_json_stack_count(data->stack) << 1)); - while (!data->count && data->parent) { - if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) { - fiobj_str_write(data->dest, "}", 1); - } else { - fiobj_str_write(data->dest, "]", 1); - } - add_seperator = 1; - data->count = 0; - data->parent = FIOBJ_INVALID; - fio_json_stack_pop(data->stack, &data->count); - fio_json_stack_pop(data->stack, &data->parent); - } - - if (add_seperator && data->parent) { - fiobj_str_write(data->dest, ",", 1); - } - } - - return 0; -} - -/* ***************************************************************************** -FIOBJ API -***************************************************************************** */ - -/** - * Parses JSON, setting `pobj` to point to the new Object. - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len) { - fiobj_json_parser_s p = {.top = FIOBJ_INVALID}; - size_t consumed = fio_json_parse(&p.p, data, len); - if (!consumed || p.p.depth) { - fiobj_free(fio_json_stack_get(&p.stack, 0)); - p.top = FIOBJ_INVALID; - } - fio_json_stack_free(&p.stack); - fiobj_free(p.key); - *pobj = p.top; - return consumed; -} - -/** - * Updates a Hash using JSON data. - * - * Parsing errors and non-dictionar object JSON data are silently ignored, - * attempting to update the Hash as much as possible before any errors - * encountered. - * - * Conflicting Hash data is overwritten (prefering the new over the old). - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -size_t fiobj_hash_update_json(FIOBJ hash, const void *data, size_t len) { - if (!hash) - return 0; - fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash}; - size_t consumed = fio_json_parse(&p.p, data, len); - fio_json_stack_free(&p.stack); - fiobj_free(p.key); - if (p.top != hash) - fiobj_free(p.top); - return consumed; -} - -/** - * Formats an object into a JSON string, appending the JSON string to an - * existing String. Remember to `fiobj_free`. - */ -FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ o, uint8_t pretty) { - assert(dest && FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (!o) { - fiobj_str_write(dest, "null", 4); - return 0; - } - fio_json_stack_s stack = FIO_ARY_INIT; - obj2json_data_s data = { - .dest = dest, - .stack = &stack, - .pretty = pretty, - .count = 1, - }; - if (!o || !FIOBJ_IS_ALLOCATED(o) || !FIOBJECT2VTBL(o)->each) { - fiobj_obj2json_task(o, &data); - return dest; - } - fiobj_each2(o, fiobj_obj2json_task, &data); - fio_json_stack_free(&stack); - return dest; -} - -/* Formats an object into a JSON string. Remember to `fiobj_free`. */ -FIOBJ fiobj_obj2json(FIOBJ obj, uint8_t pretty) { - return fiobj_obj2json2(fiobj_str_buf(128), obj, pretty); -} - -/* ***************************************************************************** -Test -***************************************************************************** */ - -#if DEBUG -void fiobj_test_json(void) { - fprintf(stderr, "=== Testing JSON parser (simple test)\n"); -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, "* " __VA_ARGS__); \ - fprintf(stderr, "\n !!! Testing failed !!!\n"); \ - exit(-1); \ - } - char json_str[] = "{\"array\":[1,2,3,\"boom\"],\"my\":{\"secret\":42}," - "\"true\":true,\"false\":false,\"null\":null,\"float\":-2." - "2,\"string\":\"I \\\"wrote\\\" this.\"}"; - char json_str_update[] = "{\"array\":[1,2,3]}"; - char json_str2[] = - "[\n \"JSON Test Pattern pass1\",\n {\"object with 1 " - "member\":[\"array with 1 element\"]},\n {},\n [],\n -42,\n " - "true,\n false,\n null,\n {\n \"integer\": 1234567890,\n " - " \"real\": -9876.543210,\n \"e\": 0.123456789e-12,\n " - " \"E\": 1.234567890E+34,\n \"\": 23456789012E66,\n " - "\"zero\": 0,\n \"one\": 1,\n \"space\": \" \",\n " - "\"quote\": \"\\\"\",\n \"backslash\": \"\\\\\",\n " - "\"controls\": \"\\b\\f\\n\\r\\t\",\n \"slash\": \"/ & \\/\",\n " - " \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n \"ALPHA\": " - "\"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n \"digit\": \"0123456789\",\n " - " \"0123456789\": \"digit\",\n \"special\": " - "\"`1~!@#$%^&*()_+-={':[,]}|;.?\",\n \"hex\": " - "\"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n \"true\": " - "true,\n \"false\": false,\n \"null\": null,\n " - "\"array\":[ ],\n \"object\":{ },\n \"address\": \"50 " - "St. James Street\",\n \"url\": \"http://www.JSON.org/\",\n " - " \"comment\": \"// /* */\": \" \",\n " - " \" s p a c e d \" :[1,2 , 3\n\n,\n\n4 , 5 , 6 " - " ,7 ],\"compact\":[1,2,3,4,5,6,7],\n \"jsontext\": " - "\"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n " - " \"quotes\": \"" \\u0022 %22 0x22 034 "\",\n " - "\"\\/" - "\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$" - "%^&*()_+-=[]{}|;:',./<>?\"\n: \"A key can be any string\"\n },\n " - "0.5 " - ",98.6\n,\n99.44\n,\n\n1066,\n1e1,\n0.1e1,\n1e-1,\n1e00,2e+00,2e-00\n," - "\"rosebud\"]"; - - FIOBJ o = 0; - TEST_ASSERT(fiobj_json2obj(&o, "1", 2) == 1, - "JSON number parsing failed to run!\n"); - TEST_ASSERT(o, "JSON (single) object missing!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_NUMBER), - "JSON (single) not a number!\n"); - TEST_ASSERT(fiobj_obj2num(o) == 1, "JSON (single) not == 1!\n"); - fiobj_free(o); - - TEST_ASSERT(fiobj_json2obj(&o, "2.0", 5) == 3, - "JSON float parsing failed to run!\n"); - TEST_ASSERT(o, "JSON (float) object missing!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_FLOAT), "JSON (float) not a float!\n"); - TEST_ASSERT(fiobj_obj2float(o) == 2, "JSON (float) not == 2!\n"); - fiobj_free(o); - - TEST_ASSERT(fiobj_json2obj(&o, json_str, sizeof(json_str)) == - (sizeof(json_str) - 1), - "JSON parsing failed to run!\n"); - TEST_ASSERT(o, "JSON object missing!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH), - "JSON root not a dictionary (not a hash)!\n"); - FIOBJ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY), - "JSON 'array' not an Array!\n"); - TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 0)) == 1, - "JSON 'array' index 0 error!\n"); - TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 1)) == 2, - "JSON 'array' index 1 error!\n"); - TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 2)) == 3, - "JSON 'array' index 2 error!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_ary_index(tmp, 3), FIOBJ_T_STRING), - "JSON 'array' index 3 type error!\n"); - TEST_ASSERT(!memcmp("boom", fiobj_obj2cstr(fiobj_ary_index(tmp, 3)).data, 4), - "JSON 'array' index 3 error!\n"); - tmp = fiobj_hash_get2(o, fiobj_hash_string("my", 2)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH), - "JSON 'my:secret' not a Hash!\n"); - TEST_ASSERT( - FIOBJ_TYPE_IS(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6)), - FIOBJ_T_NUMBER), - "JSON 'my:secret' doesn't hold a number!\n"); - TEST_ASSERT( - fiobj_obj2num(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6))) == 42, - "JSON 'my:secret' not 42!\n"); - TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("true", 4)) == fiobj_true(), - "JSON 'true' not true!\n"); - TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("false", 5)) == - fiobj_false(), - "JSON 'false' not false!\n"); - TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("null", 4)) == fiobj_null(), - "JSON 'null' not null!\n"); - tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n"); - tmp = fiobj_hash_get2(o, fiobj_hash_string("string", 6)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING), - "JSON 'string' not a string!\n"); - TEST_ASSERT(!strcmp(fiobj_obj2cstr(tmp).data, "I \"wrote\" this."), - "JSON 'string' incorrect!\n"); - fprintf(stderr, "* passed.\n"); - fprintf(stderr, "=== Testing JSON formatting (simple test)\n"); - tmp = fiobj_obj2json(o, 0); - fprintf(stderr, "* data (%p):\n%.*s\n", (void *)fiobj_obj2cstr(tmp).data, - (int)fiobj_obj2cstr(tmp).len, fiobj_obj2cstr(tmp).data); - if (!strcmp(fiobj_obj2cstr(tmp).data, json_str)) - fprintf(stderr, "* Stringify == Original.\n"); - TEST_ASSERT( - fiobj_hash_update_json(o, json_str_update, strlen(json_str_update)), - "JSON update failed to parse data."); - fiobj_free(tmp); - - tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY), - "JSON updated 'array' not an Array!\n"); - TEST_ASSERT(fiobj_ary_count(tmp) == 3, "JSON updated 'array' not updated?"); - tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5)); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), - "JSON updated (old) 'float' missing!\n"); - fiobj_free(o); - fprintf(stderr, "* passed.\n"); - - fprintf(stderr, "=== Testing JSON parsing (UTF-8 and special cases)\n"); - fiobj_json2obj(&o, "[\"\\uD834\\uDD1E\"]", 16); - TEST_ASSERT(o, "JSON G clef String failed to parse!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY), - "JSON G clef container has an incorrect type! (%s)\n", - fiobj_type_name(o)); - tmp = o; - o = fiobj_ary_pop(o); - fiobj_free(tmp); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), - "JSON G clef String incorrect type! %p => %s\n", (void *)o, - fiobj_type_name(o)); - TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")), - "JSON G clef String incorrect %s !\n", fiobj_obj2cstr(o).data); - fiobj_free(o); - - fiobj_json2obj(&o, "\"\\uD834\\uDD1E\"", 14); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), - "JSON direct G clef String incorrect type! %p => %s\n", (void *)o, - fiobj_type_name(o)); - TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")), - "JSON direct G clef String incorrect %s !\n", - fiobj_obj2cstr(o).data); - fiobj_free(o); - fiobj_json2obj(&o, "\"Hello\\u0000World\"", 19); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), - "JSON NUL containing String incorrect type! %p => %s\n", - (void *)o, fiobj_type_name(o)); - TEST_ASSERT( - (!memcmp(fiobj_obj2cstr(o).data, "Hello\0World", fiobj_obj2cstr(o).len)), - "JSON NUL containing String incorrect! (%u): %s . %s\n", - (int)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data, - fiobj_obj2cstr(o).data + 3); - fiobj_free(o); - size_t consumed = fiobj_json2obj(&o, json_str2, sizeof(json_str2)); - TEST_ASSERT( - consumed == (sizeof(json_str2) - 1), - "JSON messy string failed to parse (consumed %lu instead of %lu\n", - (unsigned long)consumed, (unsigned long)(sizeof(json_str2) - 1)); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY), - "JSON messy string object error\n"); - tmp = fiobj_obj2json(o, 1); - TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING), - "JSON messy string isn't a string\n"); - fprintf(stderr, "Messy JSON:\n%s\n", fiobj_obj2cstr(tmp).data); - fiobj_free(o); - fiobj_free(tmp); - fprintf(stderr, "* passed.\n"); -} - -#endif diff --git a/ext/iodine/fiobj_json.h b/ext/iodine/fiobj_json.h deleted file mode 100644 index 788f97f3..00000000 --- a/ext/iodine/fiobj_json.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef H_FIOBJ_JSON_H -#define H_FIOBJ_JSON_H - -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -JSON API -***************************************************************************** */ - -/** Limit JSON nesting, 32 is the limit to accomodate a 32 bit type. */ -#if !defined(JSON_MAX_DEPTH) || JSON_MAX_DEPTH > 32 -#undef JSON_MAX_DEPTH -#define JSON_MAX_DEPTH 32 -#endif - -/** - * Parses JSON, setting `pobj` to point to the new Object. - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len); -/** - * Stringify an object into a JSON string. Remember to `fiobj_free`. - * - * Note that only the foloowing basic fiobj types are supported: Primitives - * (True / False / NULL), Numbers (Number / Float), Strings, Hashes and Arrays. - * - * Some objects (such as the POSIX specific IO type) are unsupported and may be - * formatted incorrectly. - */ -FIOBJ fiobj_obj2json(FIOBJ, uint8_t pretty); - -/** - * Formats an object into a JSON string, appending the JSON string to an - * existing String. Remember to `fiobj_free` as usual. - * - * Note that only the following basic fiobj types are supported: Primitives - * (True / False / NULL), Numbers (Number / Float), Strings, Hashes and - * Arrays. - * - * Some objects (such as the POSIX specific IO type) are unsupported and may be - * formatted incorrectly. - */ -FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ object, uint8_t pretty); - -#if DEBUG -void fiobj_test_json(void); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/ext/iodine/fiobj_mem.h b/ext/iodine/fiobj_mem.h deleted file mode 100644 index ae60cd92..00000000 --- a/ext/iodine/fiobj_mem.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright: Boaz Segev, 2018 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_FIOBJ_MEM_H - -/** - * This is a placeholder for facil.io memory allocator functions in fio.h. - * - * In cases where the FIOBJ library is extracted from facil.io, these functions - * will call the system's memory allocator (`malloc`, `free`, etc'). - */ -#define H_FIOBJ_MEM_H - -#include - -/** - * Allocates memory using a per-CPU core block memory pool. - * Memory is zeroed out. - * - * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (12,288 bytes when using 32Kb - * blocks) will be redirected to `mmap`, as if `fio_mmap` was called. - */ -void *fio_malloc(size_t size); - -/** - * same as calling `fio_malloc(size_per_unit * unit_count)`; - * - * Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (12,288 bytes when using 32Kb - * blocks) will be redirected to `mmap`, as if `fio_mmap` was called. - */ -void *fio_calloc(size_t size_per_unit, size_t unit_count); - -/** Frees memory that was allocated using this library. */ -void fio_free(void *ptr); - -/** - * Re-allocates memory. An attept to avoid copying the data is made only for big - * memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). - */ -void *fio_realloc(void *ptr, size_t new_size); - -/** - * Re-allocates memory. An attept to avoid copying the data is made only for big - * memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT). - * - * This variation is slightly faster as it might copy less data. - */ -void *fio_realloc2(void *ptr, size_t new_size, size_t copy_length); - -/** - * Allocates memory directly using `mmap`, this is prefered for objects that - * both require almost a page of memory (or more) and expect a long lifetime. - * - * However, since this allocation will invoke the system call (`mmap`), it will - * be inherently slower. - * - * `fio_free` can be used for deallocating the memory. - */ -void *fio_mmap(size_t size); - -#if FIO_OVERRIDE_MALLOC -#define malloc fio_malloc -#define free fio_free -#define realloc fio_realloc -#define calloc fio_calloc -#endif - -#endif /* H_FIOBJ_MEM_H */ diff --git a/ext/iodine/fiobj_mustache.c b/ext/iodine/fiobj_mustache.c deleted file mode 100644 index f80126c8..00000000 --- a/ext/iodine/fiobj_mustache.c +++ /dev/null @@ -1,317 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT -*/ -#define INCLUDE_MUSTACHE_IMPLEMENTATION 1 -#include - -#include -#include -#include -#include - -#ifndef FIO_IGNORE_MACRO -/** - * This is used internally to ignore macros that shadow functions (avoiding - * named arguments when required). - */ -#define FIO_IGNORE_MACRO -#endif - -/** - * Loads a mustache template, converting it into an opaque instruction array. - * - * Returns a pointer to the instruction array. - * - * The `folder` argument should contain the template's root folder which would - * also be used to search for any required partial templates. - * - * The `filename` argument should contain the template's file name. - */ -mustache_s *fiobj_mustache_load(fio_str_info_s filename) { - return mustache_load(.filename = filename.data, .filename_len = filename.len); -} - -/** - * Loads a mustache template, converting it into an opaque instruction array. - * - * Returns a pointer to the instruction array. - * - * The `folder` argument should contain the template's root folder which would - * also be used to search for any required partial templates. - * - * The `filename` argument should contain the template's file name. - */ -mustache_s *fiobj_mustache_new FIO_IGNORE_MACRO(mustache_load_args_s args) { - return mustache_load FIO_IGNORE_MACRO(args); -} - -/** Free the mustache template */ -void fiobj_mustache_free(mustache_s *mustache) { mustache_free(mustache); } - -/** - * Renders a template into an existing FIOBJ String (`dest`'s end), using the - * information in the `data` object. - * - * Returns FIOBJ_INVALID if an error occured and a FIOBJ String on success. - */ -FIOBJ fiobj_mustache_build2(FIOBJ dest, mustache_s *mustache, FIOBJ data) { - mustache_build(mustache, .udata1 = (void *)dest, .udata2 = (void *)data); - return dest; -} - -/** - * Creates a FIOBJ String containing the rendered template using the information - * in the `data` object. - * - * Returns FIOBJ_INVALID if an error occured and a FIOBJ String on success. - */ -FIOBJ fiobj_mustache_build(mustache_s *mustache, FIOBJ data) { - if (!mustache) - return FIOBJ_INVALID; - return fiobj_mustache_build2(fiobj_str_buf(mustache->u.read_only.data_length), - mustache, data); -} - -/* ***************************************************************************** -Mustache Callbacks -***************************************************************************** */ - -static inline FIOBJ fiobj_mustache_find_obj_absolute(FIOBJ parent, FIOBJ key) { - if (!FIOBJ_TYPE_IS(parent, FIOBJ_T_HASH)) - return FIOBJ_INVALID; - FIOBJ o = FIOBJ_INVALID; - o = fiobj_hash_get(parent, key); - return o; -} - -static inline FIOBJ fiobj_mustache_find_obj_tree(mustache_section_s *section, - const char *name, - uint32_t name_len) { - FIOBJ key = fiobj_str_tmp(); - fiobj_str_write(key, name, name_len); - do { - FIOBJ tmp = fiobj_mustache_find_obj_absolute((FIOBJ)section->udata2, key); - if (tmp != FIOBJ_INVALID) { - return tmp; - } - } while ((section = mustache_section_parent(section))); - return FIOBJ_INVALID; -} - -static inline FIOBJ fiobj_mustache_find_obj(mustache_section_s *section, - const char *name, - uint32_t name_len) { - FIOBJ tmp = fiobj_mustache_find_obj_tree(section, name, name_len); - if (tmp != FIOBJ_INVALID) - return tmp; - /* interpolate sections... */ - uint32_t dot = 0; - while (dot < name_len && name[dot] != '.') - ++dot; - if (dot == name_len) - return FIOBJ_INVALID; - tmp = fiobj_mustache_find_obj_tree(section, name, dot); - if (!tmp) { - return FIOBJ_INVALID; - } - ++dot; - for (;;) { - FIOBJ key = fiobj_str_tmp(); - fiobj_str_write(key, name + dot, name_len - dot); - FIOBJ obj = fiobj_mustache_find_obj_absolute(tmp, key); - if (obj != FIOBJ_INVALID) - return obj; - name += dot; - name_len -= dot; - dot = 0; - while (dot < name_len && name[dot] != '.') - ++dot; - if (dot == name_len) { - return FIOBJ_INVALID; - } - key = fiobj_str_tmp(); - fiobj_str_write(key, name, dot); - tmp = fiobj_mustache_find_obj_absolute(tmp, key); - if (tmp == FIOBJ_INVALID) - return FIOBJ_INVALID; - ++dot; - } -} - -/** - * Called when an argument name was detected in the current section. - * - * A conforming implementation will search for the named argument both in the - * existing section and all of it's parents (walking backwards towards the root) - * until a value is detected. - * - * A missing value should be treated the same as an empty string. - * - * A conforming implementation will output the named argument's value (either - * HTML escaped or not, depending on the `escape` flag) as a string. - */ -static int mustache_on_arg(mustache_section_s *section, const char *name, - uint32_t name_len, unsigned char escape) { - FIOBJ o = fiobj_mustache_find_obj(section, name, name_len); - if (!o) - return 0; - fio_str_info_s i = fiobj_obj2cstr(o); - if (!i.len) - return 0; - return mustache_write_text(section, i.data, i.len, escape); -} - -/** - * Called when simple template text (string) is detected. - * - * A conforming implementation will output data as a string (no escaping). - */ -static int mustache_on_text(mustache_section_s *section, const char *data, - uint32_t data_len) { - FIOBJ dest = (FIOBJ)section->udata1; - fiobj_str_write(dest, data, data_len); - return 0; -} - -/** - * Called for nested sections, must return the number of objects in the new - * subsection (depending on the argument's name). - * - * Arrays should return the number of objects in the array. - * - * `true` values should return 1. - * - * `false` values should return 0. - * - * A return value of -1 will stop processing with an error. - * - * Please note, this will handle both normal and inverted sections. - */ -static int32_t mustache_on_section_test(mustache_section_s *section, - const char *name, uint32_t name_len, - uint8_t callable) { - FIOBJ o = fiobj_mustache_find_obj(section, name, name_len); - if (!o || FIOBJ_TYPE_IS(o, FIOBJ_T_FALSE)) - return 0; - if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) - return fiobj_ary_count(o); - return 1; - (void)callable; /* FIOBJ doesn't support lambdas */ -} - -/** - * Called when entering a nested section. - * - * `index` is a zero based index indicating the number of repetitions that - * occurred so far (same as the array index for arrays). - * - * A return value of -1 will stop processing with an error. - * - * Note: this is a good time to update the subsection's `udata` with the value - * of the array index. The `udata` will always contain the value or the parent's - * `udata`. - */ -static int mustache_on_section_start(mustache_section_s *section, - char const *name, uint32_t name_len, - uint32_t index) { - FIOBJ o = fiobj_mustache_find_obj(section, name, name_len); - if (!o) - return -1; - if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) - section->udata2 = (void *)fiobj_ary_index(o, index); - else - section->udata2 = (void *)o; - return 0; -} - -/** - * Called for cleanup in case of error. - */ -static void mustache_on_formatting_error(void *udata1, void *udata2) { - (void)udata1; - (void)udata2; -} - -/* ***************************************************************************** -Testing -***************************************************************************** */ - -#if DEBUG -static inline void mustache_save2file(char const *filename, char const *data, - size_t length) { -#ifdef __MINGW32__ - int fd = _open(filename, _O_CREAT | _O_RDWR); -#else - int fd = open(filename, O_CREAT | O_RDWR, 0); -#endif - if (fd == -1) { - perror("Couldn't open / create file for template testing"); - exit(-1); - } -#ifdef __MINGW32__ - _chmod(filename, _S_IREAD | _S_IWRITE); -#else - fchmod(fd, 0777); -#endif - if (pwrite(fd, data, length, 0) != (ssize_t)length) { - perror("Mustache template write error"); - exit(-1); - } - close(fd); -} - -void fiobj_mustache_test(void) { -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, "* " __VA_ARGS__); \ - fprintf(stderr, "\n !!! Testing failed !!!\n"); \ - exit(-1); \ - } - - char const *template = - "{{=<< >>=}}* Users:\r\n<<#users>><>. <<& name>> " - "(<>)\r\n<>\r\nNested: <<& nested.item >>."; - char const *template_name = "mustache_test_template.mustache"; - fprintf(stderr, "mustache test saving template %s\n", template_name); - mustache_save2file(template_name, template, strlen(template)); - mustache_s *m = - fiobj_mustache_load((fio_str_info_s){.data = (char *)template_name}); - unlink(template_name); - TEST_ASSERT(m, "fiobj_mustache_load failed.\n"); - FIOBJ data = fiobj_hash_new(); - FIOBJ key = fiobj_str_new("users", 5); - FIOBJ ary = fiobj_ary_new2(4); - fiobj_hash_set(data, key, ary); - fiobj_free(key); - for (int i = 0; i < 4; ++i) { - FIOBJ id = fiobj_str_buf(4); - fiobj_str_write_i(id, i); - FIOBJ name = fiobj_str_buf(4); - fiobj_str_write(name, "User ", 5); - fiobj_str_write_i(name, i); - FIOBJ usr = fiobj_hash_new2(2); - key = fiobj_str_new("id", 2); - fiobj_hash_set(usr, key, id); - fiobj_free(key); - key = fiobj_str_new("name", 4); - fiobj_hash_set(usr, key, name); - fiobj_free(key); - fiobj_ary_push(ary, usr); - } - key = fiobj_str_new("nested", 6); - ary = fiobj_hash_new2(2); - fiobj_hash_set(data, key, ary); - fiobj_free(key); - key = fiobj_str_new("item", 4); - fiobj_hash_set(ary, key, fiobj_str_new("dot notation success", 20)); - fiobj_free(key); - key = fiobj_mustache_build(m, data); - fiobj_free(data); - TEST_ASSERT(key, "fiobj_mustache_build failed!\n"); - fprintf(stderr, "%s\n", fiobj_obj2cstr(key).data); - fiobj_free(key); - fiobj_mustache_free(m); -} - -#endif diff --git a/ext/iodine/fiobj_mustache.h b/ext/iodine/fiobj_mustache.h deleted file mode 100644 index 95f636c0..00000000 --- a/ext/iodine/fiobj_mustache.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT -*/ -#ifndef H_FIOBJ_MUSTACHE_H -#define H_FIOBJ_MUSTACHE_H - -#include - -#include - -/** - * Loads a mustache template, converting it into an opaque instruction array. - * - * Returns a pointer to the instruction array or NULL (on error). - * - * The `filename` argument should contain the template's file name. - */ -mustache_s *fiobj_mustache_load(fio_str_info_s filename); - -/** - * Loads a mustache template, either from memory of a file, converting it into - * an opaque instruction array. - * - * Returns a pointer to the instruction array or NULL (on error). - * - * Accepts any of the following named arguments: - * * `char const *filename` - The root template's file name. - * * `size_t filename_len` - The file name's length. - * * `char const *data` - If set, will be used as the file's contents. - * * `size_t data_len` - If set, `data` will be used as the file's contents. - * * `mustache_error_en *err` - A container for any template load errors (see - * mustache_parser.h). - */ -mustache_s *fiobj_mustache_new(mustache_load_args_s args); -#define fiobj_mustache_new(...) \ - fiobj_mustache_new((mustache_load_args_s){__VA_ARGS__}) - -/** Free the mustache template */ -void fiobj_mustache_free(mustache_s *mustache); - -/** - * Creates a FIOBJ String containing the rendered template using the information - * in the `data` object. - * - * Returns FIOBJ_INVALID if an error occurred and a FIOBJ String on success. - */ -FIOBJ fiobj_mustache_build(mustache_s *mustache, FIOBJ data); - -/** - * Renders a template into an existing FIOBJ String (`dest`'s end), using the - * information in the `data` object. - * - * Returns FIOBJ_INVALID if an error occurred and a FIOBJ String on success. - */ -FIOBJ fiobj_mustache_build2(FIOBJ dest, mustache_s *mustache, FIOBJ data); - -#if DEBUG -void fiobj_mustache_test(void); -#endif - -#endif /* H_FIOBJ_MUSTACHE_H */ diff --git a/ext/iodine/fiobj_numbers.c b/ext/iodine/fiobj_numbers.c deleted file mode 100644 index 5402f879..00000000 --- a/ext/iodine/fiobj_numbers.c +++ /dev/null @@ -1,344 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -#include -#include - -#include - -#include -#include -#include - -#include - -/* ***************************************************************************** -Numbers Type -***************************************************************************** */ - -typedef struct { - fiobj_object_header_s head; - intptr_t i; -} fiobj_num_s; - -typedef struct { - fiobj_object_header_s head; - double f; -} fiobj_float_s; - -#define obj2num(o) ((fiobj_num_s *)FIOBJ2PTR(o)) -#define obj2float(o) ((fiobj_float_s *)FIOBJ2PTR(o)) - -/* ***************************************************************************** -Numbers VTable -***************************************************************************** */ - -static pthread_key_t num_vt_buffer_key; -static pthread_once_t num_vt_buffer_once = PTHREAD_ONCE_INIT; -static void init_num_vt_buffer_key(void) { - pthread_key_create(&num_vt_buffer_key, free); -} -static void init_num_vt_buffer_ptr(void) { - char *num_vt_buffer = malloc(sizeof(char)*512); - FIO_ASSERT_ALLOC(num_vt_buffer); - memset(num_vt_buffer, 0, sizeof(char)*512); - pthread_setspecific(num_vt_buffer_key, num_vt_buffer); -} - -static intptr_t fio_i2i(const FIOBJ o) { return obj2num(o)->i; } -static intptr_t fio_f2i(const FIOBJ o) { - return (intptr_t)floorl(obj2float(o)->f); -} -static double fio_i2f(const FIOBJ o) { return (double)obj2num(o)->i; } -static double fio_f2f(const FIOBJ o) { return obj2float(o)->f; } - -static size_t fio_itrue(const FIOBJ o) { return (obj2num(o)->i != 0); } -static size_t fio_ftrue(const FIOBJ o) { return (obj2float(o)->f != 0); } - -static fio_str_info_s fio_i2str(const FIOBJ o) { - pthread_once(&num_vt_buffer_once, init_num_vt_buffer_key); - char *num_buffer = pthread_getspecific(num_vt_buffer_key); - if (!num_buffer) { - init_num_vt_buffer_ptr(); - num_buffer = pthread_getspecific(num_vt_buffer_key); - } - return (fio_str_info_s){ - .data = num_buffer, - .len = fio_ltoa(num_buffer, obj2num(o)->i, 10), - }; -} -static fio_str_info_s fio_f2str(const FIOBJ o) { - if (isnan(obj2float(o)->f)) - return (fio_str_info_s){.data = (char *)"NaN", .len = 3}; - else if (isinf(obj2float(o)->f)) { - if (obj2float(o)->f > 0) - return (fio_str_info_s){.data = (char *)"Infinity", .len = 8}; - else - return (fio_str_info_s){.data = (char *)"-Infinity", .len = 9}; - } - pthread_once(&num_vt_buffer_once, init_num_vt_buffer_key); - char *num_buffer = pthread_getspecific(num_vt_buffer_key); - if (!num_buffer) { - init_num_vt_buffer_ptr(); - num_buffer = pthread_getspecific(num_vt_buffer_key); - } - return (fio_str_info_s){ - .data = num_buffer, - .len = fio_ftoa(num_buffer, obj2float(o)->f, 10), - }; -} - -static size_t fiobj_i_is_eq(const FIOBJ self, const FIOBJ other) { - return obj2num(self)->i == obj2num(other)->i; -} -static size_t fiobj_f_is_eq(const FIOBJ self, const FIOBJ other) { - return obj2float(self)->f == obj2float(other)->f; -} - -void fiobject___simple_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg); -uintptr_t fiobject___noop_count(FIOBJ o); - -const fiobj_object_vtable_s FIOBJECT_VTABLE_NUMBER = { - .class_name = "Number", - .to_i = fio_i2i, - .to_f = fio_i2f, - .to_str = fio_i2str, - .is_true = fio_itrue, - .is_eq = fiobj_i_is_eq, - .count = fiobject___noop_count, - .dealloc = fiobject___simple_dealloc, -}; - -const fiobj_object_vtable_s FIOBJECT_VTABLE_FLOAT = { - .class_name = "Float", - .to_i = fio_f2i, - .to_f = fio_f2f, - .is_true = fio_ftrue, - .to_str = fio_f2str, - .is_eq = fiobj_f_is_eq, - .count = fiobject___noop_count, - .dealloc = fiobject___simple_dealloc, -}; - -/* ***************************************************************************** -Number API -***************************************************************************** */ - -/** Creates a Number object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_num_new_bignum(intptr_t num) { - fiobj_num_s *o = fio_malloc(sizeof(*o)); - if (!o) { - perror("ERROR: fiobj number couldn't allocate memory"); - exit(errno); - } - *o = (fiobj_num_s){ - .head = - { - .type = FIOBJ_T_NUMBER, - .ref = 1, - }, - .i = num, - }; - return (FIOBJ)o; -} - -/** Mutates a Big Number object's value. Effects every object's reference! */ -// void fiobj_num_set(FIOBJ target, intptr_t num) { -// assert(FIOBJ_TYPE_IS(target, FIOBJ_T_NUMBER) && -// FIOBJ_IS_ALLOCATED(target)); obj2num(target)->i = num; -// } - -static pthread_key_t num_ret_key; -static pthread_once_t num_ret_once = PTHREAD_ONCE_INIT; -static void init_num_ret_key(void) { - pthread_key_create(&num_ret_key, free); -} -static void init_num_ret_ptr(void) { - fiobj_num_s *ret = malloc(sizeof(fiobj_num_s)); - FIO_ASSERT_ALLOC(ret); - memset(ret, 0, sizeof(fiobj_num_s)); - pthread_setspecific(num_ret_key, ret); -} -/** Creates a temporary Number object. This ignores `fiobj_free`. */ -FIOBJ fiobj_num_tmp(intptr_t num) { - pthread_once(&num_ret_once, init_num_ret_key); - fiobj_num_s *ret = pthread_getspecific(num_ret_key); - if (!ret) { - init_num_ret_ptr(); - ret = pthread_getspecific(num_ret_key); - } - *ret = (fiobj_num_s){ - .head = {.type = FIOBJ_T_NUMBER, .ref = ((~(uint32_t)0) >> 4)}, - .i = num, - }; - return (FIOBJ)ret; -} - -/* ***************************************************************************** -Float API -***************************************************************************** */ - -/** Creates a Float object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_float_new(double num) { - fiobj_float_s *o = fio_malloc(sizeof(*o)); - if (!o) { - perror("ERROR: fiobj float couldn't allocate memory"); - exit(errno); - } - *o = (fiobj_float_s){ - .head = - { - .type = FIOBJ_T_FLOAT, - .ref = 1, - }, - .f = num, - }; - return (FIOBJ)o; -} - -/** Mutates a Float object's value. Effects every object's reference! */ -void fiobj_float_set(FIOBJ obj, double num) { - assert(FIOBJ_TYPE_IS(obj, FIOBJ_T_FLOAT)); - obj2float(obj)->f = num; -} - -static pthread_key_t float_ret_key; -static pthread_once_t float_ret_once = PTHREAD_ONCE_INIT; -static void init_float_ret_key(void) { - pthread_key_create(&float_ret_key, free); -} -static void init_float_ret_ptr(void) { - fiobj_float_s *ret = malloc(sizeof(fiobj_float_s)); - FIO_ASSERT_ALLOC(ret); - memset(ret, 0, sizeof(fiobj_float_s)); - pthread_setspecific(float_ret_key, ret); -} -/** Creates a temporary Number object. This ignores `fiobj_free`. */ -FIOBJ fiobj_float_tmp(double num) { - pthread_once(&float_ret_once, init_float_ret_key); - fiobj_float_s *ret = pthread_getspecific(float_ret_key); - if (!ret) { - init_float_ret_ptr(); - ret = pthread_getspecific(float_ret_key); - } - *ret = (fiobj_float_s){ - .head = - { - .type = FIOBJ_T_FLOAT, - .ref = ((~(uint32_t)0) >> 4), - }, - .f = num, - }; - return (FIOBJ)ret; -} - -/* ***************************************************************************** -Numbers to Strings - Buffered -***************************************************************************** */ - -static pthread_key_t num_str_buffer_key; -static pthread_once_t num_str_buffer_once = PTHREAD_ONCE_INIT; -static void init_num_str_buffer_key(void) { - pthread_key_create(&num_str_buffer_key, free); -} -static void init_num_str_buffer_ptr(void) { - char *num_str_buffer = malloc(sizeof(char)*512); - FIO_ASSERT_ALLOC(num_str_buffer); - memset(num_str_buffer, 0, sizeof(char)*512); - pthread_setspecific(num_str_buffer_key, num_str_buffer); -} - -fio_str_info_s fio_ltocstr(long i) { - pthread_once(&num_str_buffer_once, init_num_str_buffer_key); - char *num_buffer = pthread_getspecific(num_str_buffer_key); - if (!num_buffer) { - init_num_str_buffer_ptr(); - num_buffer = pthread_getspecific(num_str_buffer_key); - } - return (fio_str_info_s){.data = num_buffer, - .len = fio_ltoa(num_buffer, i, 10)}; -} -fio_str_info_s fio_ftocstr(double f) { - pthread_once(&num_str_buffer_once, init_num_str_buffer_key); - char *num_buffer = pthread_getspecific(num_str_buffer_key); - if (!num_buffer) { - init_num_str_buffer_ptr(); - num_buffer = pthread_getspecific(num_str_buffer_key); - } - return (fio_str_info_s){.data = num_buffer, - .len = fio_ftoa(num_buffer, f, 10)}; -} - -/* ***************************************************************************** -Tests -***************************************************************************** */ - -#if DEBUG -void fiobj_test_numbers(void) { -#define NUMTEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "Testing failed.\n"); \ - exit(-1); \ - } - FIOBJ i = fiobj_num_new(8); - fprintf(stderr, "=== Testing Numbers\n"); - fprintf(stderr, "* FIOBJ_NUMBER_SIGN_MASK == %p\n", - (void *)FIOBJ_NUMBER_SIGN_MASK); - fprintf(stderr, "* FIOBJ_NUMBER_SIGN_BIT == %p\n", - (void *)FIOBJ_NUMBER_SIGN_BIT); - fprintf(stderr, "* FIOBJ_NUMBER_SIGN_EXCLUDE_BIT == %p\n", - (void *)FIOBJ_NUMBER_SIGN_EXCLUDE_BIT); - NUMTEST_ASSERT(FIOBJ_TYPE_IS(i, FIOBJ_T_NUMBER), - "* FIOBJ_TYPE_IS failed to return true."); - NUMTEST_ASSERT((FIOBJ_TYPE(i) == FIOBJ_T_NUMBER), - "* FIOBJ_TYPE failed to return type."); - NUMTEST_ASSERT(!FIOBJ_TYPE_IS(i, FIOBJ_T_NULL), - "* FIOBJ_TYPE_IS failed to return false."); - NUMTEST_ASSERT((i & FIOBJECT_NUMBER_FLAG), - "* Number 8 was dynamically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2num(i) == 8), "* Number 8 was not returned! %p\n", - (void *)i); - fiobj_free(i); - i = fiobj_num_new(-1); - NUMTEST_ASSERT((i & FIOBJECT_NUMBER_FLAG), - "* Number -1 was dynamically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2num(i) == -1), "* Number -1 was not returned! %p\n", - (void *)i); - fiobj_free(i); - i = fiobj_num_new(INTPTR_MAX); - NUMTEST_ASSERT((i & FIOBJECT_NUMBER_FLAG) == 0, - "* INTPTR_MAX was statically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2num(i) == INTPTR_MAX), - "* INTPTR_MAX was not returned! %p\n", (void *)i); - NUMTEST_ASSERT( - FIOBJ_TYPE_IS(i, FIOBJ_T_NUMBER), - "* FIOBJ_TYPE_IS failed to return true for dynamic allocation."); - NUMTEST_ASSERT((FIOBJ_TYPE(i) == FIOBJ_T_NUMBER), - "* FIOBJ_TYPE failed to return type for dynamic allocation."); - fiobj_free(i); - i = fiobj_num_new(INTPTR_MIN); - NUMTEST_ASSERT((i & FIOBJECT_NUMBER_FLAG) == 0, - "* INTPTR_MIN was statically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2num(i) == INTPTR_MIN), - "* INTPTR_MIN was not returned! %p\n", (void *)i); - fiobj_free(i); - fprintf(stderr, "* passed.\n"); - fprintf(stderr, "=== Testing Floats\n"); - i = fiobj_float_new(1.0); - NUMTEST_ASSERT(((i & FIOBJECT_NUMBER_FLAG) == 0), - "* float 1 was statically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2float(i) == 1.0), - "* Float 1.0 was not returned! %p\n", (void *)i); - fiobj_free(i); - i = fiobj_float_new(-1.0); - NUMTEST_ASSERT((i & FIOBJECT_NUMBER_FLAG) == 0, - "* Float -1 was statically allocated?! %p\n", (void *)i); - NUMTEST_ASSERT((fiobj_obj2float(i) == -1.0), - "* Float -1 was not returned! %p\n", (void *)i); - fiobj_free(i); - fprintf(stderr, "* passed.\n"); -} -#endif diff --git a/ext/iodine/fiobj_numbers.h b/ext/iodine/fiobj_numbers.h deleted file mode 100644 index a805ad4b..00000000 --- a/ext/iodine/fiobj_numbers.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef H_FIOBJ_NUMBERS_H -#define H_FIOBJ_NUMBERS_H -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -Numbers API (Integers) -***************************************************************************** */ - -/** Creates a Number object. Remember to use `fiobj_free`. */ -FIO_INLINE FIOBJ fiobj_num_new(intptr_t num); - -/** Creates a temporary Number object. Avoid using `fiobj_free`. */ -FIOBJ fiobj_num_tmp(intptr_t num); - -/* ***************************************************************************** -Float API (Double) -***************************************************************************** */ - -/** Creates a Float object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_float_new(double num); - -/** Mutates a Float object's value. Effects every object's reference! */ -void fiobj_float_set(FIOBJ obj, double num); - -/** Creates a temporary Float object. Avoid using `fiobj_free`. */ -FIOBJ fiobj_float_tmp(double num); - -/* ***************************************************************************** -Numerical Helpers: not FIOBJ specific, but included as part of the library -***************************************************************************** */ - -/** - * A helper function that converts between String data to a signed int64_t. - * - * Numbers are assumed to be in base 10. - * - * The `0x##` (or `x##`) and `0b##` (or `b##`) are recognized as base 16 and - * base 2 (binary MSB first) respectively. - * - * The pointer will be updated to point to the first byte after the number. - */ -int64_t fio_atol(char **pstr); - -/** A helper function that converts between String data to a signed double. */ -double fio_atof(char **pstr); - -/** - * A helper function that converts between a signed int64_t to a string. - * - * No overflow guard is provided, make sure there's at least 66 bytes available - * (for base 2). - * - * Supports base 2, base 10 and base 16. An unsupported base will silently - * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the - * beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL terminator). - */ -size_t fio_ltoa(char *dest, int64_t num, uint8_t base); - -/** - * A helper function that converts between a double to a string. - * - * No overflow guard is provided, make sure there's at least 130 bytes available - * (for base 2). - * - * Supports base 2, base 10 and base 16. An unsupported base will silently - * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the - * beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL terminator). - */ -size_t fio_ftoa(char *dest, double num, uint8_t base); - -/** Converts a number to a temporary, thread safe, C string object */ -fio_str_info_s __attribute__((deprecated("use local buffer with fio_ltoa"))) -fio_ltocstr(long); - -/** Converts a float to a temporary, thread safe, C string object */ -fio_str_info_s __attribute__((deprecated("use local buffer with fio_ftoa"))) -fio_ftocstr(double); - -/* ***************************************************************************** -Pointer Wrapping Helper MACROs (uses integers) -***************************************************************************** */ - -#define fiobj_ptr_wrap(ptr) fiobj_num_new((uintptr_t)(ptr)) -#define fiobj_ptr_unwrap(obj) ((void *)fiobj_obj2num((obj))) - -/* ***************************************************************************** -Inline Number Initialization -***************************************************************************** */ - -FIOBJ fiobj_num_new_bignum(intptr_t num); - -/** Creates a Number object. Remember to use `fiobj_free`. */ -FIO_INLINE FIOBJ fiobj_num_new(intptr_t num) { - if ((((uintptr_t)num & - (FIOBJ_NUMBER_SIGN_BIT | FIOBJ_NUMBER_SIGN_EXCLUDE_BIT)) == 0) || - (((uintptr_t)num & - (FIOBJ_NUMBER_SIGN_BIT | FIOBJ_NUMBER_SIGN_EXCLUDE_BIT)) == - (FIOBJ_NUMBER_SIGN_BIT | FIOBJ_NUMBER_SIGN_EXCLUDE_BIT))) { - const uintptr_t num_abs = (uintptr_t)num & FIOBJ_NUMBER_SIGN_MASK; - const uintptr_t num_sign = (uintptr_t)num & FIOBJ_NUMBER_SIGN_BIT; - return ((num_abs << 1) | num_sign | FIOBJECT_NUMBER_FLAG); - } - return fiobj_num_new_bignum(num); -} - -#if DEBUG -void fiobj_test_numbers(void); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/ext/iodine/fiobj_str.c b/ext/iodine/fiobj_str.c deleted file mode 100644 index ecaf03d1..00000000 --- a/ext/iodine/fiobj_str.c +++ /dev/null @@ -1,433 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#endif - -#ifdef _SC_PAGESIZE -#define PAGE_SIZE sysconf(_SC_PAGESIZE) -#else -#define PAGE_SIZE 4096 -#endif - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#define FIO_INCLUDE_STR -#define FIO_STR_NO_REF -#include - -#ifndef PATH_MAX -#define PATH_MAX PAGE_SIZE -#endif - -#include - -/* ***************************************************************************** -String Type -***************************************************************************** */ - -typedef struct { - fiobj_object_header_s head; - uint64_t hash; - fio_str_s str; -} fiobj_str_s; - -#define obj2str(o) ((fiobj_str_s *)(FIOBJ2PTR(o))) - -static inline fio_str_info_s fiobj_str_get_cstr(const FIOBJ o) { - return fio_str_info(&obj2str(o)->str); -} - -/* ***************************************************************************** -String VTables -***************************************************************************** */ - -static fio_str_info_s fio_str2str(const FIOBJ o) { - return fiobj_str_get_cstr(o); -} - -static void fiobj_str_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg) { - fio_str_free(&obj2str(o)->str); - fio_free(FIOBJ2PTR(o)); - (void)task; - (void)arg; -} - -static size_t fiobj_str_is_eq(const FIOBJ self, const FIOBJ other) { - return fio_str_iseq(&obj2str(self)->str, &obj2str(other)->str); -} - -static intptr_t fio_str2i(const FIOBJ o) { - char *pos = fio_str_data(&obj2str(o)->str); - return fio_atol(&pos); -} -static double fio_str2f(const FIOBJ o) { - char *pos = fio_str_data(&obj2str(o)->str); - return fio_atof(&pos); -} - -static size_t fio_str2bool(const FIOBJ o) { - return fio_str_len(&obj2str(o)->str) != 0; -} - -uintptr_t fiobject___noop_count(const FIOBJ o); - -const fiobj_object_vtable_s FIOBJECT_VTABLE_STRING = { - .class_name = "String", - .dealloc = fiobj_str_dealloc, - .to_i = fio_str2i, - .to_f = fio_str2f, - .to_str = fio_str2str, - .is_eq = fiobj_str_is_eq, - .is_true = fio_str2bool, - .count = fiobject___noop_count, -}; - -/* ***************************************************************************** -String API -***************************************************************************** */ - -/** Creates a buffer String object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_str_buf(size_t capa) { - if (capa) - capa = capa + 1; - else - capa = PAGE_SIZE; - - fiobj_str_s *s = fio_malloc(sizeof(*s)); - if (!s) { - perror("ERROR: fiobj string couldn't allocate memory"); - exit(errno); - } - *s = (fiobj_str_s){ - .head = - { - .ref = 1, - .type = FIOBJ_T_STRING, - }, - .str = FIO_STR_INIT, - }; - if (capa) { - fio_str_capa_assert(&s->str, capa); - } - return ((uintptr_t)s | FIOBJECT_STRING_FLAG); -} - -/** Creates a String object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_str_new(const char *str, size_t len) { - fiobj_str_s *s = fio_malloc(sizeof(*s)); - if (!s) { - perror("ERROR: fiobj string couldn't allocate memory"); - exit(errno); - } - *s = (fiobj_str_s){ - .head = - { - .ref = 1, - .type = FIOBJ_T_STRING, - }, - .str = FIO_STR_INIT, - }; - if (str && len) { - fio_str_write(&s->str, str, len); - } - return ((uintptr_t)s | FIOBJECT_STRING_FLAG); -} - -/** - * Creates a String object. Remember to use `fiobj_free`. - * - * It's possible to wrap a previosly allocated memory block in a FIOBJ String - * object, as long as it was allocated using `fio_malloc`. - * - * The ownership of the memory indicated by `str` will "move" to the object and - * will be freed (using `fio_free`) once the object's reference count drops to - * zero. - */ -FIOBJ fiobj_str_move(char *str, size_t len, size_t capacity) { - fiobj_str_s *s = fio_malloc(sizeof(*s)); - if (!s) { - perror("ERROR: fiobj string couldn't allocate memory"); - exit(errno); - } - *s = (fiobj_str_s){ - .head = - { - .ref = 1, - .type = FIOBJ_T_STRING, - }, - .str = FIO_STR_INIT_EXISTING(str, len, capacity), - }; - return ((uintptr_t)s | FIOBJECT_STRING_FLAG); -} - -static pthread_key_t str_tmp_key; -static pthread_once_t str_tmp_once = PTHREAD_ONCE_INIT; -static void init_str_tmp_key(void) { - pthread_key_create(&str_tmp_key, free); -} -static void init_str_tmp_key_ptr(void) { - fiobj_str_s *tmp = malloc(sizeof(fiobj_str_s)); - FIO_ASSERT_ALLOC(tmp); - tmp->head.ref = ((~(uint32_t)0) >> 4); - tmp->head.type = FIOBJ_T_STRING; - tmp->str.small = 1; - pthread_setspecific(str_tmp_key, tmp); -} -/** - * Returns a thread-static temporary string. Avoid calling `fiobj_dup` or - * `fiobj_free`. - */ -FIOBJ fiobj_str_tmp(void) { - pthread_once(&str_tmp_once, init_str_tmp_key); - fiobj_str_s *tmp = (fiobj_str_s *)pthread_getspecific(str_tmp_key); - if (!tmp) { - init_str_tmp_key_ptr(); - tmp = (fiobj_str_s *)pthread_getspecific(str_tmp_key); - } - tmp->str.frozen = 0; - fio_str_resize(&tmp->str, 0); - return ((uintptr_t)tmp | FIOBJECT_STRING_FLAG); -} - -/** Prevents the String object from being changed. */ -void fiobj_str_freeze(FIOBJ str) { - if (FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)) - fio_str_freeze(&obj2str(str)->str); -} - -/** Confirms the requested capacity is available and allocates as required. */ -size_t fiobj_str_capa_assert(FIOBJ str, size_t size) { - - assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)); - if (obj2str(str)->str.frozen) - return 0; - fio_str_info_s state = fio_str_capa_assert(&obj2str(str)->str, size); - return state.capa; -} - -/** Return's a String's capacity, if any. */ -size_t fiobj_str_capa(FIOBJ str) { - assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)); - return fio_str_capa(&obj2str(str)->str); -} - -/** Resizes a String object, allocating more memory if required. */ -void fiobj_str_resize(FIOBJ str, size_t size) { - assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)); - fio_str_resize(&obj2str(str)->str, size); - obj2str(str)->hash = 0; - return; -} - -/** Deallocates any unnecessary memory (if supported by OS). */ -void fiobj_str_compact(FIOBJ str) { - assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)); - fio_str_compact(&obj2str(str)->str); - return; -} - -/** Empties a String's data. */ -void fiobj_str_clear(FIOBJ str) { - assert(FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)); - fio_str_resize(&obj2str(str)->str, 0); - obj2str(str)->hash = 0; -} - -/** - * Writes data at the end of the string, resizing the string as required. - * Returns the new length of the String - */ -size_t fiobj_str_write(FIOBJ dest, const char *data, size_t len) { - assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (obj2str(dest)->str.frozen) - return 0; - obj2str(dest)->hash = 0; - return fio_str_write(&obj2str(dest)->str, data, len).len; -} - -/** - * Writes a number at the end of the String using normal base 10 notation. - * - * Returns the new length of the String - */ -size_t fiobj_str_write_i(FIOBJ dest, int64_t num) { - assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (obj2str(dest)->str.frozen) - return 0; - obj2str(dest)->hash = 0; - return fio_str_write_i(&obj2str(dest)->str, num).len; -} - -/** - * Writes data at the end of the string, resizing the string as required. - * Returns the new length of the String - */ -size_t fiobj_str_printf(FIOBJ dest, const char *format, ...) { - assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (obj2str(dest)->str.frozen) - return 0; - obj2str(dest)->hash = 0; - va_list argv; - va_start(argv, format); - fio_str_info_s state = fio_str_vprintf(&obj2str(dest)->str, format, argv); - va_end(argv); - return state.len; -} - -size_t fiobj_str_vprintf(FIOBJ dest, const char *format, va_list argv) { - assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (obj2str(dest)->str.frozen) - return 0; - obj2str(dest)->hash = 0; - fio_str_info_s state = fio_str_vprintf(&obj2str(dest)->str, format, argv); - return state.len; -} - -/** Dumps the `filename` file's contents at the end of a String. If `limit == - * 0`, than the data will be read until EOF. - * - * If the file can't be located, opened or read, or if `start_at` is beyond - * the EOF position, NULL is returned. - * - * Remember to use `fiobj_free`. - */ -size_t fiobj_str_readfile(FIOBJ dest, const char *filename, intptr_t start_at, - intptr_t limit) { - fio_str_info_s state = - fio_str_readfile(&obj2str(dest)->str, filename, start_at, limit); - return state.len; -} - -/** - * Writes data at the end of the string, resizing the string as required. - * Returns the new length of the String - */ -size_t fiobj_str_concat(FIOBJ dest, FIOBJ obj) { - assert(FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING)); - if (obj2str(dest)->str.frozen) - return 0; - obj2str(dest)->hash = 0; - fio_str_info_s o = fiobj_obj2cstr(obj); - if (o.len == 0) - return fio_str_len(&obj2str(dest)->str); - return fio_str_write(&obj2str(dest)->str, o.data, o.len).len; -} - -/** - * Calculates a String's SipHash value for use as a HashMap key. - */ -uint64_t fiobj_str_hash(FIOBJ o) { - assert(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING)); - // if (obj2str(o)->is_small) { - // return fiobj_hash_string(STR_INTENAL_STR(o), STR_INTENAL_LEN(o)); - // } else - if (obj2str(o)->hash) { - return obj2str(o)->hash; - } - fio_str_info_s state = fio_str_info(&obj2str(o)->str); - obj2str(o)->hash = fiobj_hash_string(state.data, state.len); - return obj2str(o)->hash; -} - -/* ***************************************************************************** -Tests -***************************************************************************** */ - -#if DEBUG -void fiobj_test_string(void) { - fprintf(stderr, "=== Testing Strings\n"); - fprintf(stderr, "* Internal String Capacity %u \n", - (unsigned int)FIO_STR_SMALL_CAPA); -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, "* " __VA_ARGS__); \ - fprintf(stderr, "Testing failed.\n"); \ - exit(-1); \ - } -#define STR_EQ(o, str) \ - TEST_ASSERT((fiobj_str_getlen(o) == strlen(str) && \ - !memcmp(fiobj_str_mem_addr(o), str, strlen(str))), \ - "String not equal to " str) - FIOBJ o = fiobj_str_new("Hello", 5); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), "Small String isn't string!\n"); - TEST_ASSERT(obj2str(o)->str.small, "Hello isn't small\n"); - fiobj_str_write(o, " World", 6); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), - "Hello World String isn't string!\n"); - TEST_ASSERT(obj2str(o)->str.small, "Hello World isn't small\n"); - TEST_ASSERT(fiobj_obj2cstr(o).len == 11, - "Invalid small string length (%u != 11)!\n", - (unsigned int)fiobj_obj2cstr(o).len) - fiobj_str_write(o, " World, you crazy longer sleep loving person :-)", 48); - TEST_ASSERT(!obj2str(o)->str.small, "Crazier shouldn't be small\n"); - fiobj_free(o); - - o = fiobj_str_new( - "hello my dear friend, I hope that your are well and happy.", 58); - TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING), "Long String isn't string!\n"); - TEST_ASSERT(!obj2str(o)->str.small, - "Long String is small! (capa: %lu, len: %lu)\n", - fio_str_capa(&obj2str(o)->str), fio_str_len(&obj2str(o)->str)); - TEST_ASSERT(fiobj_obj2cstr(o).len == 58, - "Invalid long string length (%lu != 58)!\n", - fiobj_obj2cstr(o).len) - uint64_t hash = fiobj_str_hash(o); - TEST_ASSERT(!obj2str(o)->str.frozen, "String forzen when only hashing!\n"); - fiobj_str_freeze(o); - TEST_ASSERT(obj2str(o)->str.frozen, "String not forzen!\n"); - fiobj_str_write(o, " World", 6); - TEST_ASSERT(hash == fiobj_str_hash(o), - "String hash changed after hashing - not frozen?\n"); - TEST_ASSERT(fiobj_obj2cstr(o).len == 58, - "String was edited after hashing - not frozen!\n (%lu): %s", - (unsigned long)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data); - fiobj_free(o); - - o = fiobj_str_buf(1); - fiobj_str_printf(o, "%u", 42); - TEST_ASSERT(fio_str_len(&obj2str(o)->str) == 2, - "fiobj_strprintf length error.\n"); - TEST_ASSERT(fiobj_obj2num(o), "fiobj_strprintf integer error.\n"); - TEST_ASSERT(!memcmp(fiobj_obj2cstr(o).data, "42", 2), - "fiobj_strprintf string error.\n"); - fiobj_free(o); - - o = fiobj_str_buf(4); - for (int i = 0; i < 16000; ++i) { - fiobj_str_write(o, "a", 1); - } - TEST_ASSERT(fio_str_len(&obj2str(o)->str) == 16000, - "16K fiobj_str_write not 16K.\n"); - TEST_ASSERT(fio_str_capa(&obj2str(o)->str) >= 16000, - "16K fiobj_str_write capa not enough.\n"); - fiobj_free(o); - - o = fiobj_str_buf(0); - TEST_ASSERT(fiobj_str_readfile(o, __FILE__, 0, 0), - "`fiobj_str_readfile` - file wasn't read!"); - TEST_ASSERT(!memcmp(fiobj_obj2cstr(o).data, "/*", 2), - "`fiobj_str_readfile` error, start of file doesn't match:\n%s", - fiobj_obj2cstr(o).data); - fiobj_free(o); - - fprintf(stderr, "* passed.\n"); -} -#endif diff --git a/ext/iodine/fiobj_str.h b/ext/iodine/fiobj_str.h deleted file mode 100644 index 8b563736..00000000 --- a/ext/iodine/fiobj_str.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef H_FIOBJ_STR_H -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#define H_FIOBJ_STR_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define FIOBJ_IS_STRING(obj) FIOBJ_TYPE_IS((obj), FIOBJ_T_STRING) - -/* ***************************************************************************** -API: Creating a String Object -***************************************************************************** */ - -/** Creates a String object. Remember to use `fiobj_free`. */ -FIOBJ fiobj_str_new(const char *str, size_t len); - -/** - * Creates a String object with pre-allocation for Strings up to `capa` long. - * - * If `capa` is zero, a whole memory page will be allocated. - * - * Remember to use `fiobj_free`. - */ -FIOBJ fiobj_str_buf(size_t capa); - -/** Creates a copy from an existing String. Remember to use `fiobj_free`. */ -static inline __attribute__((unused)) FIOBJ fiobj_str_copy(FIOBJ src) { - fio_str_info_s s = fiobj_obj2cstr(src); - return fiobj_str_new(s.data, s.len); -} - -/** - * Creates a String object. Remember to use `fiobj_free`. - * - * It's possible to wrap a previosly allocated memory block in a FIOBJ String - * object, as long as it was allocated using `fio_malloc`. - * - * The ownership of the memory indicated by `str` will "move" to the object and - * will be freed (using `fio_free`) once the object's reference count drops to - * zero. - * - * Note: The original memory MUST be allocated using `fio_malloc` (NOT the - * system's `malloc`) and it will be freed using `fio_free`. - */ -FIOBJ fiobj_str_move(char *str, size_t len, size_t capacity); - -/** - * Returns a thread-static temporary string. Avoid calling `fiobj_dup` or - * `fiobj_free`. - */ -FIOBJ fiobj_str_tmp(void); - -/* ***************************************************************************** -API: Editing a String -***************************************************************************** */ - -/** - * Prevents the String object from being changed. - * - * When a String is used as a key for a Hash, it is automatically frozen to - * prevent the Hash from becoming broken. - */ -void fiobj_str_freeze(FIOBJ str); - -/** - * Confirms the String allows for the requested capacity (counting used space as - * well as free space). - * - * Returns updated capacity. - */ -size_t fiobj_str_capa_assert(FIOBJ str, size_t size); - -/** Returns a String's capacity, if any. This should include the NUL byte. */ -size_t fiobj_str_capa(FIOBJ str); - -/** Resizes a String object, allocating more memory if required. */ -void fiobj_str_resize(FIOBJ str, size_t size); - -/** - * Performs a best attempt at minimizing memory consumption. - * - * Actual effects depend on the underlying memory allocator and it's - * implementation. Not all allocators will free any memory. - */ -void fiobj_str_compact(FIOBJ str); - -/** Alias for `fiobj_str_compact`. */ -#define fiobj_str_minimize(str) fiobj_str_compact((str)) - -/** Empties a String's data. */ -void fiobj_str_clear(FIOBJ str); - -/** - * Writes data at the end of the string, resizing the string as required. - * Returns the new length of the String - */ -size_t fiobj_str_write(FIOBJ dest, const char *data, size_t len); - -/** - * Writes a number at the end of the String using normal base 10 notation. - * - * Returns the new length of the String - */ -size_t fiobj_str_write_i(FIOBJ dest, int64_t num); - -/** - * Writes data at the end of the string using a printf like interface, resizing - * the string as required. Returns the new length of the String - */ -__attribute__((format(printf, 2, 3))) size_t -fiobj_str_printf(FIOBJ dest, const char *format, ...); - -/** - * Writes data at the end of the string using a vprintf like interface, resizing - * the string as required. - * - * Returns the new length of the String - */ -__attribute__((format(printf, 2, 0))) size_t -fiobj_str_vprintf(FIOBJ dest, const char *format, va_list argv); - -/** - * Writes data at the end of the string, resizing the string as required. - * - * Remember to call `fiobj_free` to free the source (when done with it). - * - * Returns the new length of the String. - */ -size_t fiobj_str_concat(FIOBJ dest, FIOBJ source); -#define fiobj_str_join(dest, src) fiobj_str_concat((dest), (src)) - -/** - * Dumps the `filename` file's contents at the end of the String. - * - * If `limit == 0`, than the data will be read until EOF. - * - * If the file can't be located, opened or read, or if `start_at` is out of - * bounds (i.e., beyond the EOF position), FIOBJ_INVALID is returned. - * - * If `start_at` is negative, it will be computed from the end of the file. - * - * Remember to use `fiobj_free`. - * - * NOTE: Requires a UNIX system, otherwise always returns FIOBJ_INVALID. - */ -size_t fiobj_str_readfile(FIOBJ dest, const char *filename, intptr_t start_at, - intptr_t limit); - -/* ***************************************************************************** -API: String Values -***************************************************************************** */ - -/** - * Calculates a String's SipHash value for possible use as a HashMap key. - */ -uint64_t fiobj_str_hash(FIOBJ o); - -#if DEBUG -void fiobj_test_string(void); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/ext/iodine/fiobject.c b/ext/iodine/fiobject.c deleted file mode 100644 index 4e5f0432..00000000 --- a/ext/iodine/fiobject.c +++ /dev/null @@ -1,620 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -/** -This facil.io core library provides wrappers around complex and (or) dynamic -types, abstracting some complexity and making dynamic type related tasks easier. -*/ - -#include - -#define FIO_ARY_NAME fiobj_stack -#define FIO_ARY_TYPE FIOBJ -#define FIO_ARY_INVALID FIOBJ_INVALID -/* don't free or compare objects, this stack shouldn't have side-effects */ -#include - -#include -#include -#include -#include -#include - -/* ***************************************************************************** -Use the facil.io features when available, but override when missing. -***************************************************************************** */ -#ifndef fd_data /* defined in fio.c */ - -#pragma weak fio_malloc -void *fio_malloc(size_t size) { - void *m = malloc(size); - if (m) - memset(m, 0, size); - return m; -} - -#pragma weak fio_calloc -void *__attribute__((weak)) fio_calloc(size_t size, size_t count) { - return calloc(size, count); -} - -#pragma weak fio_free -void __attribute__((weak)) fio_free(void *ptr) { free(ptr); } - -#pragma weak fio_realloc -void *__attribute__((weak)) fio_realloc(void *ptr, size_t new_size) { - return realloc(ptr, new_size); -} - -#pragma weak fio_realloc2 -void *__attribute__((weak)) -fio_realloc2(void *ptr, size_t new_size, size_t valid_len) { - return realloc(ptr, new_size); - (void)valid_len; -} - -#pragma weak fio_mmap -void *__attribute__((weak)) fio_mmap(size_t size) { return fio_malloc(size); } - -/** The logging level */ -#if DEBUG -#pragma weak FIO_LOG_LEVEL -int __attribute__((weak)) FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEBUG; -#else -#pragma weak FIO_LOG_LEVEL -int __attribute__((weak)) FIO_LOG_LEVEL = FIO_LOG_LEVEL_INFO; -#endif - -/** - * We include this in case the parser is used outside of facil.io. - */ -int64_t __attribute__((weak)) fio_atol(char **pstr) { - return strtoll(*pstr, pstr, 0); -} -#pragma weak fio_atol - -/** - * We include this in case the parser is used outside of facil.io. - */ -double __attribute__((weak)) fio_atof(char **pstr) { - return strtod(*pstr, pstr); -} -#pragma weak fio_atof - -/** - * A helper function that writes a signed int64_t to a string. - * - * No overflow guard is provided, make sure there's at least 68 bytes - * available (for base 2). - * - * Offers special support for base 2 (binary), base 8 (octal), base 10 and base - * 16 (hex). An unsupported base will silently default to base 10. Prefixes - * are automatically added (i.e., "0x" for hex and "0b" for base 2). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -#pragma weak fio_ltoa -size_t __attribute__((weak)) fio_ltoa(char *dest, int64_t num, uint8_t base) { - const char notation[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - size_t len = 0; - char buf[48] = {0}; /* we only need up to 20 for base 10, but base 3 needs 41... */ - - if (!num) - goto zero; - - switch (base) { - case 1: /* fallthrough */ - case 2: - /* Base 2 */ - { - uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */ - uint8_t i = 0; /* counting bits */ - dest[len++] = '0'; - dest[len++] = 'b'; - - while ((i < 64) && (n & 0x8000000000000000) == 0) { - n = n << 1; - i++; - } - /* make sure the Binary representation doesn't appear signed. */ - if (i) { - dest[len++] = '0'; - } - /* write to dest. */ - while (i < 64) { - dest[len++] = ((n & 0x8000000000000000) ? '1' : '0'); - n = n << 1; - i++; - } - dest[len] = 0; - return len; - } - case 8: - /* Base 8 */ - { - uint64_t l = 0; - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - dest[len++] = '0'; - - while (num) { - buf[l++] = '0' + (num & 7); - num = num >> 3; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - } - - case 16: - /* Base 16 */ - { - uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */ - uint8_t i = 0; /* counting bits */ - dest[len++] = '0'; - dest[len++] = 'x'; - while (i < 8 && (n & 0xFF00000000000000) == 0) { - n = n << 8; - i++; - } - /* make sure the Hex representation doesn't appear signed. */ - if (i && (n & 0x8000000000000000)) { - dest[len++] = '0'; - dest[len++] = '0'; - } - /* write the damn thing */ - while (i < 8) { - uint8_t tmp = (n & 0xF000000000000000) >> 60; - dest[len++] = notation[tmp]; - tmp = (n & 0x0F00000000000000) >> 56; - dest[len++] = notation[tmp]; - i++; - n = n << 8; - } - dest[len] = 0; - return len; - } - case 3: /* fallthrough */ - case 4: /* fallthrough */ - case 5: /* fallthrough */ - case 6: /* fallthrough */ - case 7: /* fallthrough */ - case 9: /* fallthrough */ - /* rare bases */ - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - uint64_t l = 0; - while (num) { - uint64_t t = num / base; - buf[l++] = '0' + (num - (t * base)); - num = t; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - - default: - break; - } - /* Base 10, the default base */ - - if (num < 0) { - dest[len++] = '-'; - num = 0 - num; - } - uint64_t l = 0; - while (num) { - uint64_t t = num / 10; - buf[l++] = '0' + (num - (t * 10)); - num = t; - } - while (l) { - --l; - dest[len++] = buf[l]; - } - dest[len] = 0; - return len; - -zero: - switch (base) { - case 1: /* fallthrough */ - case 2: - dest[len++] = '0'; - dest[len++] = 'b'; - /* fallthrough */ - case 16: - dest[len++] = '0'; - dest[len++] = 'x'; - dest[len++] = '0'; - } - dest[len++] = '0'; - dest[len] = 0; - return len; -} - -/** - * A helper function that converts between a double to a string. - * - * No overflow guard is provided, make sure there's at least 130 bytes - * available (for base 2). - * - * Supports base 2, base 10 and base 16. An unsupported base will silently - * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the - * beginning of the string). - * - * Returns the number of bytes actually written (excluding the NUL - * terminator). - */ -#pragma weak fio_ftoa -size_t __attribute__((weak)) fio_ftoa(char *dest, double num, uint8_t base) { - if (base == 2 || base == 16) { - /* handle the binary / Hex representation the same as if it were an - * int64_t - */ - int64_t *i = (void *)# - return fio_ltoa(dest, *i, base); - } - - size_t written = sprintf(dest, "%g", num); - uint8_t need_zero = 1; - char *start = dest; - while (*start) { - if (*start == ',') // locale issues? - *start = '.'; - if (*start == '.' || *start == 'e') { - need_zero = 0; - break; - } - start++; - } - if (need_zero) { - dest[written++] = '.'; - dest[written++] = '0'; - } - return written; -} - -#endif -/* ***************************************************************************** -the `fiobj_each2` function -***************************************************************************** */ - -struct task_packet_s { - int (*task)(FIOBJ obj, void *arg); - void *arg; - fiobj_stack_s *stack; - FIOBJ next; - uintptr_t counter; - uint8_t stop; - uint8_t incomplete; -}; - -static int fiobj_task_wrapper(FIOBJ o, void *p_) { - struct task_packet_s *p = p_; - ++p->counter; - int ret = p->task(o, p->arg); - if (ret == -1) { - p->stop = 1; - return -1; - } - if (FIOBJ_IS_ALLOCATED(o) && FIOBJECT2VTBL(o)->each) { - p->incomplete = 1; - p->next = o; - return -1; - } - return 0; -} -/** - * Single layer iteration using a callback for each nested fio object. - * - * Accepts any `FIOBJ ` type but only collections (Arrays and Hashes) are - * processed. The container itself (the Array or the Hash) is **not** processed - * (unlike `fiobj_each2`). - * - * The callback task function must accept an object and an opaque user pointer. - * - * Hash objects pass along a `FIOBJ_T_COUPLET` object, containing - * references for both the key and the object. Keys shouldn't be altered once - * placed as a key (or the Hash will break). Collections (Arrays / Hashes) can't - * be used as keeys. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the "stop" position, i.e., the number of items processed + the - * starting point. - */ -size_t fiobj_each2(FIOBJ o, int (*task)(FIOBJ obj, void *arg), void *arg) { - if (!o || !FIOBJ_IS_ALLOCATED(o) || (FIOBJECT2VTBL(o)->each == NULL)) { - task(o, arg); - return 1; - } - /* run task for root object */ - if (task(o, arg) == -1) - return 1; - uintptr_t pos = 0; - fiobj_stack_s stack = FIO_ARY_INIT; - struct task_packet_s packet = { - .task = task, - .arg = arg, - .stack = &stack, - .counter = 1, - }; - do { - if (!pos) - packet.next = 0; - packet.incomplete = 0; - pos = FIOBJECT2VTBL(o)->each(o, pos, fiobj_task_wrapper, &packet); - if (packet.stop) - goto finish; - if (packet.incomplete) { - fiobj_stack_push(&stack, pos); - fiobj_stack_push(&stack, o); - } - - if (packet.next) { - fiobj_stack_push(&stack, (FIOBJ)0); - fiobj_stack_push(&stack, packet.next); - } - o = FIOBJ_INVALID; - fiobj_stack_pop(&stack, &o); - fiobj_stack_pop(&stack, &pos); - } while (o); -finish: - fiobj_stack_free(&stack); - return packet.counter; -} - -/* ***************************************************************************** -Free complex objects (objects with nesting) -***************************************************************************** */ - -static void fiobj_dealloc_task(FIOBJ o, void *stack_) { - // if (!o) - // fprintf(stderr, "* WARN: freeing a NULL no-object\n"); - // else - // fprintf(stderr, "* freeing object %s\n", fiobj_obj2cstr(o).data); - if (!o || !FIOBJ_IS_ALLOCATED(o)) - return; - if (OBJREF_REM(o)) - return; - if (!FIOBJECT2VTBL(o)->each || !FIOBJECT2VTBL(o)->count(o)) { - FIOBJECT2VTBL(o)->dealloc(o, NULL, NULL); - return; - } - fiobj_stack_s *s = stack_; - fiobj_stack_push(s, o); -} -/** - * Decreases an object's reference count, releasing memory and - * resources. - * - * This function affects nested objects, meaning that when an Array or - * a Hash object is passed along, it's children (nested objects) are - * also freed. - */ -void fiobj_free_complex_object(FIOBJ o) { - fiobj_stack_s stack = FIO_ARY_INIT; - do { - FIOBJECT2VTBL(o)->dealloc(o, fiobj_dealloc_task, &stack); - } while (!fiobj_stack_pop(&stack, &o)); - fiobj_stack_free(&stack); -} - -/* ***************************************************************************** -Is Equal? -***************************************************************************** */ -#include - -static inline int fiobj_iseq_simple(const FIOBJ o, const FIOBJ o2) { - if (o == o2) - return 1; - if (!o || !o2) - return 0; /* they should have compared equal before. */ - if (!FIOBJ_IS_ALLOCATED(o) || !FIOBJ_IS_ALLOCATED(o2)) - return 0; /* they should have compared equal before. */ - if (FIOBJECT2HEAD(o)->type != FIOBJECT2HEAD(o2)->type) - return 0; /* non-type equality is a barriar to equality. */ - if (!FIOBJECT2VTBL(o)->is_eq(o, o2)) - return 0; - return 1; -} - -static int fiobj_iseq____internal_complex__task(FIOBJ o, void *ary_) { - fiobj_stack_s *ary = ary_; - fiobj_stack_push(ary, o); - if (fiobj_hash_key_in_loop()) - fiobj_stack_push(ary, fiobj_hash_key_in_loop()); - return 0; -} - -/** used internally for complext nested tests (Array / Hash types) */ -int fiobj_iseq____internal_complex__(FIOBJ o, FIOBJ o2) { - // if (FIOBJECT2VTBL(o)->each && FIOBJECT2VTBL(o)->count(o)) - // return int fiobj_iseq____internal_complex__(const FIOBJ o, const FIOBJ - // o2); - fiobj_stack_s left = FIO_ARY_INIT, right = FIO_ARY_INIT, queue = FIO_ARY_INIT; - do { - fiobj_each1(o, 0, fiobj_iseq____internal_complex__task, &left); - fiobj_each1(o2, 0, fiobj_iseq____internal_complex__task, &right); - while (fiobj_stack_count(&left)) { - o = FIOBJ_INVALID; - o2 = FIOBJ_INVALID; - fiobj_stack_pop(&left, &o); - fiobj_stack_pop(&right, &o2); - if (!fiobj_iseq_simple(o, o2)) - goto unequal; - if (FIOBJ_IS_ALLOCATED(o) && FIOBJECT2VTBL(o)->each && - FIOBJECT2VTBL(o)->count(o)) { - fiobj_stack_push(&queue, o); - fiobj_stack_push(&queue, o2); - } - } - o = FIOBJ_INVALID; - o2 = FIOBJ_INVALID; - fiobj_stack_pop(&queue, &o2); - fiobj_stack_pop(&queue, &o); - if (!fiobj_iseq_simple(o, o2)) - goto unequal; - } while (o); - fiobj_stack_free(&left); - fiobj_stack_free(&right); - fiobj_stack_free(&queue); - return 1; -unequal: - fiobj_stack_free(&left); - fiobj_stack_free(&right); - fiobj_stack_free(&queue); - return 0; -} - -/* ***************************************************************************** -Defaults / NOOPs -***************************************************************************** */ - -void fiobject___noop_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), void *arg) { - (void)o; - (void)task; - (void)arg; -} -void fiobject___simple_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), - void *arg) { - fio_free(FIOBJ2PTR(o)); - (void)task; - (void)arg; -} - -uintptr_t fiobject___noop_count(const FIOBJ o) { - (void)o; - return 0; -} -size_t fiobject___noop_is_eq(const FIOBJ o1, const FIOBJ o2) { - (void)o1; - (void)o2; - return 0; -} - -fio_str_info_s fiobject___noop_to_str(const FIOBJ o) { - (void)o; - return (fio_str_info_s){.len = 0, .data = NULL}; -} -intptr_t fiobject___noop_to_i(const FIOBJ o) { - (void)o; - return 0; -} -double fiobject___noop_to_f(const FIOBJ o) { - (void)o; - return 0; -} - -#if DEBUG - -#include -#include - -static int fiobject_test_task(FIOBJ o, void *arg) { - ++((uintptr_t *)arg)[0]; - if (!o) - fprintf(stderr, "* WARN: counting a NULL no-object\n"); - // else - // fprintf(stderr, "* counting object %s\n", fiobj_obj2cstr(o).data); - return 0; - (void)o; -} - -void fiobj_test_core(void) { -#define TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "Testing failed.\n"); \ - exit(-1); \ - } - fprintf(stderr, "=== Testing Primitives\n"); - FIOBJ o = fiobj_null(); - TEST_ASSERT(o == (FIOBJ)FIOBJ_T_NULL, "fiobj_null isn't NULL!\n"); - TEST_ASSERT(FIOBJ_TYPE(0) == FIOBJ_T_NULL, "NULL isn't NULL!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(0, FIOBJ_T_NULL), "NULL isn't NULL! (2)\n"); - TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_null()), - "fiobj_null claims to be allocated!\n"); - TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_true()), - "fiobj_true claims to be allocated!\n"); - TEST_ASSERT(!FIOBJ_IS_ALLOCATED(fiobj_false()), - "fiobj_false claims to be allocated!\n"); - TEST_ASSERT(FIOBJ_TYPE(fiobj_true()) == FIOBJ_T_TRUE, - "fiobj_true isn't FIOBJ_T_TRUE!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_true(), FIOBJ_T_TRUE), - "fiobj_true isn't FIOBJ_T_TRUE! (2)\n"); - TEST_ASSERT(FIOBJ_TYPE(fiobj_false()) == FIOBJ_T_FALSE, - "fiobj_false isn't FIOBJ_T_TRUE!\n"); - TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_false(), FIOBJ_T_FALSE), - "fiobj_false isn't FIOBJ_T_TRUE! (2)\n"); - fiobj_free(o); /* testing for crash*/ - fprintf(stderr, "* passed.\n"); - fprintf(stderr, "=== Testing fioj_each2\n"); - o = fiobj_ary_new2(4); - FIOBJ tmp = fiobj_ary_new(); - fiobj_ary_push(o, tmp); - fiobj_ary_push(o, fiobj_true()); - fiobj_ary_push(o, fiobj_null()); - fiobj_ary_push(o, fiobj_num_new(10)); - fiobj_ary_push(tmp, fiobj_num_new(13)); - fiobj_ary_push(tmp, fiobj_hash_new()); - FIOBJ key = fiobj_str_new("my key", 6); - fiobj_hash_set(fiobj_ary_entry(tmp, -1), key, fiobj_true()); - fiobj_free(key); - /* we have root array + 4 children (w/ array) + 2 children (w/ hash) + 1 */ - uintptr_t count = 0; - size_t each_ret = 0; - TEST_ASSERT(fiobj_each2(o, fiobject_test_task, (void *)&count) == 8, - "fiobj_each1 didn't count everything... (%d != %d)", (int)count, - (int)each_ret); - TEST_ASSERT(count == 8, "Something went wrong with the counter task... (%d)", - (int)count) - fprintf(stderr, "* passed.\n"); - fprintf(stderr, "=== Testing fioj_iseq with nested items\n"); - FIOBJ o2 = fiobj_ary_new2(4); - tmp = fiobj_ary_new(); - fiobj_ary_push(o2, tmp); - fiobj_ary_push(o2, fiobj_true()); - fiobj_ary_push(o2, fiobj_null()); - fiobj_ary_push(o2, fiobj_num_new(10)); - fiobj_ary_push(tmp, fiobj_num_new(13)); - fiobj_ary_push(tmp, fiobj_hash_new()); - key = fiobj_str_new("my key", 6); - fiobj_hash_set(fiobj_ary_entry(tmp, -1), key, fiobj_true()); - fiobj_free(key); - TEST_ASSERT(!fiobj_iseq(o, FIOBJ_INVALID), - "Array and FIOBJ_INVALID can't be equal!"); - TEST_ASSERT(!fiobj_iseq(o, fiobj_null()), - "Array and fiobj_null can't be equal!"); - TEST_ASSERT(fiobj_iseq(o, o2), "Arrays aren't euqal!"); - fiobj_free(o); - fiobj_free(o2); - TEST_ASSERT(fiobj_iseq(fiobj_null(), fiobj_null()), - "fiobj_null() not equal to self!"); - TEST_ASSERT(fiobj_iseq(fiobj_false(), fiobj_false()), - "fiobj_false() not equal to self!"); - TEST_ASSERT(fiobj_iseq(fiobj_true(), fiobj_true()), - "fiobj_true() not equal to self!"); - TEST_ASSERT(!fiobj_iseq(fiobj_null(), fiobj_false()), - "fiobj_null eqal to fiobj_false!"); - TEST_ASSERT(!fiobj_iseq(fiobj_null(), fiobj_true()), - "fiobj_null eqal to fiobj_true!"); - fprintf(stderr, "* passed.\n"); -} - -#endif diff --git a/ext/iodine/fiobject.h b/ext/iodine/fiobject.h deleted file mode 100644 index 52915026..00000000 --- a/ext/iodine/fiobject.h +++ /dev/null @@ -1,654 +0,0 @@ -#ifndef H_FIOBJECT_H -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ - -/** -This facil.io core library provides wrappers around complex and (or) dynamic -types, abstracting some complexity and making dynamic type related tasks easier. -*/ -#define H_FIOBJECT_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS) -#define __attribute__(...) -#define __has_include(...) 0 -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#elif !defined(__clang__) && !defined(__has_builtin) -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -Core Types -***************************************************************************** */ - -typedef enum __attribute__((packed)) { - FIOBJ_T_NUMBER = 0x01, - FIOBJ_T_NULL = 0x06, - FIOBJ_T_TRUE = 0x16, - FIOBJ_T_FALSE = 0x26, - FIOBJ_T_FLOAT, - FIOBJ_T_STRING, - FIOBJ_T_ARRAY, - FIOBJ_T_HASH, - FIOBJ_T_DATA, - FIOBJ_T_UNKNOWN -} fiobj_type_enum; - -typedef uintptr_t FIOBJ; - -/** a Macro retriving an object's type. Use FIOBJ_TYPE_IS(x) for testing. */ -#define FIOBJ_TYPE(obj) fiobj_type((obj)) -#define FIOBJ_TYPE_IS(obj, type) fiobj_type_is((obj), (type)) -#define FIOBJ_IS_NULL(obj) (!obj || obj == (FIOBJ)FIOBJ_T_NULL) -#define FIOBJ_INVALID 0 - -#ifndef FIO_STR_INFO_TYPE -/** A String information type, reports information about a C string. */ -typedef struct fio_str_info_s { - size_t capa; /* Buffer capacity, if the string is writable. */ - size_t len; /* String length. */ - char *data; /* String's first byte. */ -} fio_str_info_s; -#define FIO_STR_INFO_TYPE -#endif - -/* ***************************************************************************** -Primitives -***************************************************************************** */ - -#define FIO_INLINE static inline __attribute__((unused)) - -FIO_INLINE FIOBJ fiobj_null(void) { return (FIOBJ)FIOBJ_T_NULL; } -FIO_INLINE FIOBJ fiobj_true(void) { return (FIOBJ)FIOBJ_T_TRUE; } -FIO_INLINE FIOBJ fiobj_false(void) { return (FIOBJ)FIOBJ_T_FALSE; } - -/* ***************************************************************************** -Generic Object API -***************************************************************************** */ - -/** Returns a C string naming the objects dynamic type. */ -FIO_INLINE const char *fiobj_type_name(const FIOBJ obj); - -/** - * Heuristic copy with a preference for copy reference(!) to minimize - * allocations. - * - * Always returns the value passed along. - */ -FIO_INLINE FIOBJ fiobj_dup(FIOBJ); - -/** - * Frees the object and any of it's "children". - * - * This function affects nested objects, meaning that when an Array or - * a Hash object is passed along, it's children (nested objects) are - * also freed. - */ -FIO_INLINE void fiobj_free(FIOBJ); - -/** - * Tests if an object evaluates as TRUE. - * - * This is object type specific. For example, empty strings might evaluate as - * FALSE, even though they aren't a boolean type. - */ -FIO_INLINE int fiobj_is_true(const FIOBJ); - -/** - * Returns an Object's numerical value. - * - * If a String is passed to the function, it will be parsed assuming base 10 - * numerical data. - * - * Hashes and Arrays return their object count. - * - * IO objects return the length of their data. - * - * A type error results in 0. - */ -FIO_INLINE intptr_t fiobj_obj2num(const FIOBJ obj); - -/** - * Returns a Float's value. - * - * If a String is passed to the function, they will benparsed assuming base 10 - * numerical data. - * - * A type error results in 0. - */ -FIO_INLINE double fiobj_obj2float(const FIOBJ obj); - -/** - * Returns a C String (NUL terminated) using the `fio_str_info_s` data type. - * - * The Sting in binary safe and might contain NUL bytes in the middle as well as - * a terminating NUL. - * - * If a a Number or a Float are passed to the function, they - * will be parsed as a *temporary*, thread-safe, String. - * - * Numbers will be represented in base 10 numerical data. - * - * A type error results in NULL (i.e. object isn't a String). - */ -FIO_INLINE fio_str_info_s fiobj_obj2cstr(const FIOBJ obj); - -/** - * Calculates an Objects's SipHash value for possible use as a HashMap key. - * - * The Object MUST answer to the fiobj_obj2cstr, or the result is unusable. In - * other words, Hash Objects and Arrays can NOT be used for Hash keys. - */ -FIO_INLINE uint64_t fiobj_obj2hash(const FIOBJ o); - -/** - * Single layer iteration using a callback for each nested fio object. - * - * Accepts any `FIOBJ ` type but only collections (Arrays and Hashes) are - * processed. The container itself (the Array or the Hash) is **not** processed - * (unlike `fiobj_each2`). - * - * The callback task function must accept an object and an opaque user pointer. - * - * Hash objects pass along only the value object. The keys can be accessed using - * the `fiobj_hash_key_in_loop` function. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the "stop" position, i.e., the number of items processed + the - * starting point. - */ -FIO_INLINE size_t fiobj_each1(FIOBJ, size_t start_at, - int (*task)(FIOBJ obj, void *arg), void *arg); - -/** - * Deep iteration using a callback for each fio object, including the parent. - * - * Accepts any `FIOBJ ` type. - * - * Collections (Arrays, Hashes) are deeply probed and shouldn't be edited - * during an `fiobj_each2` call (or weird things may happen). - * - * The callback task function must accept an object and an opaque user pointer. - * - * Hash objects keys are available using the `fiobj_hash_key_in_loop` function. - * - * Notice that when passing collections to the function, the collection itself - * is sent to the callback followed by it's children (if any). This is true also - * for nested collections (a nested Hash will be sent first, followed by the - * nested Hash's children and then followed by the rest of it's siblings. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - */ -size_t fiobj_each2(FIOBJ, int (*task)(FIOBJ obj, void *arg), void *arg); - -/** - * Deeply compare two objects. No hashing or recursive function calls are - * involved. - * - * Uses a similar algorithm to `fiobj_each2`, except adjusted to two objects. - * - * Hash objects are order sensitive. To be equal, Hash keys must match in order. - * - * Returns 1 if true and 0 if false. - */ -FIO_INLINE int fiobj_iseq(const FIOBJ obj1, const FIOBJ obj2); - -/* ***************************************************************************** -Object Type Identification -***************************************************************************** */ - -#define FIOBJECT_NUMBER_FLAG 1 - -#if UINTPTR_MAX < 0xFFFFFFFFFFFFFFFF -#define FIOBJECT_PRIMITIVE_FLAG 2 -#define FIOBJECT_STRING_FLAG 0 -#define FIOBJECT_HASH_FLAG 0 -#define FIOBJECT_TYPE_MASK (~(uintptr_t)3) -#else -#define FIOBJECT_PRIMITIVE_FLAG 6 -#define FIOBJECT_STRING_FLAG 2 -#define FIOBJECT_HASH_FLAG 4 -#define FIOBJECT_TYPE_MASK (~(uintptr_t)7) -#endif - -#define FIOBJ_NUMBER_SIGN_MASK ((~((uintptr_t)0)) >> 1) -#define FIOBJ_NUMBER_SIGN_BIT (~FIOBJ_NUMBER_SIGN_MASK) -#define FIOBJ_NUMBER_SIGN_EXCLUDE_BIT (FIOBJ_NUMBER_SIGN_BIT >> 1) - -#define FIOBJ_IS_ALLOCATED(o) \ - ((o) && ((o)&FIOBJECT_NUMBER_FLAG) == 0 && \ - ((o)&FIOBJECT_PRIMITIVE_FLAG) != FIOBJECT_PRIMITIVE_FLAG) -#define FIOBJ2PTR(o) ((void *)((o)&FIOBJECT_TYPE_MASK)) - -FIO_INLINE fiobj_type_enum fiobj_type(FIOBJ o) { - if (!o) - return FIOBJ_T_NULL; - if (o & FIOBJECT_NUMBER_FLAG) - return FIOBJ_T_NUMBER; - if ((o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_PRIMITIVE_FLAG) - return (fiobj_type_enum)o; - if (FIOBJECT_STRING_FLAG && - (o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_STRING_FLAG) - return FIOBJ_T_STRING; - if (FIOBJECT_HASH_FLAG && (o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_HASH_FLAG) - return FIOBJ_T_HASH; - return ((fiobj_type_enum *)FIOBJ2PTR(o))[0]; -} - -/** - * This is faster than getting the type, since the switch statement is - * optimized away (it's calculated during compile time). - */ -FIO_INLINE size_t fiobj_type_is(FIOBJ o, fiobj_type_enum type) { - switch (type) { - case FIOBJ_T_NUMBER: - return (o & FIOBJECT_NUMBER_FLAG) || - ((fiobj_type_enum *)o)[0] == FIOBJ_T_NUMBER; - case FIOBJ_T_NULL: - return !o || o == fiobj_null(); - case FIOBJ_T_TRUE: - return o == fiobj_true(); - case FIOBJ_T_FALSE: - return o == fiobj_false(); - case FIOBJ_T_STRING: - return (FIOBJECT_STRING_FLAG && (o & FIOBJECT_NUMBER_FLAG) == 0 && - (o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_STRING_FLAG) || - (FIOBJECT_STRING_FLAG == 0 && FIOBJ_IS_ALLOCATED(o) && - ((fiobj_type_enum *)FIOBJ2PTR(o))[0] == FIOBJ_T_STRING); - case FIOBJ_T_HASH: - if (FIOBJECT_HASH_FLAG) { - return ((o & FIOBJECT_NUMBER_FLAG) == 0 && - (o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_HASH_FLAG); - } - /* fallthrough */ - case FIOBJ_T_FLOAT: - case FIOBJ_T_ARRAY: - case FIOBJ_T_DATA: - case FIOBJ_T_UNKNOWN: - return FIOBJ_IS_ALLOCATED(o) && - ((fiobj_type_enum *)FIOBJ2PTR(o))[0] == type; - } - return FIOBJ_IS_ALLOCATED(o) && ((fiobj_type_enum *)FIOBJ2PTR(o))[0] == type; -} - -/* ***************************************************************************** -Object Header -***************************************************************************** */ - -typedef struct { - /* a String allowing logging type data. */ - const char *class_name; - /* deallocate root object's memory, perform task for each nested object. */ - void (*const dealloc)(FIOBJ, void (*task)(FIOBJ, void *), void *); - /* return the number of normal nested object */ - uintptr_t (*const count)(const FIOBJ); - /* tests the object for truthfulness. */ - size_t (*const is_true)(const FIOBJ); - /* tests if two objects are equal. */ - size_t (*const is_eq)(const FIOBJ, const FIOBJ); - /* iterates through the normal nested objects (ignore deep nesting) */ - size_t (*const each)(FIOBJ, size_t start_at, int (*task)(FIOBJ, void *), - void *); - /* object value as String */ - fio_str_info_s (*const to_str)(const FIOBJ); - /* object value as Integer */ - intptr_t (*const to_i)(const FIOBJ); - /* object value as Float */ - double (*const to_f)(const FIOBJ); -} fiobj_object_vtable_s; - -typedef struct { - /* must be first */ - fiobj_type_enum type; - /* reference counter */ - uint32_t ref; -} fiobj_object_header_s; - -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_NUMBER; -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_FLOAT; -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_STRING; -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_ARRAY; -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_HASH; -extern const fiobj_object_vtable_s FIOBJECT_VTABLE_DATA; - -#define FIOBJECT2VTBL(o) fiobj_type_vtable(o) -#define FIOBJECT2HEAD(o) (((fiobj_object_header_s *)FIOBJ2PTR((o)))) - -FIO_INLINE const fiobj_object_vtable_s *fiobj_type_vtable(FIOBJ o) { - switch (FIOBJ_TYPE(o)) { - case FIOBJ_T_NUMBER: - return &FIOBJECT_VTABLE_NUMBER; - case FIOBJ_T_FLOAT: - return &FIOBJECT_VTABLE_FLOAT; - case FIOBJ_T_STRING: - return &FIOBJECT_VTABLE_STRING; - case FIOBJ_T_ARRAY: - return &FIOBJECT_VTABLE_ARRAY; - case FIOBJ_T_HASH: - return &FIOBJECT_VTABLE_HASH; - case FIOBJ_T_DATA: - return &FIOBJECT_VTABLE_DATA; - case FIOBJ_T_NULL: - case FIOBJ_T_TRUE: - case FIOBJ_T_FALSE: - case FIOBJ_T_UNKNOWN: - return NULL; - } - return NULL; -} - -/* ***************************************************************************** -Atomic reference counting -***************************************************************************** */ - -/* C11 Atomics are defined? */ -#if defined(__ATOMIC_RELAXED) -/** An atomic addition operation */ -#define fiobj_ref_inc(o) \ - __atomic_add_fetch(&FIOBJECT2HEAD(o)->ref, 1, __ATOMIC_SEQ_CST) -/** An atomic subtraction operation */ -#define fiobj_ref_dec(o) \ - __atomic_sub_fetch(&FIOBJECT2HEAD(o)->ref, 1, __ATOMIC_SEQ_CST) - -/* Select the correct compiler builtin method. */ -#elif defined(__has_builtin) && !FIO_GNUC_BYPASS - -#if __has_builtin(__sync_fetch_and_or) -/** An atomic addition operation */ -#define fiobj_ref_inc(o) __sync_add_and_fetch(&FIOBJECT2HEAD(o)->ref, 1) -/** An atomic subtraction operation */ -#define fiobj_ref_dec(o) __sync_sub_and_fetch(&FIOBJECT2HEAD(o)->ref, 1) - -#else -#error missing required atomic options. -#endif /* defined(__has_builtin) */ - -#elif __GNUC__ > 3 -/** An atomic addition operation */ -#define fiobj_ref_inc(o) __sync_add_and_fetch(&FIOBJECT2HEAD(o)->ref, 1) -/** An atomic subtraction operation */ -#define fiobj_ref_dec(o) __sync_sub_and_fetch(&FIOBJECT2HEAD(o)->ref, 1) - -#else -#error missing required atomic options. -#endif - -#define OBJREF_ADD(o) fiobj_ref_inc(o) -#define OBJREF_REM(o) fiobj_ref_dec(o) - -/* ***************************************************************************** -Inlined Functions -***************************************************************************** */ - -/** Returns a C string naming the objects dynamic type. */ -FIO_INLINE const char *fiobj_type_name(const FIOBJ o) { - if (o & FIOBJECT_NUMBER_FLAG) - return "Number"; - if (FIOBJ_IS_ALLOCATED(o)) - return FIOBJECT2VTBL(o)->class_name; - if (!o) - return "NULL"; - return "Primitive"; -} - -/** used internally to free objects with nested objects. */ -void fiobj_free_complex_object(FIOBJ o); - -/** - * Copy by reference(!) - increases an object's (and any nested object's) - * reference count. - * - * Always returns the value passed along. - */ -FIO_INLINE FIOBJ fiobj_dup(FIOBJ o) { - if (FIOBJ_IS_ALLOCATED(o)) - OBJREF_ADD(o); - return o; -} - -/** - * Decreases an object's reference count, releasing memory and - * resources. - * - * This function affects nested objects, meaning that when an Array or - * a Hash object is passed along, it's children (nested objects) are - * also freed. - * - * Returns the number of existing references or zero if memory was released. - */ -FIO_INLINE void fiobj_free(FIOBJ o) { - if (!FIOBJ_IS_ALLOCATED(o)) - return; - if (fiobj_ref_dec(o)) - return; - if (FIOBJECT2VTBL(o)->each && FIOBJECT2VTBL(o)->count(o)) - fiobj_free_complex_object(o); - else - FIOBJECT2VTBL(o)->dealloc(o, NULL, NULL); -} - -/** - * Tests if an object evaluates as TRUE. - * - * This is object type specific. For example, empty strings might evaluate as - * FALSE, even though they aren't a boolean type. - */ -FIO_INLINE int fiobj_is_true(const FIOBJ o) { - if (o & FIOBJECT_NUMBER_FLAG) - return ((uintptr_t)o >> 1) != 0; - if ((o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_PRIMITIVE_FLAG) - return o == FIOBJ_T_TRUE; - return (int)(FIOBJECT2VTBL(o)->is_true(o)); -} - -/** - * Returns an object's numerical value. - * - * If a String or Symbol are passed to the function, they will be - * parsed assuming base 10 numerical data. - * - * Hashes and Arrays return their object count. - * - * IO and File objects return their underlying file descriptor. - * - * A type error results in 0. - */ -FIO_INLINE intptr_t fiobj_obj2num(const FIOBJ o) { - if (o & FIOBJECT_NUMBER_FLAG) { - const uintptr_t sign = - (o & FIOBJ_NUMBER_SIGN_BIT) - ? (FIOBJ_NUMBER_SIGN_BIT | FIOBJ_NUMBER_SIGN_EXCLUDE_BIT) - : 0; - return (intptr_t)(((o & FIOBJ_NUMBER_SIGN_MASK) >> 1) | sign); - } - if (!o || !FIOBJ_IS_ALLOCATED(o)) - return o == FIOBJ_T_TRUE; - return FIOBJECT2VTBL(o)->to_i(o); -} - -/** Converts a number to a temporary, thread safe, C string object */ -fio_str_info_s fio_ltocstr(long); - -/** Converts a float to a temporary, thread safe, C string object */ -fio_str_info_s fio_ftocstr(double); - -/** - * Returns a C String (NUL terminated) using the `fio_str_info_s` data type. - * - * The String is binary safe and might contain NUL bytes in the middle as well as - * a terminating NUL. - * - * If a a Number or a Float are passed to the function, they - * will be parsed as a *temporary*, thread-safe, String. - * - * Numbers will be represented in base 10 numerical data. - * - * A type error results in NULL (i.e. object isn't a String). - */ -FIO_INLINE fio_str_info_s fiobj_obj2cstr(const FIOBJ o) { - if (!o) { - fio_str_info_s ret = {0, 4, (char *)"null"}; - return ret; - } - if (o & FIOBJECT_NUMBER_FLAG) - return fio_ltocstr(((intptr_t)o) >> 1); - if ((o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_PRIMITIVE_FLAG) { - switch ((fiobj_type_enum)o) { - case FIOBJ_T_NULL: { - fio_str_info_s ret = {0, 4, (char *)"null"}; - return ret; - } - case FIOBJ_T_FALSE: { - fio_str_info_s ret = {0, 5, (char *)"false"}; - return ret; - } - case FIOBJ_T_TRUE: { - fio_str_info_s ret = {0, 4, (char *)"true"}; - return ret; - } - default: - break; - } - } - return FIOBJECT2VTBL(o)->to_str(o); -} - -/* referenced here */ -uint64_t fiobj_str_hash(FIOBJ o); -/** - * Calculates an Objects's SipHash value for possible use as a HashMap key. - * - * The Object MUST answer to the fiobj_obj2cstr, or the result is unusable. In - * other words, Hash Objects and Arrays can NOT be used for Hash keys. - */ -FIO_INLINE uint64_t fiobj_obj2hash(const FIOBJ o) { - if (FIOBJ_TYPE_IS(o, FIOBJ_T_STRING)) - return fiobj_str_hash(o); - if (!FIOBJ_IS_ALLOCATED(o)) - return (uint64_t)o; - fio_str_info_s s = fiobj_obj2cstr(o); - return FIO_HASH_FN(s.data, s.len, (uintptr_t)&fiobj_each2, - (uintptr_t)&fiobj_free_complex_object); -} - -FIO_INLINE uint64_t fiobj_hash_string(const void *data, size_t len) { - return FIO_HASH_FN(data, len, (uintptr_t)&fiobj_each2, - (uintptr_t)&fiobj_free_complex_object); -} - -/** - * Returns a Float's value. - * - * If a String or Symbol are passed to the function, they will be - * parsed assuming base 10 numerical data. - * - * Hashes and Arrays return their object count. - * - * IO and File objects return their underlying file descriptor. - * - * A type error results in 0. - */ -FIO_INLINE double fiobj_obj2float(const FIOBJ o) { - if (o & FIOBJECT_NUMBER_FLAG) - return (double)(fiobj_obj2num(o)); - if (!o || (o & FIOBJECT_PRIMITIVE_FLAG) == FIOBJECT_PRIMITIVE_FLAG) - return (double)(o == FIOBJ_T_TRUE); - return FIOBJECT2VTBL(o)->to_f(o); -} - -/** used internally for complext nested tests (Array / Hash types) */ -int fiobj_iseq____internal_complex__(FIOBJ o, FIOBJ o2); -/** - * Deeply compare two objects. No hashing or recursive function calls are - * involved. - * - * Uses a similar algorithm to `fiobj_each2`, except adjusted to two objects. - * - * Hash order will be tested when comapring Hashes. - * - * KNOWN ISSUES: - * - * * Temporarily broken for collections (Arrays / Hashes). - * - * * Hash order will be tested as well as the Hash content, which means that - * equal Hashes might be considered unequal if their order doesn't match. - * - * Returns 1 if true and 0 if false. - */ -FIO_INLINE int fiobj_iseq(const FIOBJ o, const FIOBJ o2) { - if (o == o2) - return 1; - if (!o || !o2) - return 0; /* they should have compared equal before. */ - if (!FIOBJ_IS_ALLOCATED(o) || !FIOBJ_IS_ALLOCATED(o2)) - return 0; /* they should have compared equal before. */ - if (FIOBJECT2HEAD(o)->type != FIOBJECT2HEAD(o2)->type) - return 0; /* non-type equality is a barriar to equality. */ - if (!FIOBJECT2VTBL(o)->is_eq(o, o2)) - return 0; - if (FIOBJECT2VTBL(o)->each && FIOBJECT2VTBL(o)->count(o)) - return fiobj_iseq____internal_complex__((FIOBJ)o, (FIOBJ)o2); - return 1; -} - -/** - * Single layer iteration using a callback for each nested fio object. - * - * Accepts any `FIOBJ ` type but only collections (Arrays and Hashes) are - * processed. The container itself (the Array or the Hash) is **not** processed - * (unlike `fiobj_each2`). - * - * The callback task function must accept an object and an opaque user pointer. - * - * Hash objects pass along a `FIOBJ_T_COUPLET` object, containing - * references for both the key and the object. Keys shouldn't be altered once - * placed as a key (or the Hash will break). Collections (Arrays / Hashes) can't - * be used as keeys. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the "stop" position, i.e., the number of items processed + the - * starting point. - */ -FIO_INLINE size_t fiobj_each1(FIOBJ o, size_t start_at, - int (*task)(FIOBJ obj, void *arg), void *arg) { - if (FIOBJ_IS_ALLOCATED(o) && FIOBJECT2VTBL(o)->each) - return FIOBJECT2VTBL(o)->each(o, start_at, task, arg); - return 0; -} - -#if DEBUG -void fiobj_test_core(void); -#endif - -#ifdef __cplusplus -} /* closing brace for extern "C" */ -#endif -#endif diff --git a/ext/iodine/hpack.h b/ext/iodine/hpack.h deleted file mode 100644 index d6db7bf4..00000000 --- a/ext/iodine/hpack.h +++ /dev/null @@ -1,1923 +0,0 @@ -#ifndef H_HPACK_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include - -#include - -#ifndef MAYBE_UNUSED -#define MAYBE_UNUSED __attribute__((unused)) -#endif - -/** - * Sets the limit for both a single header value and a packed header group. - * Must be less than 2^16 -1 - */ -#define HPACK_BUFFER_SIZE 16384 - -/** - * Sets the limit for the amount of data an HPACK dynamic table can reference. - * Should be less then 65,535 (2^16 -1 is the type size limit). - */ -#define HPACK_MAX_TABLE_SIZE 65535 - -/* ***************************************************************************** -Required Callbacks -***************************************************************************** */ - -/* ***************************************************************************** -Types -***************************************************************************** */ - -/** The HPACK context. */ -typedef struct hpack_context_s hpack_context_s; - -/* ***************************************************************************** -Context API -***************************************************************************** */ - -/* ***************************************************************************** -Primitive Types API -***************************************************************************** */ - -/** - * Encodes an integer. - * - * Returns the number of bytes written to the destination buffer. If the buffer - * was too small, returns the number of bytes that would have been written. - */ -static inline int hpack_int_pack(void *dest, size_t limit, uint64_t i, - uint8_t prefix); - -/** - * Decodes an integer, updating the `pos` marker to the next unprocessed byte. - * - * The position marker may start as non-zero, meaning that `len - (*pos)` is the - * actual length. - * - * An encoding / decoding error results in a return value of -1. - */ -static inline int64_t hpack_int_unpack(void *data, size_t len, uint8_t prefix, - size_t *pos); - -/** - * Encodes a String. - * - * Returns the number of bytes written to the destination buffer. If the buffer - * was too small, returns the number of bytes that would have been written. - */ -static inline int hpack_string_pack(void *dest, size_t limit, void *data, - size_t len, uint8_t compress); - -/** - * Decodes a String. - * - * Returns the number of bytes written to the destination buffer. If the buffer - * was too small, returns the number of bytes that would have been written. - * - * An encoding / decoding error results in a return value of -1. - * - * The position marker may start as non-zero, meaning that `len - (*pos)` is the - * actual length. - */ -static inline int hpack_string_unpack(void *dest, size_t limit, void *encoded, - size_t len, size_t *pos); - -/* ***************************************************************************** -Static table API -***************************************************************************** */ - -/** - * Sets the provided pointers with the information in the static header table. - * - * The `index` is 1..61 (not zero based). - * - * Set `get_value` to 1 to collect the value data rather then the header name. - * - * Returns -1 if request is out of bounds. - */ -static int hpack_header_static_find(uint8_t index, uint8_t get_value, - const char **name, size_t *len); - -/* ***************************************************************************** -Huffman API (internal) -***************************************************************************** */ - -/* the huffman encoding map */ -typedef const struct { - const uint32_t code; - const uint8_t bits; -} huffman_encode_s; -static const huffman_encode_s huffman_encode_table[257]; - -/* the huffman decoding binary tree type */ -typedef struct { - const int16_t value; // value, -1 == none. - const uint8_t offset[2]; // offset for 0 and one. 0 == leaf node. -} huffman_decode_s; -static const huffman_decode_s huffman_decode_tree[513]; - -/** - * Unpack (de-compress) using HPACK huffman - returns the number of bytes - * written and advances the position marker. - */ -static MAYBE_UNUSED int hpack_huffman_unpack(void *dest, size_t limit, - void *encoded, size_t len, - size_t *pos); - -/** - * Pack (compress) using HPACK huffman - returns the number of bytes written or - * required. - */ -static MAYBE_UNUSED int hpack_huffman_pack(void *dest, const int limit, - void *data, size_t len); - -/* ***************************************************************************** - - - - - - Implementation - - - - - - -***************************************************************************** */ - -/* ***************************************************************************** -Integer encoding -***************************************************************************** */ - -static inline int hpack_int_pack(void *dest_, size_t limit, uint64_t i, - uint8_t prefix) { - uint8_t mask = ((1 << (prefix)) - 1); - uint8_t *dest = (uint8_t *)dest_; - int len = 1; - - if (!dest_ || !limit) - goto calc_final_length; - - if (i < mask) { - // zero out prefix bits - dest[0] &= ~mask; - // fill in i; - dest[0] |= i; - return 1; - } - - dest[0] |= mask; - - if ((size_t)len >= limit) - goto calc_final_length; - - i -= mask; - - while (i > 127) { - dest[len] = 128 | (i & 127); - ++len; - if ((size_t)len >= limit) - goto calc_final_length; - i >>= 7; - } - - dest[len] = i & 0x7fU; - ++len; - - return len; - -calc_final_length: - len = 1; - if (i < mask) - return len; - i -= mask; - while (i) { - ++len; - i >>= 7; - } - return len; -} - -static inline int64_t hpack_int_unpack(void *data_, size_t len, uint8_t prefix, - size_t *pos) { - uint8_t *data = (uint8_t *)data_; - len -= *pos; - if (len > 8) - len = 8; - uint64_t result = 0; - uint64_t bit = 0; - uint8_t mask = ((1 << (prefix)) - 1); - - if ((mask & (data[*pos])) != mask) { - result = (mask & (data[(*pos)++])); - return (int64_t)result; - } - - ++(*pos); - --len; - - while (len && (data[*pos] & 128)) { - result |= ((data[*pos] & 0x7fU) << (bit)); - bit += 7; - ++(*pos); - --len; - } - if (!len) { - return -1; - } - result |= ((data[*pos] & 0x7fU) << bit); - result += mask; - - ++(*pos); - return (int64_t)result; -} - -/* ***************************************************************************** -String encoding -***************************************************************************** */ - -static MAYBE_UNUSED int hpack_string_pack(void *dest_, size_t limit, - void *data_, size_t len, - uint8_t compress) { - uint8_t *dest = (uint8_t *)dest_; - uint8_t *buf = (uint8_t *)data_; - int encoded_int_len = 0; - int pos = 0; - if (compress) { - dest[pos] = 128; - int comp_len = hpack_huffman_pack(NULL, 0, buf, len); - encoded_int_len = hpack_int_pack(dest, limit, comp_len, 7); - if (encoded_int_len + comp_len > (int)limit) - return comp_len + encoded_int_len; - comp_len = hpack_huffman_pack(dest + encoded_int_len, - limit - encoded_int_len, buf, len); - return encoded_int_len + comp_len; - } - dest[pos] = 0; - encoded_int_len = hpack_int_pack(dest, limit, len, 7); - if (encoded_int_len + (int)len > (int)limit) - return len + encoded_int_len; - memcpy(dest + encoded_int_len, buf, len); - return len + encoded_int_len; -} - -static MAYBE_UNUSED int hpack_string_unpack(void *dest_, size_t limit, - void *encoded_, size_t len, - size_t *pos) { - uint8_t *dest = (uint8_t *)dest_; - uint8_t *buf = (uint8_t *)encoded_; - const size_t org_pos = *pos; - uint8_t compressed = buf[*pos] & 128; - int64_t l = hpack_int_unpack(buf, len, 7, pos); - if (!l) { - return 0; - } - if (l == -1 || l > (int64_t)len - (int64_t)*pos) { - return -1; - } - len = l; - if (compressed) { - len = hpack_huffman_unpack(dest, limit, buf, len + (*pos), pos); - if (len > limit) - goto overflow; - } else { - if (len > limit) - goto overflow; - memcpy(dest, buf + (*pos), len); - *pos += len; - } - return len; - -overflow: - *pos = org_pos; - return len; -} - -/* ***************************************************************************** -Huffman encoding -***************************************************************************** */ - -static MAYBE_UNUSED int hpack_huffman_unpack(void *dest_, size_t limit, - void *encoded_, size_t len, - size_t *r_pos) { - uint8_t *dest = (uint8_t *)dest_; - uint8_t *encoded = (uint8_t *)encoded_; - size_t pos = 0; - uint8_t expect = 0; - len -= *r_pos; - register const huffman_decode_s *node = huffman_decode_tree; - while (len) { - register const uint8_t byte = encoded[(*r_pos)++]; - --len; - expect = 1; - for (uint8_t bit = 0; bit < 8; ++bit) { - node += node->offset[(byte >> (7 - bit)) & 1]; - if (node->offset[0]) - continue; - switch (node->value) { - case 256U: - goto done; - case -1: - goto error; - } - if (pos < limit) - dest[pos] = (uint8_t)node->value; - ++pos; - /* test if all remaining bits are set (possible padding) */ - expect = ((uint8_t)(byte | (0xFF << (7 - bit))) & 0xFF) ^ 0xFF; - node = huffman_decode_tree; - } - } -done: - if (expect) { - /* padding error */ - return -1; - } - return pos; -error: - return -1; -} - -static MAYBE_UNUSED int hpack_huffman_pack(void *dest_, const int limit, - void *data_, size_t len) { - uint8_t *dest = (uint8_t *)dest_; - uint8_t *data = (uint8_t *)data_; - int comp_len = 0; - uint8_t *pos = data; - const uint8_t *end = pos + len; - uint8_t offset = 0; - if (!len) - return 0; - if (!limit) - goto calc_final_length; - - dest[comp_len] = 0; - do { - uint32_t code = huffman_encode_table[*pos].code; - uint8_t bits = huffman_encode_table[*pos].bits; - ++pos; - - if (offset) { - /* does the code fit in the existing byte */ - if (bits + offset <= 8) { - dest[comp_len] |= code >> (24 + offset); - offset = offset + bits; - continue; - } - /* fill in current byte */ - dest[comp_len] |= (code >> (24 + offset)) & 0xFF; - code <<= 8 - offset; - bits -= 8 - offset; - offset = 0; - ++comp_len; - dest[comp_len] = 0; - } - - /* make sure we have enough space */ - if (((bits + (comp_len << 3) + 7) >> 3) >= limit) - goto calc_final_length; - - /* copy full bytes */ - switch (bits >> 3) { - case 3: - dest[comp_len + 2] = (uint8_t)(code >> 8) & 0xFF; - /* fallthrough */ - case 2: - dest[comp_len + 1] = (uint8_t)(code >> 16) & 0xFF; - /* fallthrough */ - case 1: - dest[comp_len + 0] = (uint8_t)(code >> 24) & 0xFF; - comp_len += (bits >> 3); - code <<= (bits & (~7)); - dest[comp_len] = 0; - } - - /* copy partial bits */ - dest[comp_len] |= (uint8_t)(code >> 24) & ((uint8_t)0xFF); - offset = bits & 7; - } while (pos < end); - - if (offset & 7) { - /* pad last bits as 1 */ - dest[comp_len] |= (uint8_t)(0xFFUL >> (offset & 7)); - ++comp_len; - } - return comp_len; - -calc_final_length: - - comp_len = 0; - for (size_t i = 0; i < len; i++) { - comp_len += huffman_encode_table[data[i]].bits; - } - comp_len += 7; - comp_len >>= 3; - return comp_len; -} - -/* ***************************************************************************** -Header static table lookup -***************************************************************************** */ - -static const struct { - struct hpack_static_data_s { - const char *val; - const size_t len; - } data[2]; -} MAYBE_UNUSED hpack_static_table[] = { - /* [0] */ {.data = {{.len = 0}, {.len = 0}}}, - {.data = {{.val = ":authority", .len = 10}, {.len = 0}}}, - {.data = {{.val = ":method", .len = 7}, {.val = "GET", .len = 3}}}, - {.data = {{.val = ":method", .len = 7}, {.val = "POST", .len = 4}}}, - {.data = {{.val = ":path", .len = 5}, {.val = "/", .len = 1}}}, - {.data = {{.val = ":path", .len = 5}, {.val = "/index.html", .len = 11}}}, - {.data = {{.val = ":scheme", .len = 7}, {.val = "http", .len = 0}}}, - {.data = {{.val = ":scheme", .len = 7}, {.val = "https", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "200", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "204", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "206", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "304", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "400", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "404", .len = 0}}}, - {.data = {{.val = ":status", .len = 7}, {.val = "500", .len = 0}}}, - {.data = {{.val = "accept-charset", .len = 14}, {.len = 0}}}, - {.data = {{.val = "accept-encoding", .len = 15}, - {.val = "gzip, deflate", .len = 13}}}, - {.data = {{.val = "accept-language", .len = 15}, {.len = 0}}}, - {.data = {{.val = "accept-ranges", .len = 13}, {.len = 0}}}, - {.data = {{.val = "accept", .len = 6}, {.len = 0}}}, - {.data = {{.val = "access-control-allow-origin", .len = 27}, {.len = 0}}}, - {.data = {{.val = "age", .len = 3}, {.len = 0}}}, - {.data = {{.val = "allow", .len = 5}, {.len = 0}}}, - {.data = {{.val = "authorization", .len = 13}, {.len = 0}}}, - {.data = {{.val = "cache-control", .len = 13}, {.len = 0}}}, - {.data = {{.val = "content-disposition", .len = 0}, {.len = 0}}}, - {.data = {{.val = "content-encoding", .len = 16}, {.len = 0}}}, - {.data = {{.val = "content-language", .len = 16}, {.len = 0}}}, - {.data = {{.val = "content-length", .len = 14}, {.len = 0}}}, - {.data = {{.val = "content-location", .len = 16}, {.len = 0}}}, - {.data = {{.val = "content-range", .len = 13}, {.len = 0}}}, - {.data = {{.val = "content-type", .len = 12}, {.len = 0}}}, - {.data = {{.val = "cookie", .len = 6}, {.len = 0}}}, - {.data = {{.val = "date", .len = 4}, {.len = 0}}}, - {.data = {{.val = "etag", .len = 4}, {.len = 0}}}, - {.data = {{.val = "expect", .len = 6}, {.len = 0}}}, - {.data = {{.val = "expires", .len = 7}, {.len = 0}}}, - {.data = {{.val = "from", .len = 4}, {.len = 0}}}, - {.data = {{.val = "host", .len = 4}, {.len = 0}}}, - {.data = {{.val = "if-match", .len = 8}, {.len = 0}}}, - {.data = {{.val = "if-modified-since", .len = 17}, {.len = 0}}}, - {.data = {{.val = "if-none-match", .len = 13}, {.len = 0}}}, - {.data = {{.val = "if-range", .len = 8}, {.len = 0}}}, - {.data = {{.val = "if-unmodified-since", .len = 19}, {.len = 0}}}, - {.data = {{.val = "last-modified", .len = 13}, {.len = 0}}}, - {.data = {{.val = "link", .len = 4}, {.len = 0}}}, - {.data = {{.val = "location", .len = 8}, {.len = 0}}}, - {.data = {{.val = "max-forwards", .len = 12}, {.len = 0}}}, - {.data = {{.val = "proxy-authenticate", .len = 18}, {.len = 0}}}, - {.data = {{.val = "proxy-authorization", .len = 19}, {.len = 0}}}, - {.data = {{.val = "range", .len = 5}, {.len = 0}}}, - {.data = {{.val = "referer", .len = 7}, {.len = 0}}}, - {.data = {{.val = "refresh", .len = 7}, {.len = 0}}}, - {.data = {{.val = "retry-after", .len = 11}, {.len = 0}}}, - {.data = {{.val = "server", .len = 6}, {.len = 0}}}, - {.data = {{.val = "set-cookie", .len = 10}, {.len = 0}}}, - {.data = {{.val = "strict-transport-security", .len = 25}, {.len = 0}}}, - {.data = {{.val = "transfer-encoding", .len = 17}, {.len = 0}}}, - {.data = {{.val = "user-agent", .len = 10}, {.len = 0}}}, - {.data = {{.val = "vary", .len = 4}, {.len = 0}}}, - {.data = {{.val = "via", .len = 3}, {.len = 0}}}, - {.data = {{.val = "www-authenticate", .len = 16}, {.len = 0}}}, -}; - -static MAYBE_UNUSED int hpack_header_static_find(uint8_t index, - uint8_t requested_type, - const char **name, - size_t *len) { - if (requested_type > 1 || - index >= (sizeof(hpack_static_table) / sizeof(hpack_static_table[0]))) - goto err; - struct hpack_static_data_s d = hpack_static_table[index].data[requested_type]; - *name = d.val; - *len = d.len; - return 0; -err: - - *name = NULL; - *len = 0; - return -1; -} - -/* ***************************************************************************** - - - - - - - Testing - - - - - - - -***************************************************************************** */ - -#if DEBUG - -#include -#include - -void hpack_test(void) { - uint8_t buffer[1 << 15]; - const size_t limit = (1 << 15); - size_t buf_pos = 0; - { - /* test integer packing */ - int64_t result; - size_t pos = 0; - fprintf(stderr, "* HPACK testing integer primitive packing.\n"); - if ((result = hpack_int_unpack((uint8_t *)"\x0c", 1, 4, &pos)) != 12) { - fprintf(stderr, - "* HPACK INTEGER DECODER ERROR ex. 0c 12 != %" PRId64 "\n", - result); - exit(-1); - } - - pos = 0; - if ((result = hpack_int_unpack((uint8_t *)"\x1f\x9a\x0a", 3, 5, &pos)) != - 1337) { - fprintf( - stderr, - "* HPACK INTEGER DECODER ERROR ex. \\x1f\\x9a\\x0a 1337 != %" PRId64 - "\n", - result); - exit(-1); - } - - for (size_t i = 0; i < (1 << 21); ++i) { - buf_pos = 0; - int pack_bytes = - hpack_int_pack(buffer + buf_pos, limit - buf_pos, i, i & 7); - if (pack_bytes == -1) { - fprintf(stderr, - "* HPACK INTEGER ENCODE ERROR 1 ( %zu) (prefix == %zu)\n", i, - i & 7); - exit(-1); - } - buf_pos += pack_bytes; - pack_bytes = - hpack_int_pack(buffer + buf_pos, limit - buf_pos, (i << 4), i & 7); - if (pack_bytes == -1) { - fprintf(stderr, - "* HPACK INTEGER ENCODE ERROR 1 ( %zu) (prefix == %zu)\n", i, - i & 7); - exit(-1); - } - buf_pos = 0; - result = hpack_int_unpack(buffer, limit, (i & 7), &buf_pos); - if ((size_t)result != i) { - fprintf(stderr, - "* HPACK INTEGER DECODE ERROR 2 expected %zu got %" PRId64 - " (prefix == %zu)\n", - i, result, (i & 7)); - exit(-1); - } - result = hpack_int_unpack(buffer, limit, (i & 7), &buf_pos); - if ((size_t)result != (i << 4)) { - fprintf(stderr, - "* HPACK INTEGER DECODE ERROR 2 expected %zu got %" PRId64 - " (prefix == %zu)\n", - (i << 4), result, (i & 7)); - exit(-1); - } - } - fprintf(stderr, "* HPACK integer primitive test complete.\n"); - } - buf_pos = 0; - { - /* validate huffman tree */ - for (int i = 0; i < 257; ++i) { - const huffman_decode_s *node = huffman_decode_tree; - uint32_t code = huffman_encode_table[i].code; - uint8_t consumed = 32 - huffman_encode_table[i].bits; - while (consumed < 32) { - node += node->offset[(code >> 31) & 1]; - code <<= 1; - ++consumed; - } - if (i != node->value) { - fprintf(stderr, - "ERROR validating huffman tree - validation error for %d " - "(value: %d != " - "%d)\n", - i, node->value, i); - exit(-1); - } - } - fprintf(stderr, "* HPACK Huffman tree validated.\n"); - /* test huffman encoding / decoding packing */ - const size_t results_limit = 1024; - uint8_t results[1024]; - size_t pos = 0; - memset(results, 0, results_limit); - int tmp = hpack_huffman_unpack( - results, results_limit, - "\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97\xc8\xe9\xae\x82" - "\xae\x43\xd3", - 17, &pos); - if (tmp == -1) { - fprintf(stderr, "* HPACK HUFFMAN TEST FAILED unpacking error (1).\n"); - exit(-1); - } else if ((size_t)tmp > (limit - buf_pos)) { - fprintf(stderr, "* HPACK HUFFMAN TEST buffer full error (1).\n"); - } else if (memcmp(results, "https://www.example.com", 23) || tmp != 23) { - fprintf(stderr, - "* HPACK HUFFMAN TEST FAILED result error (1).\n(%d) %.*s\n", tmp, - tmp, results); - exit(-1); - } - memset(results, 0, results_limit); - pos = 0; - tmp = hpack_huffman_unpack( - results, results_limit, - "\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff", 12, &pos); - if (tmp == -1) { - fprintf(stderr, "* HPACK HUFFMAN TEST FAILED unpacking error (2).\n"); - exit(-1); - } else if ((size_t)tmp > results_limit) { - fprintf(stderr, "* HPACK HUFFMAN TEST buffer full error (2).\n"); - } else if (memcmp(results, "www.example.com", 15) || tmp != 15) { - fprintf(stderr, "* HPACK HUFFMAN TEST FAILED result error (2).\n"); - exit(-1); - } - - memset(results, 0, results_limit); - tmp = hpack_huffman_pack(results, results_limit, "https://www.example.com", - 23); - if (tmp == -1) { - fprintf(stderr, "* HPACK HUFFMAN TEST FAILED packing error!.\n"); - exit(-1); - } else if ((size_t)tmp > limit - buf_pos) { - fprintf(stderr, "* HPACK HUFFMAN TEST packing buffer full!\n"); - } else if (tmp != 17 || memcmp("\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97" - "\xc8\xe9\xae\x82\xae\x43\xd3", - results, 17)) { - fprintf(stderr, - "* HPACK HUFFMAN TEST FAILED packing result error!\n(%d) ", tmp); - for (int i = 0; i < tmp; ++i) { - fprintf(stderr, "\\x%.2X", results[i]); - } - fprintf(stderr, "\n"); - exit(-1); - } - memset(results, 0, results_limit); - memset(buffer, 0, 128); - tmp = hpack_huffman_pack( - buffer, limit, - "I want to go home... but I have to write tests... woohoo!", 57); - if (tmp == -1) { - fprintf(stderr, "* HPACK HUFFMAN TEST FAILED packing error (3).\n"); - exit(-1); - } else if ((size_t)tmp > limit) { - fprintf(stderr, "* HPACK HUFFMAN TEST buffer full (3).\n"); - } else { - int old_tmp = tmp; - pos = 0; - tmp = hpack_huffman_unpack(results, results_limit, buffer, tmp, &pos); - if (tmp == -1) { - fprintf( - stderr, - "* HPACK HUFFMAN TEST FAILED unpacking error (3) for %d bytes.\n" - "* Got (%d): %.*s\n", - old_tmp, tmp, (int)tmp, results); - exit(-1); - } else if (memcmp(results, - "I want to go home... but I have to write tests... " - "woohoo!", - 57) || - tmp != 57) { - fprintf(stderr, - "* HPACK HUFFMAN TEST FAILED result error (3).\n* Got " - "(%u): %.*s\n", - tmp, (int)tmp, results); - exit(-1); - } - } - fprintf(stderr, "* HPACK Huffman compression test finished.\n"); - } - buf_pos = 0; - memset(buffer, 0, 128); - if (1) { - /* test string packing */ - size_t pos = 0; - int tmp = hpack_string_unpack( - buffer, limit, "\x0a\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79", 11, - &pos); - if (pos != 11) { - fprintf(stderr, - "* HPACK STRING UNPACKING FAILED(!) wrong reading position %zu " - "!= 11\n", - pos); - exit(-1); - } - if (tmp == -1) { - fprintf(stderr, "* HPACK STRING UNPACKING FAILED(!) for example.\n"); - exit(-1); - } else { - if (tmp != 10) - fprintf(stderr, - "* HPACK STRING UNPACKING ERROR example len %d != 10.\n", tmp); - if (memcmp(buffer, "\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79", 10)) - fprintf(stderr, - "* HPACK STRING UNPACKING ERROR example returned: %.*s\n", - (int)tmp, buffer); - } - - pos = 0; - memset(buffer, 0, 128); - tmp = hpack_string_unpack( - buffer, limit, "\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff", - 13, &pos); - if (tmp == -1) { - fprintf(stderr, - "* HPACK STRING UNPACKING FAILED(!) for compressed example. %s\n", - buffer); - exit(-1); - } else { - if (tmp != 15) { - fprintf( - stderr, - "* HPACK STRING UNPACKING ERROR compressed example len %d != 15.\n", - tmp); - exit(-1); - } - if (memcmp(buffer, "www.example.com", 10)) { - fprintf(stderr, - "* HPACK STRING UNPACKING ERROR compressed example returned: " - "%.*s\n", - tmp, buffer); - exit(-1); - } - if (pos != 13) { - fprintf(stderr, - "* HPACK STRING UNPACKING FAILED(!) wrong reading position %zu " - "!= 13\n", - pos); - exit(-1); - } - } - - if (1) { - char *str1 = "This is a string to be packed, either compressed or not."; - buf_pos = 0; - size_t i = 0; - const size_t repeats = 1024; - for (i = 0; i < repeats; i++) { - tmp = hpack_string_pack(buffer + buf_pos, limit - buf_pos, str1, 56, - (i & 1) == 1); - if (tmp == -1) - fprintf(stderr, "* HPACK STRING PACKING FAIL AT %zu\n", i); - else if ((size_t)tmp > limit - buf_pos) - break; - buf_pos += tmp; - } - int count = i; - buf_pos = 0; - while (i) { - char result[56]; - memset(result, 0, 56); - --i; - tmp = hpack_string_unpack(result, 56, buffer, limit, &buf_pos); - if (tmp == -1) { - fprintf(stderr, "* HPACK STRING UNPACKING FAIL AT %zu\n", - (repeats - 1) - i); - exit(-1); - } else if (tmp != 56) { - fprintf(stderr, - "* HPACK STRING UNPACKING ERROR AT %zu - got string " - "length %u instead of 56: %.*s\n", - (repeats - 1) - i, tmp, 56, result); - exit(-1); - } - if (memcmp(str1, result, 56)) { - fprintf(stderr, - "* HPACK STRING UNPACKING ERROR AT %zu. Got (%u) %.*s\n", - (repeats - 1) - i, tmp, tmp, result); - exit(-1); - } - } - fprintf(stderr, - "* HPACK string primitive test complete (buffer used %d/%zu " - "strings)\n", - count, repeats); - } - } -} -#else - -#define hpack_test() - -#endif /* DEBUG */ - -/* ***************************************************************************** - - - - - - - Auto-generate binary tree from table data - - - - - - -***************************************************************************** */ - -#if HPACK_BUILD_HPACK_STRUCT - -/* -This section prints out the C code required to create a static, Array based, -binary tree with the following type / fields: -*/ - -#include - -typedef struct { - uint32_t code; - uint8_t bits; - int16_t value; -} huffman_code_s; - -/* the huffman decoding binary tree type */ -typedef struct { - int16_t value; // value, -1 == none. - uint8_t offset[2]; // offset for 0 and one. 0 == leaf node. -} huffman_decode_nc_s; - -/** used to print the binary reverse testing */ -static MAYBE_UNUSED void huffman__print_bin_num(uint32_t num, uint8_t bits) { - fprintf(stderr, "0b"); - if (((32 - bits) & 31)) - num <<= ((32 - bits) & 31); - for (size_t i = 0; i < bits; i++) { - if (num & (1 << (31 - i))) - fprintf(stderr, "1"); - else - fprintf(stderr, "0"); - } -} - -static void huffman__print_unit(huffman_decode_nc_s d, size_t index, - size_t code, size_t bits) { - if (d.value != -1) { - fprintf(stderr, - " {.value = %d, .offset = {%zu, %zu}}, // [%zu]:", (int)d.value, - (size_t)d.offset[0], (size_t)d.offset[1], index); - huffman__print_bin_num(code, bits); - fprintf(stderr, "\n"); - } else { - fprintf(stderr, " {.value = %d, .offset = {%zu, %zu}}, // [%zu]\n", - (int)d.value, (size_t)d.offset[0], (size_t)d.offset[1], index); - } -} - -#define HUFFMAN_TREE_BUFFER (1 << 12) - -void huffman__print_tree(void) { - /* The Huffman Encoding table was copied from - * http://httpwg.org/specs/rfc7541.html#huffman.code - */ - const huffman_encode_s encode_table[] = { - /* 257 elements, 0..256 all sym + EOS */ - {0x1ff8U, 13}, {0x7fffd8U, 23}, {0xfffffe2U, 28}, {0xfffffe3U, 28}, - {0xfffffe4U, 28}, {0xfffffe5U, 28}, {0xfffffe6U, 28}, {0xfffffe7U, 28}, - {0xfffffe8U, 28}, {0xffffeaU, 24}, {0x3ffffffcU, 30}, {0xfffffe9U, 28}, - {0xfffffeaU, 28}, {0x3ffffffdU, 30}, {0xfffffebU, 28}, {0xfffffecU, 28}, - {0xfffffedU, 28}, {0xfffffeeU, 28}, {0xfffffefU, 28}, {0xffffff0U, 28}, - {0xffffff1U, 28}, {0xffffff2U, 28}, {0x3ffffffeU, 30}, {0xffffff3U, 28}, - {0xffffff4U, 28}, {0xffffff5U, 28}, {0xffffff6U, 28}, {0xffffff7U, 28}, - {0xffffff8U, 28}, {0xffffff9U, 28}, {0xffffffaU, 28}, {0xffffffbU, 28}, - {0x14U, 6}, {0x3f8U, 10}, {0x3f9U, 10}, {0xffaU, 12}, - {0x1ff9U, 13}, {0x15U, 6}, {0xf8U, 8}, {0x7faU, 11}, - {0x3faU, 10}, {0x3fbU, 10}, {0xf9U, 8}, {0x7fbU, 11}, - {0xfaU, 8}, {0x16U, 6}, {0x17U, 6}, {0x18U, 6}, - {0x0U, 5}, {0x1U, 5}, {0x2U, 5}, {0x19U, 6}, - {0x1aU, 6}, {0x1bU, 6}, {0x1cU, 6}, {0x1dU, 6}, - {0x1eU, 6}, {0x1fU, 6}, {0x5cU, 7}, {0xfbU, 8}, - {0x7ffcU, 15}, {0x20U, 6}, {0xffbU, 12}, {0x3fcU, 10}, - {0x1ffaU, 13}, {0x21U, 6}, {0x5dU, 7}, {0x5eU, 7}, - {0x5fU, 7}, {0x60U, 7}, {0x61U, 7}, {0x62U, 7}, - {0x63U, 7}, {0x64U, 7}, {0x65U, 7}, {0x66U, 7}, - {0x67U, 7}, {0x68U, 7}, {0x69U, 7}, {0x6aU, 7}, - {0x6bU, 7}, {0x6cU, 7}, {0x6dU, 7}, {0x6eU, 7}, - {0x6fU, 7}, {0x70U, 7}, {0x71U, 7}, {0x72U, 7}, - {0xfcU, 8}, {0x73U, 7}, {0xfdU, 8}, {0x1ffbU, 13}, - {0x7fff0U, 19}, {0x1ffcU, 13}, {0x3ffcU, 14}, {0x22U, 6}, - {0x7ffdU, 15}, {0x3U, 5}, {0x23U, 6}, {0x4U, 5}, - {0x24U, 6}, {0x5U, 5}, {0x25U, 6}, {0x26U, 6}, - {0x27U, 6}, {0x6U, 5}, {0x74U, 7}, {0x75U, 7}, - {0x28U, 6}, {0x29U, 6}, {0x2aU, 6}, {0x7U, 5}, - {0x2bU, 6}, {0x76U, 7}, {0x2cU, 6}, {0x8U, 5}, - {0x9U, 5}, {0x2dU, 6}, {0x77U, 7}, {0x78U, 7}, - {0x79U, 7}, {0x7aU, 7}, {0x7bU, 7}, {0x7ffeU, 15}, - {0x7fcU, 11}, {0x3ffdU, 14}, {0x1ffdU, 13}, {0xffffffcU, 28}, - {0xfffe6U, 20}, {0x3fffd2U, 22}, {0xfffe7U, 20}, {0xfffe8U, 20}, - {0x3fffd3U, 22}, {0x3fffd4U, 22}, {0x3fffd5U, 22}, {0x7fffd9U, 23}, - {0x3fffd6U, 22}, {0x7fffdaU, 23}, {0x7fffdbU, 23}, {0x7fffdcU, 23}, - {0x7fffddU, 23}, {0x7fffdeU, 23}, {0xffffebU, 24}, {0x7fffdfU, 23}, - {0xffffecU, 24}, {0xffffedU, 24}, {0x3fffd7U, 22}, {0x7fffe0U, 23}, - {0xffffeeU, 24}, {0x7fffe1U, 23}, {0x7fffe2U, 23}, {0x7fffe3U, 23}, - {0x7fffe4U, 23}, {0x1fffdcU, 21}, {0x3fffd8U, 22}, {0x7fffe5U, 23}, - {0x3fffd9U, 22}, {0x7fffe6U, 23}, {0x7fffe7U, 23}, {0xffffefU, 24}, - {0x3fffdaU, 22}, {0x1fffddU, 21}, {0xfffe9U, 20}, {0x3fffdbU, 22}, - {0x3fffdcU, 22}, {0x7fffe8U, 23}, {0x7fffe9U, 23}, {0x1fffdeU, 21}, - {0x7fffeaU, 23}, {0x3fffddU, 22}, {0x3fffdeU, 22}, {0xfffff0U, 24}, - {0x1fffdfU, 21}, {0x3fffdfU, 22}, {0x7fffebU, 23}, {0x7fffecU, 23}, - {0x1fffe0U, 21}, {0x1fffe1U, 21}, {0x3fffe0U, 22}, {0x1fffe2U, 21}, - {0x7fffedU, 23}, {0x3fffe1U, 22}, {0x7fffeeU, 23}, {0x7fffefU, 23}, - {0xfffeaU, 20}, {0x3fffe2U, 22}, {0x3fffe3U, 22}, {0x3fffe4U, 22}, - {0x7ffff0U, 23}, {0x3fffe5U, 22}, {0x3fffe6U, 22}, {0x7ffff1U, 23}, - {0x3ffffe0U, 26}, {0x3ffffe1U, 26}, {0xfffebU, 20}, {0x7fff1U, 19}, - {0x3fffe7U, 22}, {0x7ffff2U, 23}, {0x3fffe8U, 22}, {0x1ffffecU, 25}, - {0x3ffffe2U, 26}, {0x3ffffe3U, 26}, {0x3ffffe4U, 26}, {0x7ffffdeU, 27}, - {0x7ffffdfU, 27}, {0x3ffffe5U, 26}, {0xfffff1U, 24}, {0x1ffffedU, 25}, - {0x7fff2U, 19}, {0x1fffe3U, 21}, {0x3ffffe6U, 26}, {0x7ffffe0U, 27}, - {0x7ffffe1U, 27}, {0x3ffffe7U, 26}, {0x7ffffe2U, 27}, {0xfffff2U, 24}, - {0x1fffe4U, 21}, {0x1fffe5U, 21}, {0x3ffffe8U, 26}, {0x3ffffe9U, 26}, - {0xffffffdU, 28}, {0x7ffffe3U, 27}, {0x7ffffe4U, 27}, {0x7ffffe5U, 27}, - {0xfffecU, 20}, {0xfffff3U, 24}, {0xfffedU, 20}, {0x1fffe6U, 21}, - {0x3fffe9U, 22}, {0x1fffe7U, 21}, {0x1fffe8U, 21}, {0x7ffff3U, 23}, - {0x3fffeaU, 22}, {0x3fffebU, 22}, {0x1ffffeeU, 25}, {0x1ffffefU, 25}, - {0xfffff4U, 24}, {0xfffff5U, 24}, {0x3ffffeaU, 26}, {0x7ffff4U, 23}, - {0x3ffffebU, 26}, {0x7ffffe6U, 27}, {0x3ffffecU, 26}, {0x3ffffedU, 26}, - {0x7ffffe7U, 27}, {0x7ffffe8U, 27}, {0x7ffffe9U, 27}, {0x7ffffeaU, 27}, - {0x7ffffebU, 27}, {0xffffffeU, 28}, {0x7ffffecU, 27}, {0x7ffffedU, 27}, - {0x7ffffeeU, 27}, {0x7ffffefU, 27}, {0x7fffff0U, 27}, {0x3ffffeeU, 26}, - {0x3fffffffU, 30}, - }; - /* copy code list */ - huffman_code_s ordered[257]; - for (uint16_t i = 0; i < 257; ++i) { - ordered[i] = (huffman_code_s){ - .value = i, - .bits = encode_table[i].bits, - .code = encode_table[i].code, - }; - } - /* order list by code's bit order (0100 > 0011), use a bunch of CPU... */ - { - uint16_t i = 0; - while (i < 256) { - if (ordered[i].code > ordered[i + 1].code) { - huffman_code_s tmp = ordered[i + 1]; - ++i; - do { - ordered[i] = ordered[i - 1]; - } while (--i && ordered[i - 1].code > tmp.code); - ordered[i] = tmp; - } - ++i; - } - } - /* build tree */ - huffman_decode_nc_s tree[HUFFMAN_TREE_BUFFER]; - size_t tree_len = 0; - for (int i = 0; i < HUFFMAN_TREE_BUFFER; ++i) { - tree[i] = (huffman_decode_nc_s){.value = -1, - .offset = {(uint8_t)-1, (uint8_t)-1}}; - } - { - size_t max_offset = 0; - size_t next = 1; - for (int i = 0; i < 257; ++i) { - /* for each code point, map a tree path */ - size_t pos = 0; - uint32_t code = ordered[i].code; - for (int b = 0; b < ordered[i].bits; ++b) { - if (code & (1ULL << (ordered[i].bits - 1))) { - /* map 1 branch */ - if (tree[pos].offset[1] != (uint8_t)-1) - pos += tree[pos].offset[1]; - else { - if (next - pos > max_offset) - max_offset = next - pos; - tree[pos].offset[1] = next - pos; - pos = next; - ++next; - } - } else { - /* map 0 branch */ - if (tree[pos].offset[0] != (uint8_t)-1) - pos += tree[pos].offset[0]; - else { - if (next - pos > max_offset) - max_offset = next - pos; - tree[pos].offset[0] = next - pos; - pos = next; - ++next; - } - } - code <<= 1; - } - tree[pos] = (huffman_decode_nc_s){.value = ordered[i].value}; - } - fprintf(stderr, "Total tree length = %zu, max offset = %zu\n", next, - max_offset); - tree_len = next; - } - { - /* Validate tree */ - for (int i = 0; i < 257; ++i) { - huffman_decode_nc_s *node = tree; - uint32_t code = ordered[i].code; - uint8_t consumed = 32 - ordered[i].bits; - code <<= consumed; - while (consumed < 32) { - node += node->offset[(code >> 31) & 1]; - code <<= 1; - ++consumed; - } - if (ordered[i].value != node->value) { - fprintf(stderr, - "ERROR building tree - validation error for %d (value: %d != " - "%d)\n", - i, node->value, ordered[i].value); - exit(-1); - } - } - } - fprintf(stderr, - "***** Copy after this line ****\n\n" - "/** Static Huffman encoding map, left aligned */\n" - - "static const huffman_encode_s huffman_encode_table[257] = {\n"); - for (size_t i = 0; i < 257; ++i) { - /* print huffman code left align */ - fprintf(stderr, " {.code = 0x%.08X, .bits = %u}, // [%zu] \n", - (encode_table[i].code << (32 - encode_table[i].bits)), - encode_table[i].bits, i); - } - fprintf(stderr, - "};\n\n/** Static Huffman decoding tree, flattened as an array */\n" - - "static const huffman_decode_s huffman_decode_tree[%zu] = {\n", - tree_len); - for (size_t i = 0; i < tree_len; ++i) { - huffman__print_unit( - tree[i], i, - (tree[i].value == -1) ? 0 : encode_table[tree[i].value].code, - (tree[i].value == -1) ? 0 : encode_table[tree[i].value].bits); - } - fprintf(stderr, "};\n\n\n**************( stop copying )**************\n\n"); - for (int i = 0; i < 256; ++i) { - uint8_t data[4] = {0}; - uint8_t result = 0; - size_t r_pos = 0; - uint32_t code = ordered[i].code; - code <<= 32 - ordered[i].bits; - code |= (1UL << (32 - ordered[i].bits)) - 1; - data[0] = (code >> 24) & 0xFF; - data[1] = (code >> 16) & 0xFF; - data[2] = (code >> 8) & 0xFF; - data[3] = (code >> 0) & 0xFF; - hpack_huffman_unpack(&result, 1, &data, 4, &r_pos); - r_pos = 0; - if (result != ordered[i].value) { - fprintf(stderr, "ERR: (%u) %u != %u (%d, %d)\n", data[0], result, - ordered[i].value, - hpack_huffman_unpack(&result, 1, &data, 1, &r_pos), i); - exit(-1); - } - } - hpack_test(); -} - -int main(void) { - huffman__print_tree(); - return 0; -} - -#endif - -/* ***************************************************************************** - - - - - - - Paste auto-generated data here - - - - - - - -***************************************************************************** */ - -/** Static Huffman encoding map, left aligned */ -static const huffman_encode_s huffman_encode_table[257] = { - {.code = 0xFFC00000, .bits = 13}, // [0] - {.code = 0xFFFFB000, .bits = 23}, // [1] - {.code = 0xFFFFFE20, .bits = 28}, // [2] - {.code = 0xFFFFFE30, .bits = 28}, // [3] - {.code = 0xFFFFFE40, .bits = 28}, // [4] - {.code = 0xFFFFFE50, .bits = 28}, // [5] - {.code = 0xFFFFFE60, .bits = 28}, // [6] - {.code = 0xFFFFFE70, .bits = 28}, // [7] - {.code = 0xFFFFFE80, .bits = 28}, // [8] - {.code = 0xFFFFEA00, .bits = 24}, // [9] - {.code = 0xFFFFFFF0, .bits = 30}, // [10] - {.code = 0xFFFFFE90, .bits = 28}, // [11] - {.code = 0xFFFFFEA0, .bits = 28}, // [12] - {.code = 0xFFFFFFF4, .bits = 30}, // [13] - {.code = 0xFFFFFEB0, .bits = 28}, // [14] - {.code = 0xFFFFFEC0, .bits = 28}, // [15] - {.code = 0xFFFFFED0, .bits = 28}, // [16] - {.code = 0xFFFFFEE0, .bits = 28}, // [17] - {.code = 0xFFFFFEF0, .bits = 28}, // [18] - {.code = 0xFFFFFF00, .bits = 28}, // [19] - {.code = 0xFFFFFF10, .bits = 28}, // [20] - {.code = 0xFFFFFF20, .bits = 28}, // [21] - {.code = 0xFFFFFFF8, .bits = 30}, // [22] - {.code = 0xFFFFFF30, .bits = 28}, // [23] - {.code = 0xFFFFFF40, .bits = 28}, // [24] - {.code = 0xFFFFFF50, .bits = 28}, // [25] - {.code = 0xFFFFFF60, .bits = 28}, // [26] - {.code = 0xFFFFFF70, .bits = 28}, // [27] - {.code = 0xFFFFFF80, .bits = 28}, // [28] - {.code = 0xFFFFFF90, .bits = 28}, // [29] - {.code = 0xFFFFFFA0, .bits = 28}, // [30] - {.code = 0xFFFFFFB0, .bits = 28}, // [31] - {.code = 0x50000000, .bits = 6}, // [32] - {.code = 0xFE000000, .bits = 10}, // [33] - {.code = 0xFE400000, .bits = 10}, // [34] - {.code = 0xFFA00000, .bits = 12}, // [35] - {.code = 0xFFC80000, .bits = 13}, // [36] - {.code = 0x54000000, .bits = 6}, // [37] - {.code = 0xF8000000, .bits = 8}, // [38] - {.code = 0xFF400000, .bits = 11}, // [39] - {.code = 0xFE800000, .bits = 10}, // [40] - {.code = 0xFEC00000, .bits = 10}, // [41] - {.code = 0xF9000000, .bits = 8}, // [42] - {.code = 0xFF600000, .bits = 11}, // [43] - {.code = 0xFA000000, .bits = 8}, // [44] - {.code = 0x58000000, .bits = 6}, // [45] - {.code = 0x5C000000, .bits = 6}, // [46] - {.code = 0x60000000, .bits = 6}, // [47] - {.code = 0x00000000, .bits = 5}, // [48] - {.code = 0x08000000, .bits = 5}, // [49] - {.code = 0x10000000, .bits = 5}, // [50] - {.code = 0x64000000, .bits = 6}, // [51] - {.code = 0x68000000, .bits = 6}, // [52] - {.code = 0x6C000000, .bits = 6}, // [53] - {.code = 0x70000000, .bits = 6}, // [54] - {.code = 0x74000000, .bits = 6}, // [55] - {.code = 0x78000000, .bits = 6}, // [56] - {.code = 0x7C000000, .bits = 6}, // [57] - {.code = 0xB8000000, .bits = 7}, // [58] - {.code = 0xFB000000, .bits = 8}, // [59] - {.code = 0xFFF80000, .bits = 15}, // [60] - {.code = 0x80000000, .bits = 6}, // [61] - {.code = 0xFFB00000, .bits = 12}, // [62] - {.code = 0xFF000000, .bits = 10}, // [63] - {.code = 0xFFD00000, .bits = 13}, // [64] - {.code = 0x84000000, .bits = 6}, // [65] - {.code = 0xBA000000, .bits = 7}, // [66] - {.code = 0xBC000000, .bits = 7}, // [67] - {.code = 0xBE000000, .bits = 7}, // [68] - {.code = 0xC0000000, .bits = 7}, // [69] - {.code = 0xC2000000, .bits = 7}, // [70] - {.code = 0xC4000000, .bits = 7}, // [71] - {.code = 0xC6000000, .bits = 7}, // [72] - {.code = 0xC8000000, .bits = 7}, // [73] - {.code = 0xCA000000, .bits = 7}, // [74] - {.code = 0xCC000000, .bits = 7}, // [75] - {.code = 0xCE000000, .bits = 7}, // [76] - {.code = 0xD0000000, .bits = 7}, // [77] - {.code = 0xD2000000, .bits = 7}, // [78] - {.code = 0xD4000000, .bits = 7}, // [79] - {.code = 0xD6000000, .bits = 7}, // [80] - {.code = 0xD8000000, .bits = 7}, // [81] - {.code = 0xDA000000, .bits = 7}, // [82] - {.code = 0xDC000000, .bits = 7}, // [83] - {.code = 0xDE000000, .bits = 7}, // [84] - {.code = 0xE0000000, .bits = 7}, // [85] - {.code = 0xE2000000, .bits = 7}, // [86] - {.code = 0xE4000000, .bits = 7}, // [87] - {.code = 0xFC000000, .bits = 8}, // [88] - {.code = 0xE6000000, .bits = 7}, // [89] - {.code = 0xFD000000, .bits = 8}, // [90] - {.code = 0xFFD80000, .bits = 13}, // [91] - {.code = 0xFFFE0000, .bits = 19}, // [92] - {.code = 0xFFE00000, .bits = 13}, // [93] - {.code = 0xFFF00000, .bits = 14}, // [94] - {.code = 0x88000000, .bits = 6}, // [95] - {.code = 0xFFFA0000, .bits = 15}, // [96] - {.code = 0x18000000, .bits = 5}, // [97] - {.code = 0x8C000000, .bits = 6}, // [98] - {.code = 0x20000000, .bits = 5}, // [99] - {.code = 0x90000000, .bits = 6}, // [100] - {.code = 0x28000000, .bits = 5}, // [101] - {.code = 0x94000000, .bits = 6}, // [102] - {.code = 0x98000000, .bits = 6}, // [103] - {.code = 0x9C000000, .bits = 6}, // [104] - {.code = 0x30000000, .bits = 5}, // [105] - {.code = 0xE8000000, .bits = 7}, // [106] - {.code = 0xEA000000, .bits = 7}, // [107] - {.code = 0xA0000000, .bits = 6}, // [108] - {.code = 0xA4000000, .bits = 6}, // [109] - {.code = 0xA8000000, .bits = 6}, // [110] - {.code = 0x38000000, .bits = 5}, // [111] - {.code = 0xAC000000, .bits = 6}, // [112] - {.code = 0xEC000000, .bits = 7}, // [113] - {.code = 0xB0000000, .bits = 6}, // [114] - {.code = 0x40000000, .bits = 5}, // [115] - {.code = 0x48000000, .bits = 5}, // [116] - {.code = 0xB4000000, .bits = 6}, // [117] - {.code = 0xEE000000, .bits = 7}, // [118] - {.code = 0xF0000000, .bits = 7}, // [119] - {.code = 0xF2000000, .bits = 7}, // [120] - {.code = 0xF4000000, .bits = 7}, // [121] - {.code = 0xF6000000, .bits = 7}, // [122] - {.code = 0xFFFC0000, .bits = 15}, // [123] - {.code = 0xFF800000, .bits = 11}, // [124] - {.code = 0xFFF40000, .bits = 14}, // [125] - {.code = 0xFFE80000, .bits = 13}, // [126] - {.code = 0xFFFFFFC0, .bits = 28}, // [127] - {.code = 0xFFFE6000, .bits = 20}, // [128] - {.code = 0xFFFF4800, .bits = 22}, // [129] - {.code = 0xFFFE7000, .bits = 20}, // [130] - {.code = 0xFFFE8000, .bits = 20}, // [131] - {.code = 0xFFFF4C00, .bits = 22}, // [132] - {.code = 0xFFFF5000, .bits = 22}, // [133] - {.code = 0xFFFF5400, .bits = 22}, // [134] - {.code = 0xFFFFB200, .bits = 23}, // [135] - {.code = 0xFFFF5800, .bits = 22}, // [136] - {.code = 0xFFFFB400, .bits = 23}, // [137] - {.code = 0xFFFFB600, .bits = 23}, // [138] - {.code = 0xFFFFB800, .bits = 23}, // [139] - {.code = 0xFFFFBA00, .bits = 23}, // [140] - {.code = 0xFFFFBC00, .bits = 23}, // [141] - {.code = 0xFFFFEB00, .bits = 24}, // [142] - {.code = 0xFFFFBE00, .bits = 23}, // [143] - {.code = 0xFFFFEC00, .bits = 24}, // [144] - {.code = 0xFFFFED00, .bits = 24}, // [145] - {.code = 0xFFFF5C00, .bits = 22}, // [146] - {.code = 0xFFFFC000, .bits = 23}, // [147] - {.code = 0xFFFFEE00, .bits = 24}, // [148] - {.code = 0xFFFFC200, .bits = 23}, // [149] - {.code = 0xFFFFC400, .bits = 23}, // [150] - {.code = 0xFFFFC600, .bits = 23}, // [151] - {.code = 0xFFFFC800, .bits = 23}, // [152] - {.code = 0xFFFEE000, .bits = 21}, // [153] - {.code = 0xFFFF6000, .bits = 22}, // [154] - {.code = 0xFFFFCA00, .bits = 23}, // [155] - {.code = 0xFFFF6400, .bits = 22}, // [156] - {.code = 0xFFFFCC00, .bits = 23}, // [157] - {.code = 0xFFFFCE00, .bits = 23}, // [158] - {.code = 0xFFFFEF00, .bits = 24}, // [159] - {.code = 0xFFFF6800, .bits = 22}, // [160] - {.code = 0xFFFEE800, .bits = 21}, // [161] - {.code = 0xFFFE9000, .bits = 20}, // [162] - {.code = 0xFFFF6C00, .bits = 22}, // [163] - {.code = 0xFFFF7000, .bits = 22}, // [164] - {.code = 0xFFFFD000, .bits = 23}, // [165] - {.code = 0xFFFFD200, .bits = 23}, // [166] - {.code = 0xFFFEF000, .bits = 21}, // [167] - {.code = 0xFFFFD400, .bits = 23}, // [168] - {.code = 0xFFFF7400, .bits = 22}, // [169] - {.code = 0xFFFF7800, .bits = 22}, // [170] - {.code = 0xFFFFF000, .bits = 24}, // [171] - {.code = 0xFFFEF800, .bits = 21}, // [172] - {.code = 0xFFFF7C00, .bits = 22}, // [173] - {.code = 0xFFFFD600, .bits = 23}, // [174] - {.code = 0xFFFFD800, .bits = 23}, // [175] - {.code = 0xFFFF0000, .bits = 21}, // [176] - {.code = 0xFFFF0800, .bits = 21}, // [177] - {.code = 0xFFFF8000, .bits = 22}, // [178] - {.code = 0xFFFF1000, .bits = 21}, // [179] - {.code = 0xFFFFDA00, .bits = 23}, // [180] - {.code = 0xFFFF8400, .bits = 22}, // [181] - {.code = 0xFFFFDC00, .bits = 23}, // [182] - {.code = 0xFFFFDE00, .bits = 23}, // [183] - {.code = 0xFFFEA000, .bits = 20}, // [184] - {.code = 0xFFFF8800, .bits = 22}, // [185] - {.code = 0xFFFF8C00, .bits = 22}, // [186] - {.code = 0xFFFF9000, .bits = 22}, // [187] - {.code = 0xFFFFE000, .bits = 23}, // [188] - {.code = 0xFFFF9400, .bits = 22}, // [189] - {.code = 0xFFFF9800, .bits = 22}, // [190] - {.code = 0xFFFFE200, .bits = 23}, // [191] - {.code = 0xFFFFF800, .bits = 26}, // [192] - {.code = 0xFFFFF840, .bits = 26}, // [193] - {.code = 0xFFFEB000, .bits = 20}, // [194] - {.code = 0xFFFE2000, .bits = 19}, // [195] - {.code = 0xFFFF9C00, .bits = 22}, // [196] - {.code = 0xFFFFE400, .bits = 23}, // [197] - {.code = 0xFFFFA000, .bits = 22}, // [198] - {.code = 0xFFFFF600, .bits = 25}, // [199] - {.code = 0xFFFFF880, .bits = 26}, // [200] - {.code = 0xFFFFF8C0, .bits = 26}, // [201] - {.code = 0xFFFFF900, .bits = 26}, // [202] - {.code = 0xFFFFFBC0, .bits = 27}, // [203] - {.code = 0xFFFFFBE0, .bits = 27}, // [204] - {.code = 0xFFFFF940, .bits = 26}, // [205] - {.code = 0xFFFFF100, .bits = 24}, // [206] - {.code = 0xFFFFF680, .bits = 25}, // [207] - {.code = 0xFFFE4000, .bits = 19}, // [208] - {.code = 0xFFFF1800, .bits = 21}, // [209] - {.code = 0xFFFFF980, .bits = 26}, // [210] - {.code = 0xFFFFFC00, .bits = 27}, // [211] - {.code = 0xFFFFFC20, .bits = 27}, // [212] - {.code = 0xFFFFF9C0, .bits = 26}, // [213] - {.code = 0xFFFFFC40, .bits = 27}, // [214] - {.code = 0xFFFFF200, .bits = 24}, // [215] - {.code = 0xFFFF2000, .bits = 21}, // [216] - {.code = 0xFFFF2800, .bits = 21}, // [217] - {.code = 0xFFFFFA00, .bits = 26}, // [218] - {.code = 0xFFFFFA40, .bits = 26}, // [219] - {.code = 0xFFFFFFD0, .bits = 28}, // [220] - {.code = 0xFFFFFC60, .bits = 27}, // [221] - {.code = 0xFFFFFC80, .bits = 27}, // [222] - {.code = 0xFFFFFCA0, .bits = 27}, // [223] - {.code = 0xFFFEC000, .bits = 20}, // [224] - {.code = 0xFFFFF300, .bits = 24}, // [225] - {.code = 0xFFFED000, .bits = 20}, // [226] - {.code = 0xFFFF3000, .bits = 21}, // [227] - {.code = 0xFFFFA400, .bits = 22}, // [228] - {.code = 0xFFFF3800, .bits = 21}, // [229] - {.code = 0xFFFF4000, .bits = 21}, // [230] - {.code = 0xFFFFE600, .bits = 23}, // [231] - {.code = 0xFFFFA800, .bits = 22}, // [232] - {.code = 0xFFFFAC00, .bits = 22}, // [233] - {.code = 0xFFFFF700, .bits = 25}, // [234] - {.code = 0xFFFFF780, .bits = 25}, // [235] - {.code = 0xFFFFF400, .bits = 24}, // [236] - {.code = 0xFFFFF500, .bits = 24}, // [237] - {.code = 0xFFFFFA80, .bits = 26}, // [238] - {.code = 0xFFFFE800, .bits = 23}, // [239] - {.code = 0xFFFFFAC0, .bits = 26}, // [240] - {.code = 0xFFFFFCC0, .bits = 27}, // [241] - {.code = 0xFFFFFB00, .bits = 26}, // [242] - {.code = 0xFFFFFB40, .bits = 26}, // [243] - {.code = 0xFFFFFCE0, .bits = 27}, // [244] - {.code = 0xFFFFFD00, .bits = 27}, // [245] - {.code = 0xFFFFFD20, .bits = 27}, // [246] - {.code = 0xFFFFFD40, .bits = 27}, // [247] - {.code = 0xFFFFFD60, .bits = 27}, // [248] - {.code = 0xFFFFFFE0, .bits = 28}, // [249] - {.code = 0xFFFFFD80, .bits = 27}, // [250] - {.code = 0xFFFFFDA0, .bits = 27}, // [251] - {.code = 0xFFFFFDC0, .bits = 27}, // [252] - {.code = 0xFFFFFDE0, .bits = 27}, // [253] - {.code = 0xFFFFFE00, .bits = 27}, // [254] - {.code = 0xFFFFFB80, .bits = 26}, // [255] - {.code = 0xFFFFFFFC, .bits = 30}, // [256] -}; - -/** Static Huffman decoding tree, flattened as an array */ -static const huffman_decode_s huffman_decode_tree[513] = { - {.value = -1, .offset = {1, 44}}, // [0] - {.value = -1, .offset = {1, 16}}, // [1] - {.value = -1, .offset = {1, 8}}, // [2] - {.value = -1, .offset = {1, 4}}, // [3] - {.value = -1, .offset = {1, 2}}, // [4] - {.value = 48, .offset = {0, 0}}, // [5]:0b00000 - {.value = 49, .offset = {0, 0}}, // [6]:0b00001 - {.value = -1, .offset = {1, 2}}, // [7] - {.value = 50, .offset = {0, 0}}, // [8]:0b00010 - {.value = 97, .offset = {0, 0}}, // [9]:0b00011 - {.value = -1, .offset = {1, 4}}, // [10] - {.value = -1, .offset = {1, 2}}, // [11] - {.value = 99, .offset = {0, 0}}, // [12]:0b00100 - {.value = 101, .offset = {0, 0}}, // [13]:0b00101 - {.value = -1, .offset = {1, 2}}, // [14] - {.value = 105, .offset = {0, 0}}, // [15]:0b00110 - {.value = 111, .offset = {0, 0}}, // [16]:0b00111 - {.value = -1, .offset = {1, 12}}, // [17] - {.value = -1, .offset = {1, 4}}, // [18] - {.value = -1, .offset = {1, 2}}, // [19] - {.value = 115, .offset = {0, 0}}, // [20]:0b01000 - {.value = 116, .offset = {0, 0}}, // [21]:0b01001 - {.value = -1, .offset = {1, 4}}, // [22] - {.value = -1, .offset = {1, 2}}, // [23] - {.value = 32, .offset = {0, 0}}, // [24]:0b010100 - {.value = 37, .offset = {0, 0}}, // [25]:0b010101 - {.value = -1, .offset = {1, 2}}, // [26] - {.value = 45, .offset = {0, 0}}, // [27]:0b010110 - {.value = 46, .offset = {0, 0}}, // [28]:0b010111 - {.value = -1, .offset = {1, 8}}, // [29] - {.value = -1, .offset = {1, 4}}, // [30] - {.value = -1, .offset = {1, 2}}, // [31] - {.value = 47, .offset = {0, 0}}, // [32]:0b011000 - {.value = 51, .offset = {0, 0}}, // [33]:0b011001 - {.value = -1, .offset = {1, 2}}, // [34] - {.value = 52, .offset = {0, 0}}, // [35]:0b011010 - {.value = 53, .offset = {0, 0}}, // [36]:0b011011 - {.value = -1, .offset = {1, 4}}, // [37] - {.value = -1, .offset = {1, 2}}, // [38] - {.value = 54, .offset = {0, 0}}, // [39]:0b011100 - {.value = 55, .offset = {0, 0}}, // [40]:0b011101 - {.value = -1, .offset = {1, 2}}, // [41] - {.value = 56, .offset = {0, 0}}, // [42]:0b011110 - {.value = 57, .offset = {0, 0}}, // [43]:0b011111 - {.value = -1, .offset = {1, 36}}, // [44] - {.value = -1, .offset = {1, 16}}, // [45] - {.value = -1, .offset = {1, 8}}, // [46] - {.value = -1, .offset = {1, 4}}, // [47] - {.value = -1, .offset = {1, 2}}, // [48] - {.value = 61, .offset = {0, 0}}, // [49]:0b100000 - {.value = 65, .offset = {0, 0}}, // [50]:0b100001 - {.value = -1, .offset = {1, 2}}, // [51] - {.value = 95, .offset = {0, 0}}, // [52]:0b100010 - {.value = 98, .offset = {0, 0}}, // [53]:0b100011 - {.value = -1, .offset = {1, 4}}, // [54] - {.value = -1, .offset = {1, 2}}, // [55] - {.value = 100, .offset = {0, 0}}, // [56]:0b100100 - {.value = 102, .offset = {0, 0}}, // [57]:0b100101 - {.value = -1, .offset = {1, 2}}, // [58] - {.value = 103, .offset = {0, 0}}, // [59]:0b100110 - {.value = 104, .offset = {0, 0}}, // [60]:0b100111 - {.value = -1, .offset = {1, 8}}, // [61] - {.value = -1, .offset = {1, 4}}, // [62] - {.value = -1, .offset = {1, 2}}, // [63] - {.value = 108, .offset = {0, 0}}, // [64]:0b101000 - {.value = 109, .offset = {0, 0}}, // [65]:0b101001 - {.value = -1, .offset = {1, 2}}, // [66] - {.value = 110, .offset = {0, 0}}, // [67]:0b101010 - {.value = 112, .offset = {0, 0}}, // [68]:0b101011 - {.value = -1, .offset = {1, 4}}, // [69] - {.value = -1, .offset = {1, 2}}, // [70] - {.value = 114, .offset = {0, 0}}, // [71]:0b101100 - {.value = 117, .offset = {0, 0}}, // [72]:0b101101 - {.value = -1, .offset = {1, 4}}, // [73] - {.value = -1, .offset = {1, 2}}, // [74] - {.value = 58, .offset = {0, 0}}, // [75]:0b1011100 - {.value = 66, .offset = {0, 0}}, // [76]:0b1011101 - {.value = -1, .offset = {1, 2}}, // [77] - {.value = 67, .offset = {0, 0}}, // [78]:0b1011110 - {.value = 68, .offset = {0, 0}}, // [79]:0b1011111 - {.value = -1, .offset = {1, 32}}, // [80] - {.value = -1, .offset = {1, 16}}, // [81] - {.value = -1, .offset = {1, 8}}, // [82] - {.value = -1, .offset = {1, 4}}, // [83] - {.value = -1, .offset = {1, 2}}, // [84] - {.value = 69, .offset = {0, 0}}, // [85]:0b1100000 - {.value = 70, .offset = {0, 0}}, // [86]:0b1100001 - {.value = -1, .offset = {1, 2}}, // [87] - {.value = 71, .offset = {0, 0}}, // [88]:0b1100010 - {.value = 72, .offset = {0, 0}}, // [89]:0b1100011 - {.value = -1, .offset = {1, 4}}, // [90] - {.value = -1, .offset = {1, 2}}, // [91] - {.value = 73, .offset = {0, 0}}, // [92]:0b1100100 - {.value = 74, .offset = {0, 0}}, // [93]:0b1100101 - {.value = -1, .offset = {1, 2}}, // [94] - {.value = 75, .offset = {0, 0}}, // [95]:0b1100110 - {.value = 76, .offset = {0, 0}}, // [96]:0b1100111 - {.value = -1, .offset = {1, 8}}, // [97] - {.value = -1, .offset = {1, 4}}, // [98] - {.value = -1, .offset = {1, 2}}, // [99] - {.value = 77, .offset = {0, 0}}, // [100]:0b1101000 - {.value = 78, .offset = {0, 0}}, // [101]:0b1101001 - {.value = -1, .offset = {1, 2}}, // [102] - {.value = 79, .offset = {0, 0}}, // [103]:0b1101010 - {.value = 80, .offset = {0, 0}}, // [104]:0b1101011 - {.value = -1, .offset = {1, 4}}, // [105] - {.value = -1, .offset = {1, 2}}, // [106] - {.value = 81, .offset = {0, 0}}, // [107]:0b1101100 - {.value = 82, .offset = {0, 0}}, // [108]:0b1101101 - {.value = -1, .offset = {1, 2}}, // [109] - {.value = 83, .offset = {0, 0}}, // [110]:0b1101110 - {.value = 84, .offset = {0, 0}}, // [111]:0b1101111 - {.value = -1, .offset = {1, 16}}, // [112] - {.value = -1, .offset = {1, 8}}, // [113] - {.value = -1, .offset = {1, 4}}, // [114] - {.value = -1, .offset = {1, 2}}, // [115] - {.value = 85, .offset = {0, 0}}, // [116]:0b1110000 - {.value = 86, .offset = {0, 0}}, // [117]:0b1110001 - {.value = -1, .offset = {1, 2}}, // [118] - {.value = 87, .offset = {0, 0}}, // [119]:0b1110010 - {.value = 89, .offset = {0, 0}}, // [120]:0b1110011 - {.value = -1, .offset = {1, 4}}, // [121] - {.value = -1, .offset = {1, 2}}, // [122] - {.value = 106, .offset = {0, 0}}, // [123]:0b1110100 - {.value = 107, .offset = {0, 0}}, // [124]:0b1110101 - {.value = -1, .offset = {1, 2}}, // [125] - {.value = 113, .offset = {0, 0}}, // [126]:0b1110110 - {.value = 118, .offset = {0, 0}}, // [127]:0b1110111 - {.value = -1, .offset = {1, 8}}, // [128] - {.value = -1, .offset = {1, 4}}, // [129] - {.value = -1, .offset = {1, 2}}, // [130] - {.value = 119, .offset = {0, 0}}, // [131]:0b1111000 - {.value = 120, .offset = {0, 0}}, // [132]:0b1111001 - {.value = -1, .offset = {1, 2}}, // [133] - {.value = 121, .offset = {0, 0}}, // [134]:0b1111010 - {.value = 122, .offset = {0, 0}}, // [135]:0b1111011 - {.value = -1, .offset = {1, 8}}, // [136] - {.value = -1, .offset = {1, 4}}, // [137] - {.value = -1, .offset = {1, 2}}, // [138] - {.value = 38, .offset = {0, 0}}, // [139]:0b11111000 - {.value = 42, .offset = {0, 0}}, // [140]:0b11111001 - {.value = -1, .offset = {1, 2}}, // [141] - {.value = 44, .offset = {0, 0}}, // [142]:0b11111010 - {.value = 59, .offset = {0, 0}}, // [143]:0b11111011 - {.value = -1, .offset = {1, 4}}, // [144] - {.value = -1, .offset = {1, 2}}, // [145] - {.value = 88, .offset = {0, 0}}, // [146]:0b11111100 - {.value = 90, .offset = {0, 0}}, // [147]:0b11111101 - {.value = -1, .offset = {1, 8}}, // [148] - {.value = -1, .offset = {1, 4}}, // [149] - {.value = -1, .offset = {1, 2}}, // [150] - {.value = 33, .offset = {0, 0}}, // [151]:0b1111111000 - {.value = 34, .offset = {0, 0}}, // [152]:0b1111111001 - {.value = -1, .offset = {1, 2}}, // [153] - {.value = 40, .offset = {0, 0}}, // [154]:0b1111111010 - {.value = 41, .offset = {0, 0}}, // [155]:0b1111111011 - {.value = -1, .offset = {1, 6}}, // [156] - {.value = -1, .offset = {1, 2}}, // [157] - {.value = 63, .offset = {0, 0}}, // [158]:0b1111111100 - {.value = -1, .offset = {1, 2}}, // [159] - {.value = 39, .offset = {0, 0}}, // [160]:0b11111111010 - {.value = 43, .offset = {0, 0}}, // [161]:0b11111111011 - {.value = -1, .offset = {1, 6}}, // [162] - {.value = -1, .offset = {1, 2}}, // [163] - {.value = 124, .offset = {0, 0}}, // [164]:0b11111111100 - {.value = -1, .offset = {1, 2}}, // [165] - {.value = 35, .offset = {0, 0}}, // [166]:0b111111111010 - {.value = 62, .offset = {0, 0}}, // [167]:0b111111111011 - {.value = -1, .offset = {1, 8}}, // [168] - {.value = -1, .offset = {1, 4}}, // [169] - {.value = -1, .offset = {1, 2}}, // [170] - {.value = 0, .offset = {0, 0}}, // [171]:0b1111111111000 - {.value = 36, .offset = {0, 0}}, // [172]:0b1111111111001 - {.value = -1, .offset = {1, 2}}, // [173] - {.value = 64, .offset = {0, 0}}, // [174]:0b1111111111010 - {.value = 91, .offset = {0, 0}}, // [175]:0b1111111111011 - {.value = -1, .offset = {1, 4}}, // [176] - {.value = -1, .offset = {1, 2}}, // [177] - {.value = 93, .offset = {0, 0}}, // [178]:0b1111111111100 - {.value = 126, .offset = {0, 0}}, // [179]:0b1111111111101 - {.value = -1, .offset = {1, 4}}, // [180] - {.value = -1, .offset = {1, 2}}, // [181] - {.value = 94, .offset = {0, 0}}, // [182]:0b11111111111100 - {.value = 125, .offset = {0, 0}}, // [183]:0b11111111111101 - {.value = -1, .offset = {1, 4}}, // [184] - {.value = -1, .offset = {1, 2}}, // [185] - {.value = 60, .offset = {0, 0}}, // [186]:0b111111111111100 - {.value = 96, .offset = {0, 0}}, // [187]:0b111111111111101 - {.value = -1, .offset = {1, 2}}, // [188] - {.value = 123, .offset = {0, 0}}, // [189]:0b111111111111110 - {.value = -1, .offset = {1, 30}}, // [190] - {.value = -1, .offset = {1, 10}}, // [191] - {.value = -1, .offset = {1, 4}}, // [192] - {.value = -1, .offset = {1, 2}}, // [193] - {.value = 92, .offset = {0, 0}}, // [194]:0b1111111111111110000 - {.value = 195, .offset = {0, 0}}, // [195]:0b1111111111111110001 - {.value = -1, .offset = {1, 2}}, // [196] - {.value = 208, .offset = {0, 0}}, // [197]:0b1111111111111110010 - {.value = -1, .offset = {1, 2}}, // [198] - {.value = 128, .offset = {0, 0}}, // [199]:0b11111111111111100110 - {.value = 130, .offset = {0, 0}}, // [200]:0b11111111111111100111 - {.value = -1, .offset = {1, 8}}, // [201] - {.value = -1, .offset = {1, 4}}, // [202] - {.value = -1, .offset = {1, 2}}, // [203] - {.value = 131, .offset = {0, 0}}, // [204]:0b11111111111111101000 - {.value = 162, .offset = {0, 0}}, // [205]:0b11111111111111101001 - {.value = -1, .offset = {1, 2}}, // [206] - {.value = 184, .offset = {0, 0}}, // [207]:0b11111111111111101010 - {.value = 194, .offset = {0, 0}}, // [208]:0b11111111111111101011 - {.value = -1, .offset = {1, 4}}, // [209] - {.value = -1, .offset = {1, 2}}, // [210] - {.value = 224, .offset = {0, 0}}, // [211]:0b11111111111111101100 - {.value = 226, .offset = {0, 0}}, // [212]:0b11111111111111101101 - {.value = -1, .offset = {1, 4}}, // [213] - {.value = -1, .offset = {1, 2}}, // [214] - {.value = 153, .offset = {0, 0}}, // [215]:0b111111111111111011100 - {.value = 161, .offset = {0, 0}}, // [216]:0b111111111111111011101 - {.value = -1, .offset = {1, 2}}, // [217] - {.value = 167, .offset = {0, 0}}, // [218]:0b111111111111111011110 - {.value = 172, .offset = {0, 0}}, // [219]:0b111111111111111011111 - {.value = -1, .offset = {1, 46}}, // [220] - {.value = -1, .offset = {1, 16}}, // [221] - {.value = -1, .offset = {1, 8}}, // [222] - {.value = -1, .offset = {1, 4}}, // [223] - {.value = -1, .offset = {1, 2}}, // [224] - {.value = 176, .offset = {0, 0}}, // [225]:0b111111111111111100000 - {.value = 177, .offset = {0, 0}}, // [226]:0b111111111111111100001 - {.value = -1, .offset = {1, 2}}, // [227] - {.value = 179, .offset = {0, 0}}, // [228]:0b111111111111111100010 - {.value = 209, .offset = {0, 0}}, // [229]:0b111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [230] - {.value = -1, .offset = {1, 2}}, // [231] - {.value = 216, .offset = {0, 0}}, // [232]:0b111111111111111100100 - {.value = 217, .offset = {0, 0}}, // [233]:0b111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [234] - {.value = 227, .offset = {0, 0}}, // [235]:0b111111111111111100110 - {.value = 229, .offset = {0, 0}}, // [236]:0b111111111111111100111 - {.value = -1, .offset = {1, 14}}, // [237] - {.value = -1, .offset = {1, 6}}, // [238] - {.value = -1, .offset = {1, 2}}, // [239] - {.value = 230, .offset = {0, 0}}, // [240]:0b111111111111111101000 - {.value = -1, .offset = {1, 2}}, // [241] - {.value = 129, .offset = {0, 0}}, // [242]:0b1111111111111111010010 - {.value = 132, .offset = {0, 0}}, // [243]:0b1111111111111111010011 - {.value = -1, .offset = {1, 4}}, // [244] - {.value = -1, .offset = {1, 2}}, // [245] - {.value = 133, .offset = {0, 0}}, // [246]:0b1111111111111111010100 - {.value = 134, .offset = {0, 0}}, // [247]:0b1111111111111111010101 - {.value = -1, .offset = {1, 2}}, // [248] - {.value = 136, .offset = {0, 0}}, // [249]:0b1111111111111111010110 - {.value = 146, .offset = {0, 0}}, // [250]:0b1111111111111111010111 - {.value = -1, .offset = {1, 8}}, // [251] - {.value = -1, .offset = {1, 4}}, // [252] - {.value = -1, .offset = {1, 2}}, // [253] - {.value = 154, .offset = {0, 0}}, // [254]:0b1111111111111111011000 - {.value = 156, .offset = {0, 0}}, // [255]:0b1111111111111111011001 - {.value = -1, .offset = {1, 2}}, // [256] - {.value = 160, .offset = {0, 0}}, // [257]:0b1111111111111111011010 - {.value = 163, .offset = {0, 0}}, // [258]:0b1111111111111111011011 - {.value = -1, .offset = {1, 4}}, // [259] - {.value = -1, .offset = {1, 2}}, // [260] - {.value = 164, .offset = {0, 0}}, // [261]:0b1111111111111111011100 - {.value = 169, .offset = {0, 0}}, // [262]:0b1111111111111111011101 - {.value = -1, .offset = {1, 2}}, // [263] - {.value = 170, .offset = {0, 0}}, // [264]:0b1111111111111111011110 - {.value = 173, .offset = {0, 0}}, // [265]:0b1111111111111111011111 - {.value = -1, .offset = {1, 40}}, // [266] - {.value = -1, .offset = {1, 16}}, // [267] - {.value = -1, .offset = {1, 8}}, // [268] - {.value = -1, .offset = {1, 4}}, // [269] - {.value = -1, .offset = {1, 2}}, // [270] - {.value = 178, .offset = {0, 0}}, // [271]:0b1111111111111111100000 - {.value = 181, .offset = {0, 0}}, // [272]:0b1111111111111111100001 - {.value = -1, .offset = {1, 2}}, // [273] - {.value = 185, .offset = {0, 0}}, // [274]:0b1111111111111111100010 - {.value = 186, .offset = {0, 0}}, // [275]:0b1111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [276] - {.value = -1, .offset = {1, 2}}, // [277] - {.value = 187, .offset = {0, 0}}, // [278]:0b1111111111111111100100 - {.value = 189, .offset = {0, 0}}, // [279]:0b1111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [280] - {.value = 190, .offset = {0, 0}}, // [281]:0b1111111111111111100110 - {.value = 196, .offset = {0, 0}}, // [282]:0b1111111111111111100111 - {.value = -1, .offset = {1, 8}}, // [283] - {.value = -1, .offset = {1, 4}}, // [284] - {.value = -1, .offset = {1, 2}}, // [285] - {.value = 198, .offset = {0, 0}}, // [286]:0b1111111111111111101000 - {.value = 228, .offset = {0, 0}}, // [287]:0b1111111111111111101001 - {.value = -1, .offset = {1, 2}}, // [288] - {.value = 232, .offset = {0, 0}}, // [289]:0b1111111111111111101010 - {.value = 233, .offset = {0, 0}}, // [290]:0b1111111111111111101011 - {.value = -1, .offset = {1, 8}}, // [291] - {.value = -1, .offset = {1, 4}}, // [292] - {.value = -1, .offset = {1, 2}}, // [293] - {.value = 1, .offset = {0, 0}}, // [294]:0b11111111111111111011000 - {.value = 135, .offset = {0, 0}}, // [295]:0b11111111111111111011001 - {.value = -1, .offset = {1, 2}}, // [296] - {.value = 137, .offset = {0, 0}}, // [297]:0b11111111111111111011010 - {.value = 138, .offset = {0, 0}}, // [298]:0b11111111111111111011011 - {.value = -1, .offset = {1, 4}}, // [299] - {.value = -1, .offset = {1, 2}}, // [300] - {.value = 139, .offset = {0, 0}}, // [301]:0b11111111111111111011100 - {.value = 140, .offset = {0, 0}}, // [302]:0b11111111111111111011101 - {.value = -1, .offset = {1, 2}}, // [303] - {.value = 141, .offset = {0, 0}}, // [304]:0b11111111111111111011110 - {.value = 143, .offset = {0, 0}}, // [305]:0b11111111111111111011111 - {.value = -1, .offset = {1, 32}}, // [306] - {.value = -1, .offset = {1, 16}}, // [307] - {.value = -1, .offset = {1, 8}}, // [308] - {.value = -1, .offset = {1, 4}}, // [309] - {.value = -1, .offset = {1, 2}}, // [310] - {.value = 147, .offset = {0, 0}}, // [311]:0b11111111111111111100000 - {.value = 149, .offset = {0, 0}}, // [312]:0b11111111111111111100001 - {.value = -1, .offset = {1, 2}}, // [313] - {.value = 150, .offset = {0, 0}}, // [314]:0b11111111111111111100010 - {.value = 151, .offset = {0, 0}}, // [315]:0b11111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [316] - {.value = -1, .offset = {1, 2}}, // [317] - {.value = 152, .offset = {0, 0}}, // [318]:0b11111111111111111100100 - {.value = 155, .offset = {0, 0}}, // [319]:0b11111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [320] - {.value = 157, .offset = {0, 0}}, // [321]:0b11111111111111111100110 - {.value = 158, .offset = {0, 0}}, // [322]:0b11111111111111111100111 - {.value = -1, .offset = {1, 8}}, // [323] - {.value = -1, .offset = {1, 4}}, // [324] - {.value = -1, .offset = {1, 2}}, // [325] - {.value = 165, .offset = {0, 0}}, // [326]:0b11111111111111111101000 - {.value = 166, .offset = {0, 0}}, // [327]:0b11111111111111111101001 - {.value = -1, .offset = {1, 2}}, // [328] - {.value = 168, .offset = {0, 0}}, // [329]:0b11111111111111111101010 - {.value = 174, .offset = {0, 0}}, // [330]:0b11111111111111111101011 - {.value = -1, .offset = {1, 4}}, // [331] - {.value = -1, .offset = {1, 2}}, // [332] - {.value = 175, .offset = {0, 0}}, // [333]:0b11111111111111111101100 - {.value = 180, .offset = {0, 0}}, // [334]:0b11111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [335] - {.value = 182, .offset = {0, 0}}, // [336]:0b11111111111111111101110 - {.value = 183, .offset = {0, 0}}, // [337]:0b11111111111111111101111 - {.value = -1, .offset = {1, 22}}, // [338] - {.value = -1, .offset = {1, 8}}, // [339] - {.value = -1, .offset = {1, 4}}, // [340] - {.value = -1, .offset = {1, 2}}, // [341] - {.value = 188, .offset = {0, 0}}, // [342]:0b11111111111111111110000 - {.value = 191, .offset = {0, 0}}, // [343]:0b11111111111111111110001 - {.value = -1, .offset = {1, 2}}, // [344] - {.value = 197, .offset = {0, 0}}, // [345]:0b11111111111111111110010 - {.value = 231, .offset = {0, 0}}, // [346]:0b11111111111111111110011 - {.value = -1, .offset = {1, 6}}, // [347] - {.value = -1, .offset = {1, 2}}, // [348] - {.value = 239, .offset = {0, 0}}, // [349]:0b11111111111111111110100 - {.value = -1, .offset = {1, 2}}, // [350] - {.value = 9, .offset = {0, 0}}, // [351]:0b111111111111111111101010 - {.value = 142, .offset = {0, 0}}, // [352]:0b111111111111111111101011 - {.value = -1, .offset = {1, 4}}, // [353] - {.value = -1, .offset = {1, 2}}, // [354] - {.value = 144, .offset = {0, 0}}, // [355]:0b111111111111111111101100 - {.value = 145, .offset = {0, 0}}, // [356]:0b111111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [357] - {.value = 148, .offset = {0, 0}}, // [358]:0b111111111111111111101110 - {.value = 159, .offset = {0, 0}}, // [359]:0b111111111111111111101111 - {.value = -1, .offset = {1, 20}}, // [360] - {.value = -1, .offset = {1, 8}}, // [361] - {.value = -1, .offset = {1, 4}}, // [362] - {.value = -1, .offset = {1, 2}}, // [363] - {.value = 171, .offset = {0, 0}}, // [364]:0b111111111111111111110000 - {.value = 206, .offset = {0, 0}}, // [365]:0b111111111111111111110001 - {.value = -1, .offset = {1, 2}}, // [366] - {.value = 215, .offset = {0, 0}}, // [367]:0b111111111111111111110010 - {.value = 225, .offset = {0, 0}}, // [368]:0b111111111111111111110011 - {.value = -1, .offset = {1, 4}}, // [369] - {.value = -1, .offset = {1, 2}}, // [370] - {.value = 236, .offset = {0, 0}}, // [371]:0b111111111111111111110100 - {.value = 237, .offset = {0, 0}}, // [372]:0b111111111111111111110101 - {.value = -1, .offset = {1, 4}}, // [373] - {.value = -1, .offset = {1, 2}}, // [374] - {.value = 199, .offset = {0, 0}}, // [375]:0b1111111111111111111101100 - {.value = 207, .offset = {0, 0}}, // [376]:0b1111111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [377] - {.value = 234, .offset = {0, 0}}, // [378]:0b1111111111111111111101110 - {.value = 235, .offset = {0, 0}}, // [379]:0b1111111111111111111101111 - {.value = -1, .offset = {1, 34}}, // [380] - {.value = -1, .offset = {1, 16}}, // [381] - {.value = -1, .offset = {1, 8}}, // [382] - {.value = -1, .offset = {1, 4}}, // [383] - {.value = -1, .offset = {1, 2}}, // [384] - {.value = 192, .offset = {0, 0}}, // [385]:0b11111111111111111111100000 - {.value = 193, .offset = {0, 0}}, // [386]:0b11111111111111111111100001 - {.value = -1, .offset = {1, 2}}, // [387] - {.value = 200, .offset = {0, 0}}, // [388]:0b11111111111111111111100010 - {.value = 201, .offset = {0, 0}}, // [389]:0b11111111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [390] - {.value = -1, .offset = {1, 2}}, // [391] - {.value = 202, .offset = {0, 0}}, // [392]:0b11111111111111111111100100 - {.value = 205, .offset = {0, 0}}, // [393]:0b11111111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [394] - {.value = 210, .offset = {0, 0}}, // [395]:0b11111111111111111111100110 - {.value = 213, .offset = {0, 0}}, // [396]:0b11111111111111111111100111 - {.value = -1, .offset = {1, 8}}, // [397] - {.value = -1, .offset = {1, 4}}, // [398] - {.value = -1, .offset = {1, 2}}, // [399] - {.value = 218, .offset = {0, 0}}, // [400]:0b11111111111111111111101000 - {.value = 219, .offset = {0, 0}}, // [401]:0b11111111111111111111101001 - {.value = -1, .offset = {1, 2}}, // [402] - {.value = 238, .offset = {0, 0}}, // [403]:0b11111111111111111111101010 - {.value = 240, .offset = {0, 0}}, // [404]:0b11111111111111111111101011 - {.value = -1, .offset = {1, 4}}, // [405] - {.value = -1, .offset = {1, 2}}, // [406] - {.value = 242, .offset = {0, 0}}, // [407]:0b11111111111111111111101100 - {.value = 243, .offset = {0, 0}}, // [408]:0b11111111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [409] - {.value = 255, .offset = {0, 0}}, // [410]:0b11111111111111111111101110 - {.value = -1, .offset = {1, 2}}, // [411] - {.value = 203, .offset = {0, 0}}, // [412]:0b111111111111111111111011110 - {.value = 204, .offset = {0, 0}}, // [413]:0b111111111111111111111011111 - {.value = -1, .offset = {1, 32}}, // [414] - {.value = -1, .offset = {1, 16}}, // [415] - {.value = -1, .offset = {1, 8}}, // [416] - {.value = -1, .offset = {1, 4}}, // [417] - {.value = -1, .offset = {1, 2}}, // [418] - {.value = 211, .offset = {0, 0}}, // [419]:0b111111111111111111111100000 - {.value = 212, .offset = {0, 0}}, // [420]:0b111111111111111111111100001 - {.value = -1, .offset = {1, 2}}, // [421] - {.value = 214, .offset = {0, 0}}, // [422]:0b111111111111111111111100010 - {.value = 221, .offset = {0, 0}}, // [423]:0b111111111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [424] - {.value = -1, .offset = {1, 2}}, // [425] - {.value = 222, .offset = {0, 0}}, // [426]:0b111111111111111111111100100 - {.value = 223, .offset = {0, 0}}, // [427]:0b111111111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [428] - {.value = 241, .offset = {0, 0}}, // [429]:0b111111111111111111111100110 - {.value = 244, .offset = {0, 0}}, // [430]:0b111111111111111111111100111 - {.value = -1, .offset = {1, 8}}, // [431] - {.value = -1, .offset = {1, 4}}, // [432] - {.value = -1, .offset = {1, 2}}, // [433] - {.value = 245, .offset = {0, 0}}, // [434]:0b111111111111111111111101000 - {.value = 246, .offset = {0, 0}}, // [435]:0b111111111111111111111101001 - {.value = -1, .offset = {1, 2}}, // [436] - {.value = 247, .offset = {0, 0}}, // [437]:0b111111111111111111111101010 - {.value = 248, .offset = {0, 0}}, // [438]:0b111111111111111111111101011 - {.value = -1, .offset = {1, 4}}, // [439] - {.value = -1, .offset = {1, 2}}, // [440] - {.value = 250, .offset = {0, 0}}, // [441]:0b111111111111111111111101100 - {.value = 251, .offset = {0, 0}}, // [442]:0b111111111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [443] - {.value = 252, .offset = {0, 0}}, // [444]:0b111111111111111111111101110 - {.value = 253, .offset = {0, 0}}, // [445]:0b111111111111111111111101111 - {.value = -1, .offset = {1, 30}}, // [446] - {.value = -1, .offset = {1, 14}}, // [447] - {.value = -1, .offset = {1, 6}}, // [448] - {.value = -1, .offset = {1, 2}}, // [449] - {.value = 254, .offset = {0, 0}}, // [450]:0b111111111111111111111110000 - {.value = -1, .offset = {1, 2}}, // [451] - {.value = 2, .offset = {0, 0}}, // [452]:0b1111111111111111111111100010 - {.value = 3, .offset = {0, 0}}, // [453]:0b1111111111111111111111100011 - {.value = -1, .offset = {1, 4}}, // [454] - {.value = -1, .offset = {1, 2}}, // [455] - {.value = 4, .offset = {0, 0}}, // [456]:0b1111111111111111111111100100 - {.value = 5, .offset = {0, 0}}, // [457]:0b1111111111111111111111100101 - {.value = -1, .offset = {1, 2}}, // [458] - {.value = 6, .offset = {0, 0}}, // [459]:0b1111111111111111111111100110 - {.value = 7, .offset = {0, 0}}, // [460]:0b1111111111111111111111100111 - {.value = -1, .offset = {1, 8}}, // [461] - {.value = -1, .offset = {1, 4}}, // [462] - {.value = -1, .offset = {1, 2}}, // [463] - {.value = 8, .offset = {0, 0}}, // [464]:0b1111111111111111111111101000 - {.value = 11, .offset = {0, 0}}, // [465]:0b1111111111111111111111101001 - {.value = -1, .offset = {1, 2}}, // [466] - {.value = 12, .offset = {0, 0}}, // [467]:0b1111111111111111111111101010 - {.value = 14, .offset = {0, 0}}, // [468]:0b1111111111111111111111101011 - {.value = -1, .offset = {1, 4}}, // [469] - {.value = -1, .offset = {1, 2}}, // [470] - {.value = 15, .offset = {0, 0}}, // [471]:0b1111111111111111111111101100 - {.value = 16, .offset = {0, 0}}, // [472]:0b1111111111111111111111101101 - {.value = -1, .offset = {1, 2}}, // [473] - {.value = 17, .offset = {0, 0}}, // [474]:0b1111111111111111111111101110 - {.value = 18, .offset = {0, 0}}, // [475]:0b1111111111111111111111101111 - {.value = -1, .offset = {1, 16}}, // [476] - {.value = -1, .offset = {1, 8}}, // [477] - {.value = -1, .offset = {1, 4}}, // [478] - {.value = -1, .offset = {1, 2}}, // [479] - {.value = 19, .offset = {0, 0}}, // [480]:0b1111111111111111111111110000 - {.value = 20, .offset = {0, 0}}, // [481]:0b1111111111111111111111110001 - {.value = -1, .offset = {1, 2}}, // [482] - {.value = 21, .offset = {0, 0}}, // [483]:0b1111111111111111111111110010 - {.value = 23, .offset = {0, 0}}, // [484]:0b1111111111111111111111110011 - {.value = -1, .offset = {1, 4}}, // [485] - {.value = -1, .offset = {1, 2}}, // [486] - {.value = 24, .offset = {0, 0}}, // [487]:0b1111111111111111111111110100 - {.value = 25, .offset = {0, 0}}, // [488]:0b1111111111111111111111110101 - {.value = -1, .offset = {1, 2}}, // [489] - {.value = 26, .offset = {0, 0}}, // [490]:0b1111111111111111111111110110 - {.value = 27, .offset = {0, 0}}, // [491]:0b1111111111111111111111110111 - {.value = -1, .offset = {1, 8}}, // [492] - {.value = -1, .offset = {1, 4}}, // [493] - {.value = -1, .offset = {1, 2}}, // [494] - {.value = 28, .offset = {0, 0}}, // [495]:0b1111111111111111111111111000 - {.value = 29, .offset = {0, 0}}, // [496]:0b1111111111111111111111111001 - {.value = -1, .offset = {1, 2}}, // [497] - {.value = 30, .offset = {0, 0}}, // [498]:0b1111111111111111111111111010 - {.value = 31, .offset = {0, 0}}, // [499]:0b1111111111111111111111111011 - {.value = -1, .offset = {1, 4}}, // [500] - {.value = -1, .offset = {1, 2}}, // [501] - {.value = 127, .offset = {0, 0}}, // [502]:0b1111111111111111111111111100 - {.value = 220, .offset = {0, 0}}, // [503]:0b1111111111111111111111111101 - {.value = -1, .offset = {1, 2}}, // [504] - {.value = 249, .offset = {0, 0}}, // [505]:0b1111111111111111111111111110 - {.value = -1, .offset = {1, 4}}, // [506] - {.value = -1, .offset = {1, 2}}, // [507] - {.value = 10, .offset = {0, 0}}, // [508]:0b111111111111111111111111111100 - {.value = 13, .offset = {0, 0}}, // [509]:0b111111111111111111111111111101 - {.value = -1, .offset = {1, 2}}, // [510] - {.value = 22, .offset = {0, 0}}, // [511]:0b111111111111111111111111111110 - {.value = 256, .offset = {0, 0}}, // [512]:0b111111111111111111111111111111 -}; - -/* ***************************************************************************** - - - - - - Don't overwrite this - - - - - -***************************************************************************** */ - -#endif /* H_HPACK_H */ diff --git a/ext/iodine/http.c b/ext/iodine/http.c deleted file mode 100644 index 4bec2745..00000000 --- a/ext/iodine/http.c +++ /dev/null @@ -1,2743 +0,0 @@ -/* -Copyright: Boaz Segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifndef HAVE_TM_TM_ZONE -#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ - defined(__DragonFly__) || defined(__bsdi__) || defined(__ultrix) || \ - (defined(__APPLE__) && defined(__MACH__)) || \ - (defined(__sun) && !defined(__SVR4)) -/* Known BSD systems */ -#define HAVE_TM_TM_ZONE 1 -#elif defined(__GLIBC__) && defined(_BSD_SOURCE) -/* GNU systems with _BSD_SOURCE */ -#define HAVE_TM_TM_ZONE 1 -#else -#define HAVE_TM_TM_ZONE 0 -#endif -#endif - -#ifndef __MINGW32__ -/* ***************************************************************************** -SSL/TLS patch -***************************************************************************** */ - -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - */ -void __attribute__((weak)) -fio_tls_alpn_add(void *tls, const char *protocol_name, - void (*callback)(intptr_t uuid, void *udata_connection, - void *udata_tls), - void *udata_tls, void (*on_cleanup)(void *udata_tls)) { - FIO_LOG_FATAL("HTTP SSL/TLS required but unavailable!"); - exit(-1); - (void)tls; - (void)protocol_name; - (void)callback; - (void)on_cleanup; - (void)udata_tls; -} -#pragma weak fio_tls_alpn_add -#endif - -/* ***************************************************************************** -Small Helpers -***************************************************************************** */ -static inline int hex2byte(uint8_t *dest, const uint8_t *source); - -static inline void add_content_length(http_s *r, uintptr_t length) { - static uint64_t cl_hash = 0; - if (!cl_hash) - cl_hash = fiobj_hash_string("content-length", 14); - if (!fiobj_hash_get2(r->private_data.out_headers, cl_hash)) { - fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_LENGTH, - fiobj_num_new(length)); - } -} -static inline void add_content_type(http_s *r) { - static uint64_t ct_hash = 0; - if (!ct_hash) - ct_hash = fiobj_hash_string("content-type", 12); - if (!fiobj_hash_get2(r->private_data.out_headers, ct_hash)) { - fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_TYPE, - http_mimetype_find2(r->path)); - } -} - -static FIOBJ current_date; -static time_t last_date_added; -static fio_lock_i date_lock; -static inline void add_date(http_s *r) { - static uint64_t date_hash = 0; - if (!date_hash) - date_hash = fiobj_hash_string("date", 4); - - if (fio_last_tick().tv_sec > last_date_added) { - fio_lock(&date_lock); - if (fio_last_tick().tv_sec > last_date_added) { /* retest inside lock */ - /* 32 chars are ok for a while, but http_time2str below has a buffer sized - * 48 chars and does a memcpy ... */ - FIOBJ tmp = fiobj_str_buf(32); - FIOBJ old = current_date; - fiobj_str_resize( - tmp, http_time2str(fiobj_obj2cstr(tmp).data, fio_last_tick().tv_sec)); - last_date_added = fio_last_tick().tv_sec; - current_date = tmp; - fiobj_free(old); - } - fio_unlock(&date_lock); - } - - if (!fiobj_hash_get2(r->private_data.out_headers, date_hash)) { - fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_DATE, - fiobj_dup(current_date)); - } -} - -struct header_writer_s { - FIOBJ dest; - FIOBJ name; - FIOBJ value; -}; - -static int write_header(FIOBJ o, void *w_) { - struct header_writer_s *w = w_; - if (!o) - return 0; - if (fiobj_hash_key_in_loop()) { - w->name = fiobj_hash_key_in_loop(); - } - if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) { - fiobj_each1(o, 0, write_header, w); - return 0; - } - fio_str_info_s name = fiobj_obj2cstr(w->name); - fio_str_info_s str = fiobj_obj2cstr(o); - if (!str.data) - return 0; - fiobj_str_write(w->dest, name.data, name.len); - fiobj_str_write(w->dest, ":", 1); - fiobj_str_write(w->dest, str.data, str.len); - fiobj_str_write(w->dest, "\r\n", 2); - return 0; -} - -static char invalid_cookie_name_char[256]; - -static char invalid_cookie_value_char[256]; -/* ***************************************************************************** -The Request / Response type and functions -***************************************************************************** */ -static const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - -/** - * Sets a response header, taking ownership of the value object, but NOT the - * name object (so name objects could be reused in future responses). - * - * Returns -1 on error and 0 on success. - */ -int http_set_header(http_s *r, FIOBJ name, FIOBJ value) { - if (HTTP_INVALID_HANDLE(r) || !name) { - fiobj_free(value); - return -1; - } - set_header_add(r->private_data.out_headers, name, value); - return 0; -} -/** - * Sets a response header. - * - * Returns -1 on error and 0 on success. - */ -int http_set_header2(http_s *r, fio_str_info_s n, fio_str_info_s v) { - if (HTTP_INVALID_HANDLE(r) || !n.data || !n.len || (v.data && !v.len)) - return -1; - FIOBJ tmp = fiobj_str_new(n.data, n.len); - int ret = http_set_header(r, tmp, fiobj_str_new(v.data, v.len)); - fiobj_free(tmp); - return ret; -} - -/** - * Sets a response cookie, taking ownership of the value object, but NOT the - * name object (so name objects could be reused in future responses). - * - * Returns -1 on error and 0 on success. - */ -#undef http_set_cookie -int http_set_cookie(http_s *h, http_cookie_args_s cookie) { -#if DEBUG - FIO_ASSERT(h, "Can't set cookie for NULL HTTP handler!"); -#endif - if (HTTP_INVALID_HANDLE(h) || cookie.name_len >= 32768 || - cookie.value_len >= 131072) { - return -1; - } - - static int warn_illegal = 0; - - /* write name and value while auto-correcting encoding issues */ - size_t capa = cookie.name_len + cookie.value_len + 128; - size_t len = 0; - FIOBJ c = fiobj_str_buf(capa); - fio_str_info_s t = fiobj_obj2cstr(c); - -#define copy_cookie_ch(ch_var) \ - if (invalid_cookie_##ch_var##_char[(uint8_t)cookie.ch_var[tmp]]) { \ - if (!warn_illegal) { \ - ++warn_illegal; \ - FIO_LOG_WARNING("illegal char 0x%.2x in cookie " #ch_var " (in %s)\n" \ - " automatic %% encoding applied", \ - cookie.ch_var[tmp], cookie.ch_var); \ - } \ - t.data[len++] = '%'; \ - t.data[len++] = hex_chars[((uint8_t)cookie.ch_var[tmp] >> 4) & 0x0F]; \ - t.data[len++] = hex_chars[(uint8_t)cookie.ch_var[tmp] & 0x0F]; \ - } else { \ - t.data[len++] = cookie.ch_var[tmp]; \ - } \ - tmp += 1; \ - if (capa <= len + 3) { \ - capa += 32; \ - fiobj_str_capa_assert(c, capa); \ - t = fiobj_obj2cstr(c); \ - } - - if (cookie.name) { - size_t tmp = 0; - if (cookie.name_len) { - while (tmp < cookie.name_len) { - copy_cookie_ch(name); - } - } else { - while (cookie.name[tmp]) { - copy_cookie_ch(name); - } - } - } - t.data[len++] = '='; - if (cookie.value) { - size_t tmp = 0; - if (cookie.value_len) { - while (tmp < cookie.value_len) { - copy_cookie_ch(value); - } - } else { - while (cookie.value[tmp]) { - copy_cookie_ch(value); - } - } - } else - cookie.max_age = -1; - - if (http_settings(h) && http_settings(h)->is_client) { - if (!cookie.value) { - fiobj_free(c); - return -1; - } - set_header_add(h->private_data.out_headers, HTTP_HEADER_COOKIE, c); - return 0; - } - - t.data[len++] = ';'; - t.data[len++] = ' '; - - if (h->status_str || !h->status) { /* on first request status == 0 */ - static uint64_t cookie_hash; - if (!cookie_hash) - cookie_hash = fiobj_hash_string("cookie", 6); - FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, cookie_hash); - if (!tmp) { - set_header_add(h->private_data.out_headers, HTTP_HEADER_COOKIE, c); - } else { - fiobj_str_join(tmp, c); - fiobj_free(c); - } - return 0; - } - - if (capa <= len + 40) { - capa = len + 40; - fiobj_str_capa_assert(c, capa); - t = fiobj_obj2cstr(c); - } - if (cookie.max_age) { - memcpy(t.data + len, "Max-Age=", 8); - len += 8; - len += fio_ltoa(t.data + len, cookie.max_age, 10); - t.data[len++] = ';'; - t.data[len++] = ' '; - } - fiobj_str_resize(c, len); - - if (cookie.domain && cookie.domain_len) { - fiobj_str_write(c, "domain=", 7); - fiobj_str_write(c, cookie.domain, cookie.domain_len); - fiobj_str_write(c, ";", 1); - t.data[len++] = ' '; - } - if (cookie.path && cookie.path_len) { - fiobj_str_write(c, "path=", 5); - fiobj_str_write(c, cookie.path, cookie.path_len); - fiobj_str_write(c, ";", 1); - t.data[len++] = ' '; - } - if (cookie.http_only) { - fiobj_str_write(c, "HttpOnly;", 9); - } - if (cookie.secure) { - fiobj_str_write(c, "secure;", 7); - } - set_header_add(h->private_data.out_headers, HTTP_HEADER_SET_COOKIE, c); - return 0; -} -#define http_set_cookie(http__req__, ...) \ - http_set_cookie((http__req__), (http_cookie_args_s){__VA_ARGS__}) - -/** - * Sends the response headers and body. - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -int http_send_body(http_s *r, void *data, uintptr_t length) { - if (HTTP_INVALID_HANDLE(r)) - return -1; - if (!length || !data) { - http_finish(r); - return 0; - } - add_content_length(r, length); - // add_content_type(r); - add_date(r); - return ((http_vtable_s *)r->private_data.vtbl) - ->http_send_body(r, data, length); -} -/** - * Sends the response headers and the specified file (the response's body). - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -int http_sendfile(http_s *r, int fd, uintptr_t length, uintptr_t offset) { - if (HTTP_INVALID_HANDLE(r)) { - close(fd); - return -1; - }; - add_content_length(r, length); - add_content_type(r); - add_date(r); - return ((http_vtable_s *)r->private_data.vtbl) - ->http_sendfile(r, fd, length, offset); -} - -static inline int http_test_encoded_path(const char *mem, size_t len) { - const char *pos = NULL; - const char *end = mem + len; - while (mem < end && (pos = memchr(mem, '/', (size_t)len))) { - len = end - pos; - mem = pos + 1; - if (pos[1] == '/') - return -1; - if (len > 3 && pos[1] == '.' && pos[2] == '.' && pos[3] == '/') - return -1; - } - return 0; -} - -/** - * Sends the response headers and the specified file (the response's body). - * - * Returns -1 eton error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len, - const char *encoded, size_t encoded_len) { - if (HTTP_INVALID_HANDLE(h)) - return -1; - struct stat file_data = {.st_size = 0}; - static uint64_t accept_enc_hash = 0; - if (!accept_enc_hash) - accept_enc_hash = fiobj_hash_string("accept-encoding", 15); - static uint64_t range_hash = 0; - if (!range_hash) - range_hash = fiobj_hash_string("range", 5); - - /* create filename string */ - FIOBJ filename = fiobj_str_tmp(); - if (prefix && prefix_len) { - /* start with prefix path */ - if (encoded && prefix[prefix_len - 1] == '/' && encoded[0] == '/') - --prefix_len; - fiobj_str_capa_assert(filename, prefix_len + encoded_len + 4); - fiobj_str_write(filename, prefix, prefix_len); - } - { - /* decode filename in cases where it's URL encoded */ - fio_str_info_s tmp = fiobj_obj2cstr(filename); - if (encoded) { - char *pos = (char *)encoded; - const char *end = encoded + encoded_len; - while (pos < end) { - if (*pos == '%') { - // decode hex value (this is a percent encoded value). - if (hex2byte((uint8_t *)tmp.data + tmp.len, (uint8_t *)pos + 1)) - return -1; - tmp.len++; - pos += 3; - } else - tmp.data[tmp.len++] = *(pos++); - } - tmp.data[tmp.len] = 0; - fiobj_str_resize(filename, tmp.len); - /* test for path manipulations after decoding */ - if (http_test_encoded_path(tmp.data + prefix_len, tmp.len - prefix_len)) - return -1; - } - if (tmp.data[tmp.len - 1] == '/') - fiobj_str_write(filename, "index.html", 10); - } - /* test for file existance */ - - int file = -1; - uint8_t is_gz = 0; - - fio_str_info_s s = fiobj_obj2cstr(filename); - { - FIOBJ tmp = fiobj_hash_get2(h->headers, accept_enc_hash); - if (!tmp) - goto no_gzip_support; - fio_str_info_s ac_str = fiobj_obj2cstr(tmp); - if (!ac_str.data || !strstr(ac_str.data, "gzip")) - goto no_gzip_support; - if (s.data[s.len - 3] != '.' || s.data[s.len - 2] != 'g' || - s.data[s.len - 1] != 'z') { - fiobj_str_write(filename, ".gz", 3); - s = fiobj_obj2cstr(filename); - if (!stat(s.data, &file_data) && - (S_ISREG(file_data.st_mode) || S_ISLNK(file_data.st_mode))) { - is_gz = 1; - goto found_file; - } - fiobj_str_resize(filename, s.len - 3); - } - } -no_gzip_support: - if (stat(s.data, &file_data) || - !(S_ISREG(file_data.st_mode) || S_ISLNK(file_data.st_mode))) - return -1; -found_file: - /* set last-modified */ - { - FIOBJ tmp = fiobj_str_buf(32); - fiobj_str_resize( - tmp, http_time2str(fiobj_obj2cstr(tmp).data, file_data.st_mtime)); - http_set_header(h, HTTP_HEADER_LAST_MODIFIED, tmp); - } - /* set cache-control */ - http_set_header(h, HTTP_HEADER_CACHE_CONTROL, fiobj_dup(HTTP_HVALUE_MAX_AGE)); - /* set & test etag */ - uint64_t etag = (uint64_t)file_data.st_size; - etag ^= (uint64_t)file_data.st_mtime; - etag = fiobj_hash_string(&etag, sizeof(uint64_t)); - FIOBJ etag_str = fiobj_str_buf(32); - fiobj_str_resize(etag_str, - fio_base64_encode(fiobj_obj2cstr(etag_str).data, - (void *)&etag, sizeof(uint64_t))); - /* set */ - http_set_header(h, HTTP_HEADER_ETAG, etag_str); - /* test */ - { - static uint64_t none_match_hash = 0; - if (!none_match_hash) - none_match_hash = fiobj_hash_string("if-none-match", 13); - FIOBJ tmp2 = fiobj_hash_get2(h->headers, none_match_hash); - if (tmp2 && fiobj_iseq(tmp2, etag_str)) { - h->status = 304; - http_finish(h); - return 0; - } - } - /* handle range requests */ - int64_t offset = 0; - int64_t length = file_data.st_size; - { - static uint64_t ifrange_hash = 0; - if (!ifrange_hash) - ifrange_hash = fiobj_hash_string("if-range", 8); - FIOBJ tmp = fiobj_hash_get2(h->headers, ifrange_hash); - if (tmp && fiobj_iseq(tmp, etag_str)) { - fiobj_hash_delete2(h->headers, range_hash); - } else { - tmp = fiobj_hash_get2(h->headers, range_hash); - if (tmp) { - /* range ahead... */ - if (FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY)) - tmp = fiobj_ary_index(tmp, 0); - fio_str_info_s range = fiobj_obj2cstr(tmp); - if (!range.data || memcmp("bytes=", range.data, 6)) - goto open_file; - char *pos = range.data + 6; - int64_t start_at = 0, end_at = 0; - start_at = fio_atol(&pos); - if (start_at >= file_data.st_size) - goto open_file; - if (start_at >= 0) { - pos++; - end_at = fio_atol(&pos); - if (end_at <= 0) - goto open_file; - } - /* we ignore multimple ranges, only responding with the first range. */ - if (start_at < 0) { - if (0 - start_at < file_data.st_size) { - offset = file_data.st_size - start_at; - length = 0 - start_at; - } - } else if (end_at) { - offset = start_at; - length = end_at - start_at + 1; - if (length + start_at > file_data.st_size || length <= 0) - length = length - start_at; - } else { - offset = start_at; - length = length - start_at; - } - h->status = 206; - - { - FIOBJ cranges = fiobj_str_buf(1); - fiobj_str_printf(cranges, "bytes %lu-%lu/%lu", - (unsigned long)start_at, - (unsigned long)(start_at + length - 1), - (unsigned long)file_data.st_size); - http_set_header(h, HTTP_HEADER_CONTENT_RANGE, cranges); - } - http_set_header(h, HTTP_HEADER_ACCEPT_RANGES, - fiobj_dup(HTTP_HVALUE_BYTES)); - } - } - } - /* test for an OPTIONS request or invalid methods */ - s = fiobj_obj2cstr(h->method); - switch (s.len) { - case 7: - if (!strncasecmp("options", s.data, 7)) { - http_set_header2(h, (fio_str_info_s){.data = (char *)"allow", .len = 5}, - (fio_str_info_s){.data = (char *)"GET, HEAD", .len = 9}); - h->status = 200; - http_finish(h); - return 0; - } - break; - case 3: - if (!strncasecmp("get", s.data, 3)) - goto open_file; - break; - case 4: - if (!strncasecmp("head", s.data, 4)) { - http_set_header(h, HTTP_HEADER_CONTENT_LENGTH, fiobj_num_new(length)); - http_finish(h); - return 0; - } - goto open_file; - break; - } - http_send_error(h, 403); - return 0; -open_file: - s = fiobj_obj2cstr(filename); - file = open(s.data, O_RDONLY); - if (file == -1) { - FIO_LOG_ERROR("(HTTP) couldn't open file %s!\n", s.data); - perror(" "); - http_send_error(h, 500); - return 0; - } - { - FIOBJ tmp = 0; - uintptr_t pos = 0; - if (is_gz) { - http_set_header(h, HTTP_HEADER_CONTENT_ENCODING, - fiobj_dup(HTTP_HVALUE_GZIP)); - - pos = s.len - 4; - while (pos && s.data[pos] != '.') - pos--; - pos++; /* assuming, but that's fine. */ - tmp = http_mimetype_find(s.data + pos, s.len - pos - 3); - - } else { - pos = s.len - 1; - while (pos && s.data[pos] != '.') - pos--; - pos++; /* assuming, but that's fine. */ - tmp = http_mimetype_find(s.data + pos, s.len - pos); - } - if (tmp) - http_set_header(h, HTTP_HEADER_CONTENT_TYPE, tmp); - } - http_sendfile(h, file, length, offset); - return 0; -} - -/** - * Sends an HTTP error response. - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - * - * The `uuid` argument is optional and will be used only if the `http_s` - * argument is set to NULL. - */ -int http_send_error(http_s *r, size_t error) { - if (!r || !r->private_data.out_headers) { - return -1; - } - if (error < 100 || error >= 1000) - error = 500; - r->status = error; - char buffer[16]; - buffer[0] = '/'; - size_t pos = 1 + fio_ltoa(buffer + 1, error, 10); - buffer[pos++] = '.'; - buffer[pos++] = 'h'; - buffer[pos++] = 't'; - buffer[pos++] = 'm'; - buffer[pos++] = 'l'; - buffer[pos] = 0; - if (http_sendfile2(r, http2protocol(r)->settings->public_folder, - http2protocol(r)->settings->public_folder_length, buffer, - pos)) { - http_set_header(r, HTTP_HEADER_CONTENT_TYPE, - http_mimetype_find((char *)"txt", 3)); - fio_str_info_s t = http_status2str(error); - http_send_body(r, t.data, t.len); - } - return 0; -} - -/** - * Sends the response headers for a header only response. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -void http_finish(http_s *r) { - if (!r || !r->private_data.vtbl) { - return; - } - add_content_length(r, 0); - add_date(r); - ((http_vtable_s *)r->private_data.vtbl)->http_finish(r); -} -/** - * Pushes a data response when supported (HTTP/2 only). - * - * Returns -1 on error and 0 on success. - */ -int http_push_data(http_s *r, void *data, uintptr_t length, FIOBJ mime_type) { - if (!r || !(http_fio_protocol_s *)r->private_data.flag) - return -1; - return ((http_vtable_s *)r->private_data.vtbl) - ->http_push_data(r, data, length, mime_type); -} -/** - * Pushes a file response when supported (HTTP/2 only). - * - * If `mime_type` is NULL, an attempt at automatic detection using - * `filename` will be made. - * - * Returns -1 on error and 0 on success. - */ -int http_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) { - if (HTTP_INVALID_HANDLE(h)) - return -1; - return ((http_vtable_s *)h->private_data.vtbl) - ->http_push_file(h, filename, mime_type); -} - -/** - * Upgrades an HTTP/1.1 connection to a Websocket connection. - */ -#undef http_upgrade2ws -int http_upgrade2ws(http_s *h, websocket_settings_s args) { - if (!h) { - FIO_LOG_ERROR("`http_upgrade2ws` requires a valid `http_s` handle."); - goto error; - } - if (HTTP_INVALID_HANDLE(h)) - goto error; - return ((http_vtable_s *)h->private_data.vtbl)->http2websocket(h, &args); -error: - if (args.on_close) - args.on_close(-1, args.udata); - return -1; -} - -/* ***************************************************************************** -Pause / Resume -***************************************************************************** */ -struct http_pause_handle_s { - uintptr_t uuid; - http_s *h; - void *udata; - void (*task)(http_s *); - void (*fallback)(void *); -}; - -/** Returns the `udata` associated with the paused opaque handle */ -void *http_paused_udata_get(http_pause_handle_s *http) { return http->udata; } - -/** - * Sets the `udata` associated with the paused opaque handle, returning the - * old value. - */ -void *http_paused_udata_set(http_pause_handle_s *http, void *udata) { - void *old = http->udata; - http->udata = udata; - return old; -} - -/* perform the pause task outside of the connection's lock */ -static void http_pause_wrapper(void *h_, void *task_) { - void (*task)(void *h) = (void (*)(void *h))((uintptr_t)task_); - task(h_); -} - -/* perform the resume task within of the connection's lock */ -static void http_resume_wrapper(intptr_t uuid, fio_protocol_s *p_, void *arg) { - http_fio_protocol_s *p = (http_fio_protocol_s *)p_; - http_pause_handle_s *http = arg; - http_s *h = http->h; - h->udata = http->udata; - http_vtable_s *vtbl = (http_vtable_s *)h->private_data.vtbl; - if (http->task) - http->task(h); - vtbl->http_on_resume(h, p); - fio_free(http); - (void)uuid; -} - -/* perform the resume task fallback */ -static void http_resume_fallback_wrapper(intptr_t uuid, void *arg) { - http_pause_handle_s *http = arg; - if (http->fallback) - http->fallback(http->udata); - fio_free(http); - (void)uuid; -} - -/** - * Defers the request / response handling for later. - */ -void http_pause(http_s *h, void (*task)(http_pause_handle_s *http)) { - if (HTTP_INVALID_HANDLE(h)) { - return; - } - http_fio_protocol_s *p = (http_fio_protocol_s *)h->private_data.flag; - http_vtable_s *vtbl = (http_vtable_s *)h->private_data.vtbl; - http_pause_handle_s *http = fio_malloc(sizeof(*http)); - FIO_ASSERT_ALLOC(http); - *http = (http_pause_handle_s){ - .uuid = p->uuid, - .h = h, - .udata = h->udata, - }; - vtbl->http_on_pause(h, p); - fio_defer(http_pause_wrapper, http, (void *)((uintptr_t)task)); -} - -/** - * Defers the request / response handling for later. - */ -void http_resume(http_pause_handle_s *http, void (*task)(http_s *h), - void (*fallback)(void *udata)) { - if (!http) - return; - http->task = task; - http->fallback = fallback; - fio_defer_io_task(http->uuid, .udata = http, .type = FIO_PR_LOCK_TASK, - .task = http_resume_wrapper, - .fallback = http_resume_fallback_wrapper); -} - -/** - * Hijacks the socket away from the HTTP protocol and away from facil.io. - */ -intptr_t http_hijack(http_s *h, fio_str_info_s *leftover) { - if (!h) - return -1; - return ((http_vtable_s *)h->private_data.vtbl)->http_hijack(h, leftover); -} - -/* ***************************************************************************** -Setting the default settings and allocating a persistent copy -***************************************************************************** */ - -static void http_on_request_fallback(http_s *h) { http_send_error(h, 404); } -static void http_on_upgrade_fallback(http_s *h, char *p, size_t i) { - http_send_error(h, 400); - (void)p; - (void)i; -} -static void http_on_response_fallback(http_s *h) { http_send_error(h, 400); } - -static http_settings_s *http_settings_new(http_settings_s arg_settings) { - /* TODO: improve locality by unifying malloc to a single call */ - if (!arg_settings.on_request) - arg_settings.on_request = http_on_request_fallback; - if (!arg_settings.on_response) - arg_settings.on_response = http_on_response_fallback; - if (!arg_settings.on_upgrade) - arg_settings.on_upgrade = http_on_upgrade_fallback; - - if (!arg_settings.max_body_size) - arg_settings.max_body_size = HTTP_DEFAULT_BODY_LIMIT; - if (!arg_settings.timeout) - arg_settings.timeout = 40; - if (!arg_settings.ws_max_msg_size) - arg_settings.ws_max_msg_size = 262144; /** defaults to ~250KB */ - if (!arg_settings.ws_timeout) - arg_settings.ws_timeout = 40; /* defaults to 40 seconds */ - if (!arg_settings.max_header_size) - arg_settings.max_header_size = 32 * 1024; /* defaults to 32Kib seconds */ - if (arg_settings.max_clients <= 0 || - (size_t)(arg_settings.max_clients + HTTP_BUSY_UNLESS_HAS_FDS) > - fio_capa()) { - arg_settings.max_clients = fio_capa(); - if ((ssize_t)arg_settings.max_clients - HTTP_BUSY_UNLESS_HAS_FDS > 0) - arg_settings.max_clients -= HTTP_BUSY_UNLESS_HAS_FDS; - } - - http_settings_s *settings = malloc(sizeof(*settings) + sizeof(void *)); - *settings = arg_settings; - - if (settings->public_folder) { - settings->public_folder_length = strlen(settings->public_folder); - if (settings->public_folder[0] == '~' && - settings->public_folder[1] == '/' && getenv("HOME")) { - char *home = getenv("HOME"); - size_t home_len = strlen(home); - char *tmp = malloc(settings->public_folder_length + home_len + 1); - FIO_ASSERT_ALLOC(tmp); - memcpy(tmp, home, home_len); - if (home[home_len - 1] == '/') - --home_len; - memcpy(tmp + home_len, settings->public_folder + 1, - settings->public_folder_length); // copy also the NULL - settings->public_folder = tmp; - settings->public_folder_length = strlen(settings->public_folder); - } else { - settings->public_folder = malloc(settings->public_folder_length + 1); - FIO_ASSERT_ALLOC(settings->public_folder); - memcpy((void *)settings->public_folder, arg_settings.public_folder, - settings->public_folder_length); - ((uint8_t *)settings->public_folder)[settings->public_folder_length] = 0; - } - } - return settings; -} - -static void http_settings_free(http_settings_s *s) { - free((void *)s->public_folder); - free(s); -} -/* ***************************************************************************** -Listening to HTTP connections -***************************************************************************** */ - -static uint8_t fio_http_at_capa = 0; - -static void http_on_server_protocol_http1(intptr_t uuid, void *set, - void *ignr_) { - if ((unsigned int)fio_uuid2fd(uuid) >= - ((http_settings_s *)set)->max_clients) { - if (fio_uuid2fd(uuid) != -1) { - if (!fio_http_at_capa) - FIO_LOG_WARNING("HTTP server at capacity"); - fio_http_at_capa = 1; - http_send_error2(503, uuid, set); - fio_close(uuid); - } - return; - } - fio_http_at_capa = 0; - fio_protocol_s *pr = http1_new(uuid, set, NULL, 0); - if (!pr) - fio_close(uuid); - else - fio_timeout_set(uuid, ((http_settings_s *)set)->timeout); - (void)ignr_; -} - -static void http_on_open(intptr_t uuid, void *set) { - http_on_server_protocol_http1(uuid, set, NULL); -} - -static void http_on_finish(intptr_t uuid, void *set) { - http_settings_s *settings = set; - - if (settings->on_finish) - settings->on_finish(settings); - - http_settings_free(settings); - (void)uuid; -} - -/** - * Listens to HTTP connections at the specified `port`. - * - * Leave as NULL to ignore IP binding. - * - * Returns -1 on error and 0 on success. - */ -#undef http_listen -intptr_t http_listen(const char *port, const char *binding, - struct http_settings_s arg_settings) { - if (arg_settings.on_request == NULL) { - FIO_LOG_ERROR("http_listen requires the .on_request parameter " - "to be set\n"); - kill(0, SIGINT); - exit(11); - } - - http_settings_s *settings = http_settings_new(arg_settings); - settings->is_client = 0; -#ifndef __MINGW32__ - if (settings->tls) { - fio_tls_alpn_add(settings->tls, "http/1.1", http_on_server_protocol_http1, - NULL, NULL); - } -#endif - - return fio_listen(.port = port, .address = binding, .tls = arg_settings.tls, - .on_finish = http_on_finish, .on_open = http_on_open, - .udata = settings); -} -/** Listens to HTTP connections at the specified `port` and `binding`. */ -#define http_listen(port, binding, ...) \ - http_listen((port), (binding), (struct http_settings_s)(__VA_ARGS__)) - -/** - * Returns the settings used to setup the connection. - * - * Returns NULL on error (i.e., connection was lost). - */ -struct http_settings_s *http_settings(http_s *r) { - return ((http_fio_protocol_s *)r->private_data.flag)->settings; -} - -/** - * Returns the direct address of the connected peer (likely an intermediary). - */ -fio_str_info_s http_peer_addr(http_s *h) { - return fio_peer_addr(((http_fio_protocol_s *)h->private_data.flag)->uuid); -} - -/* ***************************************************************************** -HTTP client connections -***************************************************************************** */ - -static void http_on_close_client(intptr_t uuid, fio_protocol_s *protocol) { - http_fio_protocol_s *p = (http_fio_protocol_s *)protocol; - http_settings_s *set = p->settings; - void (**original)(intptr_t, fio_protocol_s *) = - (void (**)(intptr_t, fio_protocol_s *))(set + 1); - if (set->on_finish) - set->on_finish(set); - - original[0](uuid, protocol); - http_settings_free(set); -} - -static void http_on_open_client_perform(http_settings_s *set) { - http_s *h = set->udata; - set->on_response(h); -} -static void http_on_open_client_http1(intptr_t uuid, void *set_, - void *ignore_) { - http_settings_s *set = set_; - http_s *h = set->udata; - fio_timeout_set(uuid, set->timeout); - fio_protocol_s *pr = http1_new(uuid, set, NULL, 0); - if (!pr) { - fio_close(uuid); - return; - } - { /* store the original on_close at the end of the struct, we wrap it. */ - void (**original)(intptr_t, fio_protocol_s *) = - (void (**)(intptr_t, fio_protocol_s *))(set + 1); - *original = pr->on_close; - pr->on_close = http_on_close_client; - } - h->private_data.flag = (uintptr_t)pr; - h->private_data.vtbl = http1_vtable(); - http_on_open_client_perform(set); - (void)ignore_; -} - -static void http_on_open_client(intptr_t uuid, void *set_) { - http_on_open_client_http1(uuid, set_, NULL); -} - -static void http_on_client_failed(intptr_t uuid, void *set_) { - http_settings_s *set = set_; - http_s *h = set->udata; - set->udata = h->udata; - http_s_destroy(h, 0); - fio_free(h); - if (set->on_finish) - set->on_finish(set); - http_settings_free(set); - (void)uuid; -} - -intptr_t http_connect__(void); /* sublime text marker */ -/** - * Connects to an HTTP server as a client. - * - * Upon a successful connection, the `on_response` callback is called with an - * empty `http_s*` handler (status == 0). Use the same API to set it's content - * and send the request to the server. The next`on_response` will contain the - * response. - * - * `address` should contain a full URL style address for the server. i.e.: - * "http:/www.example.com:8080/" - * - * Returns -1 on error and 0 on success. the `on_finish` callback is always - * called. - */ -intptr_t http_connect FIO_IGNORE_MACRO(const char *url, - const char *unix_address, - struct http_settings_s arg_settings) { - if (!arg_settings.on_response && !arg_settings.on_upgrade) { - FIO_LOG_ERROR("http_connect requires either an on_response " - " or an on_upgrade callback.\n"); - errno = EINVAL; - goto on_error; - } - size_t len = 0, h_len = 0; - char *a = NULL, *p = NULL, *host = NULL; - uint8_t is_websocket = 0; - uint8_t is_secure = 0; - FIOBJ path = FIOBJ_INVALID; - if (!url && !unix_address) { - FIO_LOG_ERROR("http_connect requires a valid address."); - errno = EINVAL; - goto on_error; - } - if (url) { - fio_url_s u = fio_url_parse(url, strlen(url)); - if (u.scheme.data && - (u.scheme.len == 2 || (u.scheme.len == 3 && u.scheme.data[2] == 's')) && - u.scheme.data[0] == 'w' && u.scheme.data[1] == 's') { - is_websocket = 1; - is_secure = (u.scheme.len == 3); - } else if (u.scheme.data && - (u.scheme.len == 4 || - (u.scheme.len == 5 && u.scheme.data[4] == 's')) && - u.scheme.data[0] == 'h' && u.scheme.data[1] == 't' && - u.scheme.data[2] == 't' && u.scheme.data[3] == 'p') { - is_secure = (u.scheme.len == 5); - } - if (is_secure && !arg_settings.tls) { - FIO_LOG_ERROR("Secure connections (%.*s) require a TLS object.", - (int)u.scheme.len, u.scheme.data); - errno = EINVAL; - goto on_error; - } - if (u.path.data) { - path = fiobj_str_new( - u.path.data, strlen(u.path.data)); /* copy query and target as well */ - } - if (unix_address) { - a = (char *)unix_address; - h_len = len = strlen(a); - host = a; - } else { - if (!u.host.data) { - FIO_LOG_ERROR("http_connect requires a valid address."); - errno = EINVAL; - goto on_error; - } - /***** no more error handling, since memory is allocated *****/ - /* copy address */ - a = fio_malloc(u.host.len + 1); - memcpy(a, u.host.data, u.host.len); - a[u.host.len] = 0; - len = u.host.len; - /* copy port */ - if (u.port.data) { - p = fio_malloc(u.port.len + 1); - memcpy(p, u.port.data, u.port.len); - p[u.port.len] = 0; - } else if (is_secure) { - p = fio_malloc(3 + 1); - memcpy(p, "443", 3); - p[3] = 0; - } else { - p = fio_malloc(2 + 1); - memcpy(p, "80", 2); - p[2] = 0; - } - } - if (u.host.data) { - host = u.host.data; - h_len = u.host.len; - } - } - - /* set settings */ - if (!arg_settings.timeout) - arg_settings.timeout = 30; - http_settings_s *settings = http_settings_new(arg_settings); - settings->is_client = 1; - // if (settings->tls) { - // fio_tls_alpn_add(settings->tls, "http/1.1", http_on_open_client_http1, - // NULL, NULL); - // } - - if (!arg_settings.ws_timeout) - settings->ws_timeout = 0; /* allow server to dictate timeout */ - if (!arg_settings.timeout) - settings->timeout = 0; /* allow server to dictate timeout */ - http_s *h = fio_malloc(sizeof(*h)); - FIO_ASSERT(h, "HTTP Client handler allocation failed"); - http_s_new(h, 0, http1_vtable()); - h->udata = arg_settings.udata; - h->status = 0; - h->path = path; - settings->udata = h; - settings->tls = arg_settings.tls; - if (host) - http_set_header2(h, (fio_str_info_s){.data = (char *)"host", .len = 4}, - (fio_str_info_s){.data = host, .len = h_len}); - intptr_t ret; - if (is_websocket) { - /* force HTTP/1.1 */ - ret = fio_connect(.address = a, .port = p, .on_fail = http_on_client_failed, - .on_connect = http_on_open_client, .udata = settings, - .tls = arg_settings.tls); - (void)0; - } else { - /* Allow for any HTTP version */ - ret = fio_connect(.address = a, .port = p, .on_fail = http_on_client_failed, - .on_connect = http_on_open_client, .udata = settings, - .tls = arg_settings.tls); - (void)0; - } - if (a != unix_address) - fio_free(a); - fio_free(p); - return ret; -on_error: - if (arg_settings.on_finish) - arg_settings.on_finish(&arg_settings); - return -1; -} - -/* ***************************************************************************** -HTTP Websocket Connect -***************************************************************************** */ - -#undef http_upgrade2ws -static void on_websocket_http_connected(http_s *h) { - websocket_settings_s *s = h->udata; - h->udata = http_settings(h)->udata = NULL; - if (!h->path) { - FIO_LOG_WARNING("(websocket client) path not specified in " - "address, assuming root!"); - h->path = fiobj_str_new("/", 1); - } - http_upgrade2ws(h, *s); - fio_free(s); -} - -static void on_websocket_http_connection_finished(http_settings_s *settings) { - websocket_settings_s *s = settings->udata; - if (s) { - if (s->on_close) - s->on_close(0, s->udata); - fio_free(s); - } -} - -#undef websocket_connect -int websocket_connect(const char *address, websocket_settings_s settings) { - websocket_settings_s *s = fio_malloc(sizeof(*s)); - FIO_ASSERT_ALLOC(s); - *s = settings; - return http_connect(address, NULL, .on_request = on_websocket_http_connected, - .on_response = on_websocket_http_connected, - .on_finish = on_websocket_http_connection_finished, - .udata = s); -} -#define websocket_connect(address, ...) \ - websocket_connect((address), (websocket_settings_s){__VA_ARGS__}) - -/* ***************************************************************************** -EventSource Support (SSE) - -Note: - -* `http_sse_subscribe` and `http_sse_unsubscribe` are implemented in the - http_internal logical unit. - -***************************************************************************** */ - -static inline void http_sse_copy2str(FIOBJ dest, char *prefix, size_t pre_len, - fio_str_info_s data) { - if (!data.len) - return; - const char *stop = data.data + data.len; - while (data.len) { - fiobj_str_write(dest, prefix, pre_len); - char *pos = data.data; - while (pos < stop && *pos != '\n' && *pos != '\r') - ++pos; - fiobj_str_write(dest, data.data, (uintptr_t)(pos - data.data)); - fiobj_str_write(dest, "\r\n", 2); - if (*pos == '\r') - ++pos; - if (*pos == '\n') - ++pos; - data.len -= (uintptr_t)(pos - data.data); - data.data = pos; - } -} - -/** The on message callback. the `*msg` pointer is to a temporary object. */ -static void http_sse_on_message(fio_msg_s *msg) { - http_sse_internal_s *sse = msg->udata1; - struct http_sse_subscribe_args *args = msg->udata2; - /* perform a callback */ - fio_protocol_s *pr = fio_protocol_try_lock(sse->uuid, FIO_PR_LOCK_TASK); - if (!pr) - goto postpone; - args->on_message(&sse->sse, msg->channel, msg->msg, args->udata); - fio_protocol_unlock(pr, FIO_PR_LOCK_TASK); - return; -postpone: - if (errno == EBADF) - return; - fio_message_defer(msg); - return; -} - -static void http_sse_on_message__direct(http_sse_s *sse, fio_str_info_s channel, - fio_str_info_s msg, void *udata) { - http_sse_write(sse, .data = msg); - (void)udata; - (void)channel; -} -/** An optional callback for when a subscription is fully canceled. */ -static void http_sse_on_unsubscribe(void *sse_, void *args_) { - http_sse_internal_s *sse = sse_; - struct http_sse_subscribe_args *args = args_; - if (args->on_unsubscribe) - args->on_unsubscribe(args->udata); - fio_free(args); - http_sse_try_free(sse); -} - -/** This macro allows easy access to the `http_sse_subscribe` function. */ -#undef http_sse_subscribe -/** - * Subscribes to a channel. See {struct http_sse_subscribe_args} for possible - * arguments. - * - * Returns a subscription ID on success and 0 on failure. - * - * All subscriptions are automatically revoked once the connection is closed. - * - * If the connections subscribes to the same channel more than once, messages - * will be merged. However, another subscription ID will be assigned, and two - * calls to {http_sse_unsubscribe} will be required in order to unregister from - * the channel. - */ -uintptr_t http_sse_subscribe(http_sse_s *sse_, - struct http_sse_subscribe_args args) { - http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_); - if (sse->uuid == -1) - return 0; - if (!args.on_message) - args.on_message = http_sse_on_message__direct; - struct http_sse_subscribe_args *udata = fio_malloc(sizeof(*udata)); - FIO_ASSERT_ALLOC(udata); - *udata = args; - - fio_atomic_add(&sse->ref, 1); - subscription_s *sub = - fio_subscribe(.channel = args.channel, .on_message = http_sse_on_message, - .on_unsubscribe = http_sse_on_unsubscribe, .udata1 = sse, - .udata2 = udata, .match = args.match); - if (!sub) - return 0; - - fio_lock(&sse->lock); - fio_ls_s *pos = fio_ls_push(&sse->subscriptions, sub); - fio_unlock(&sse->lock); - return (uintptr_t)pos; -} - -/** - * Cancels a subscription and invalidates the subscription object. - */ -void http_sse_unsubscribe(http_sse_s *sse_, uintptr_t subscription) { - if (!sse_ || !subscription) - return; - http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_); - subscription_s *sub = (subscription_s *)((fio_ls_s *)subscription)->obj; - fio_lock(&sse->lock); - fio_ls_remove((fio_ls_s *)subscription); - fio_unlock(&sse->lock); - fio_unsubscribe(sub); -} - -#undef http_upgrade2sse -/** - * Upgrades an HTTP connection to an EventSource (SSE) connection. - * - * Thie `http_s` handle will be invalid after this call. - * - * On HTTP/1.1 connections, this will preclude future requests using the same - * connection. - */ -int http_upgrade2sse(http_s *h, http_sse_s sse) { - if (HTTP_INVALID_HANDLE(h)) { - if (sse.on_close) - sse.on_close(&sse); - return -1; - } - return ((http_vtable_s *)h->private_data.vtbl)->http_upgrade2sse(h, &sse); -} - -/** - * Sets the ping interval for SSE connections. - */ -void http_sse_set_timout(http_sse_s *sse_, uint8_t timeout) { - if (!sse_) - return; - http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_); - fio_timeout_set(sse->uuid, timeout); -} - -#undef http_sse_write -/** - * Writes data to an EventSource (SSE) connection. - */ -int http_sse_write(http_sse_s *sse, struct http_sse_write_args args) { - if (!sse || !(args.id.len + args.data.len + args.event.len) || - fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid)) - return -1; - FIOBJ buf; - { - /* best guess at data length, ignoring missing fields and multiline data */ - const size_t total = 4 + args.id.len + 2 + 7 + args.event.len + 2 + 6 + - args.data.len + 2 + 7 + 10 + 4; - buf = fiobj_str_buf(total); - } - http_sse_copy2str(buf, (char *)"id: ", 4, args.id); - http_sse_copy2str(buf, (char *)"event: ", 7, args.event); - if (args.retry) { - FIOBJ i = fiobj_num_new(args.retry); - fiobj_str_write(buf, (char *)"retry: ", 7); - fiobj_str_join(buf, i); - fiobj_free(i); - } - http_sse_copy2str(buf, (char *)"data: ", 6, args.data); - fiobj_str_write(buf, "\r\n", 2); - return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse) - ->vtable->http_sse_write(sse, buf); -} - -/** - * Get the connection's UUID (for fio_defer and similar use cases). - */ -intptr_t http_sse2uuid(http_sse_s *sse) { - if (!sse || - fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid)) - return -1; - return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid; -} - -/** - * Closes an EventSource (SSE) connection. - */ -int http_sse_close(http_sse_s *sse) { - if (!sse || - fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid)) - return -1; - return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse) - ->vtable->http_sse_close(sse); -} - -/** - * Duplicates an SSE handle by reference, remember to http_sse_free. - * - * Returns the same object (increases a reference count, no allocation is made). - */ -http_sse_s *http_sse_dup(http_sse_s *sse) { - fio_atomic_add(&FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->ref, 1); - return sse; -} - -/** - * Frees an SSE handle by reference (decreases the reference count). - */ -void http_sse_free(http_sse_s *sse) { - http_sse_try_free(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)); -} - -/* ***************************************************************************** -HTTP GET and POST parsing helpers -***************************************************************************** */ - -/** URL decodes a string, returning a `FIOBJ`. */ -static inline FIOBJ http_urlstr2fiobj(char *s, size_t len) { - FIOBJ o = fiobj_str_buf(len); - ssize_t l = http_decode_url(fiobj_obj2cstr(o).data, s, len); - if (l < 0) { - fiobj_free(o); - return fiobj_str_new(NULL, 0); /* empty string */ - } - fiobj_str_resize(o, (size_t)l); - return o; -} - -/** converts a string into a `FIOBJ`. */ -static inline FIOBJ http_str2fiobj(char *s, size_t len, uint8_t encoded) { - switch (len) { - case 0: - return fiobj_str_new(NULL, 0); /* empty string */ - case 4: - if (!strncasecmp(s, "true", 4)) - return fiobj_true(); - if (!strncasecmp(s, "null", 4)) - return fiobj_null(); - break; - case 5: - if (!strncasecmp(s, "false", 5)) - return fiobj_false(); - } - { - char *end = s; - const uint64_t tmp = fio_atol(&end); - if (end == s + len) - return fiobj_num_new(tmp); - } - { - char *end = s; - const double tmp = fio_atof(&end); - if (end == s + len) - return fiobj_float_new(tmp); - } - if (encoded) - return http_urlstr2fiobj(s, len); - return fiobj_str_new(s, len); -} - -/** Parses the query part of an HTTP request/response. Uses `http_add2hash`. */ -void http_parse_query(http_s *h) { - if (!h->query) - return; - if (!h->params) - h->params = fiobj_hash_new(); - fio_str_info_s q = fiobj_obj2cstr(h->query); - do { - char *cut = memchr(q.data, '&', q.len); - if (!cut) - cut = q.data + q.len; - char *cut2 = memchr(q.data, '=', (cut - q.data)); - if (cut2) { - /* we only add named elements... */ - http_add2hash(h->params, q.data, (size_t)(cut2 - q.data), (cut2 + 1), - (size_t)(cut - (cut2 + 1)), 1); - } - if (cut[0] == '&') { - /* protecting against some ...less informed... clients */ - if (cut[1] == 'a' && cut[2] == 'm' && cut[3] == 'p' && cut[4] == ';') - cut += 5; - else - cut += 1; - } - q.len -= (uintptr_t)(cut - q.data); - q.data = cut; - } while (q.len); -} - -static inline void http_parse_cookies_cookie_str(FIOBJ dest, FIOBJ str, - uint8_t is_url_encoded) { - if (!FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)) - return; - fio_str_info_s s = fiobj_obj2cstr(str); - while (s.len) { - if (s.data[0] == ' ') { - ++s.data; - --s.len; - continue; - } - char *cut = memchr(s.data, '=', s.len); - if (!cut) - cut = s.data; - char *cut2 = memchr(cut, ';', s.len - (cut - s.data)); - if (!cut2) - cut2 = s.data + s.len; - http_add2hash(dest, s.data, cut - s.data, cut + 1, (cut2 - (cut + 1)), - is_url_encoded); - if ((size_t)((cut2 + 1) - s.data) > s.len) - s.len = 0; - else - s.len -= ((cut2 + 1) - s.data); - s.data = cut2 + 1; - } -} -static inline void http_parse_cookies_setcookie_str(FIOBJ dest, FIOBJ str, - uint8_t is_url_encoded) { - if (!FIOBJ_TYPE_IS(str, FIOBJ_T_STRING)) - return; - fio_str_info_s s = fiobj_obj2cstr(str); - char *cut = memchr(s.data, '=', s.len); - if (!cut) - cut = s.data; - char *cut2 = memchr(cut, ';', s.len - (cut - s.data)); - if (!cut2) - cut2 = s.data + s.len; - if (cut2 > cut) - http_add2hash(dest, s.data, cut - s.data, cut + 1, (cut2 - (cut + 1)), - is_url_encoded); -} - -/** Parses any Cookie / Set-Cookie headers, using the `http_add2hash` scheme. */ -void http_parse_cookies(http_s *h, uint8_t is_url_encoded) { - if (!h->headers) - return; - if (h->cookies && fiobj_hash_count(h->cookies)) { - FIO_LOG_WARNING("(http) attempting to parse cookies more than once."); - return; - } - static uint64_t setcookie_header_hash; - if (!setcookie_header_hash) - setcookie_header_hash = fiobj_obj2hash(HTTP_HEADER_SET_COOKIE); - FIOBJ c = fiobj_hash_get2(h->headers, fiobj_obj2hash(HTTP_HEADER_COOKIE)); - if (c) { - if (!h->cookies) - h->cookies = fiobj_hash_new(); - if (FIOBJ_TYPE_IS(c, FIOBJ_T_ARRAY)) { - /* Array of Strings */ - size_t count = fiobj_ary_count(c); - for (size_t i = 0; i < count; ++i) { - http_parse_cookies_cookie_str( - h->cookies, fiobj_ary_index(c, (int64_t)i), is_url_encoded); - } - } else { - /* single string */ - http_parse_cookies_cookie_str(h->cookies, c, is_url_encoded); - } - } - c = fiobj_hash_get2(h->headers, fiobj_obj2hash(HTTP_HEADER_SET_COOKIE)); - if (c) { - if (!h->cookies) - h->cookies = fiobj_hash_new(); - if (FIOBJ_TYPE_IS(c, FIOBJ_T_ARRAY)) { - /* Array of Strings */ - size_t count = fiobj_ary_count(c); - for (size_t i = 0; i < count; ++i) { - http_parse_cookies_setcookie_str( - h->cookies, fiobj_ary_index(c, (int64_t)i), is_url_encoded); - } - } else { - /* single string */ - http_parse_cookies_setcookie_str(h->cookies, c, is_url_encoded); - } - } -} - -/** - * Adds a named parameter to the hash, resolving nesting references. - * - * i.e.: - * - * * "name[]" references a nested Array (nested in the Hash). - * * "name[key]" references a nested Hash. - * * "name[][key]" references a nested Hash within an array. Hash keys will be - * unique (repeating a key advances the hash). - * * These rules can be nested (i.e. "name[][key1][][key2]...") - * * "name[][]" is an error (there's no way for the parser to analyze - * dimensions) - * - * Note: names can't begin with "[" or end with "]" as these are reserved - * characters. - */ -int http_add2hash2(FIOBJ dest, char *name, size_t name_len, FIOBJ val, - uint8_t encoded) { - if (!name) - goto error; - FIOBJ nested_ary = FIOBJ_INVALID; - char *cut1; - /* we can't start with an empty object name */ - while (name_len && name[0] == '[') { - --name_len; - ++name; - } - if (!name_len) { - /* an empty name is an error */ - goto error; - } - uint32_t nesting = ((uint32_t)~0); -rebase: - /* test for nesting level limit (limit at 32) */ - if (!nesting) - goto error; - /* start clearing away bits. */ - nesting >>= 1; - /* since we might be rebasing, notice that "name" might be "name]" */ - cut1 = memchr(name, '[', name_len); - if (!cut1) - goto place_in_hash; - /* simple case "name=" (the "=" was already removed) */ - if (cut1 == name) { - /* an empty name is an error */ - goto error; - } - if (cut1 + 1 == name + name_len) { - /* we have name[= ... autocorrect */ - name_len -= 1; - goto place_in_array; - } - - if (cut1[1] == ']') { - /* Nested Array "name[]..." */ - - /* Test for name[]= */ - if ((cut1 + 2) == name + name_len) { - name_len -= 2; - goto place_in_array; - } - - /* Test for a nested Array format error */ - if (cut1[2] != '[' || cut1[3] == ']') { /* error, we can't parse this */ - goto error; - } - - /* we have name[][key...= */ - - /* ensure array exists and it's an array + set nested_ary */ - const size_t len = ((cut1[-1] == ']') ? (size_t)((cut1 - 1) - name) - : (size_t)(cut1 - name)); - const uint64_t hash = - fiobj_hash_string(name, len); /* hash the current name */ - nested_ary = fiobj_hash_get2(dest, hash); - if (!nested_ary) { - /* create a new nested array */ - FIOBJ key = - encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len); - nested_ary = fiobj_ary_new2(4); - fiobj_hash_set(dest, key, nested_ary); - fiobj_free(key); - } else if (!FIOBJ_TYPE_IS(nested_ary, FIOBJ_T_ARRAY)) { - /* convert existing object to an array - auto error correction */ - FIOBJ key = - encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len); - FIOBJ tmp = fiobj_ary_new2(4); - fiobj_ary_push(tmp, nested_ary); - nested_ary = tmp; - fiobj_hash_set(dest, key, nested_ary); - fiobj_free(key); - } - - /* test if last object in the array is a hash - create hash if not */ - dest = fiobj_ary_index(nested_ary, -1); - if (!dest || !FIOBJ_TYPE_IS(dest, FIOBJ_T_HASH)) { - dest = fiobj_hash_new(); - fiobj_ary_push(nested_ary, dest); - } - - /* rebase `name` to `key` and restart. */ - cut1 += 3; /* consume "[][" */ - name_len -= (size_t)(cut1 - name); - name = cut1; - goto rebase; - - } else { - /* we have name[key]... */ - const size_t len = ((cut1[-1] == ']') ? (size_t)((cut1 - 1) - name) - : (size_t)(cut1 - name)); - const uint64_t hash = - fiobj_hash_string(name, len); /* hash the current name */ - FIOBJ tmp = fiobj_hash_get2(dest, hash); - if (!tmp) { - /* hash doesn't exist, create it */ - FIOBJ key = - encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len); - tmp = fiobj_hash_new(); - fiobj_hash_set(dest, key, tmp); - fiobj_free(key); - } else if (!FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH)) { - /* type error, referencing an existing object that isn't a Hash */ - goto error; - } - dest = tmp; - /* no rollback is possible once we enter the new nesting level... */ - nested_ary = FIOBJ_INVALID; - /* rebase `name` to `key` and restart. */ - cut1 += 1; /* consume "[" */ - name_len -= (size_t)(cut1 - name); - name = cut1; - goto rebase; - } - -place_in_hash: - if (name[name_len - 1] == ']') - --name_len; - { - FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len) - : fiobj_str_new(name, name_len); - FIOBJ old = fiobj_hash_replace(dest, key, val); - if (old) { - if (nested_ary) { - fiobj_hash_replace(dest, key, old); - old = fiobj_hash_new(); - fiobj_hash_set(old, key, val); - fiobj_ary_push(nested_ary, old); - } else { - if (!FIOBJ_TYPE_IS(old, FIOBJ_T_ARRAY)) { - FIOBJ tmp = fiobj_ary_new2(4); - fiobj_ary_push(tmp, old); - old = tmp; - } - fiobj_ary_push(old, val); - fiobj_hash_replace(dest, key, old); - } - } - fiobj_free(key); - } - return 0; - -place_in_array: - if (name[name_len - 1] == ']') - --name_len; - { - uint64_t hash = fiobj_hash_string(name, name_len); - FIOBJ ary = fiobj_hash_get2(dest, hash); - if (!ary) { - FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len) - : fiobj_str_new(name, name_len); - ary = fiobj_ary_new2(4); - fiobj_hash_set(dest, key, ary); - fiobj_free(key); - } else if (!FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)) { - FIOBJ tmp = fiobj_ary_new2(4); - fiobj_ary_push(tmp, ary); - ary = tmp; - FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len) - : fiobj_str_new(name, name_len); - fiobj_hash_replace(dest, key, ary); - fiobj_free(key); - } - fiobj_ary_push(ary, val); - } - return 0; -error: - fiobj_free(val); - errno = EOPNOTSUPP; - return -1; -} - -/** - * Adds a named parameter to the hash, resolving nesting references. - * - * i.e.: - * - * * "name[]" references a nested Array (nested in the Hash). - * * "name[key]" references a nested Hash. - * * "name[][key]" references a nested Hash within an array. Hash keys will be - * unique (repeating a key advances the hash). - * * These rules can be nested (i.e. "name[][key1][][key2]...") - * * "name[][]" is an error (there's no way for the parser to analyze - * dimensions) - * - * Note: names can't begin with "[" or end with "]" as these are reserved - * characters. - */ -int http_add2hash(FIOBJ dest, char *name, size_t name_len, char *value, - size_t value_len, uint8_t encoded) { - return http_add2hash2(dest, name, name_len, - http_str2fiobj(value, value_len, encoded), encoded); -} - -/* ***************************************************************************** -HTTP Body Parsing -***************************************************************************** */ -#include - -typedef struct { - http_mime_parser_s p; - http_s *h; - fio_str_info_s buffer; - size_t pos; - size_t partial_offset; - size_t partial_length; - FIOBJ partial_name; -} http_fio_mime_s; - -#define http_mime_parser2fio(parser) ((http_fio_mime_s *)(parser)) - -/** Called when all the data is available at once. */ -static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name, - size_t name_len, void *filename, - size_t filename_len, void *mimetype, - size_t mimetype_len, void *value, - size_t value_len) { - if (!filename_len) { - http_add2hash(http_mime_parser2fio(parser)->h->params, name, name_len, - value, value_len, 0); - return; - } - FIOBJ n = fiobj_str_new(name, name_len); - fiobj_str_write(n, "[data]", 6); - fio_str_info_s tmp = fiobj_obj2cstr(n); - http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, - value, value_len, 0); - fiobj_str_resize(n, name_len); - fiobj_str_write(n, "[name]", 6); - tmp = fiobj_obj2cstr(n); - http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, - filename, filename_len, 0); - if (mimetype_len) { - fiobj_str_resize(n, name_len); - fiobj_str_write(n, "[type]", 6); - tmp = fiobj_obj2cstr(n); - http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, - mimetype, mimetype_len, 0); - } - fiobj_free(n); -} - -/** Called when the data didn't fit in the buffer. Data will be streamed. */ -static void http_mime_parser_on_partial_start( - http_mime_parser_s *parser, void *name, size_t name_len, void *filename, - size_t filename_len, void *mimetype, size_t mimetype_len) { - http_mime_parser2fio(parser)->partial_length = 0; - http_mime_parser2fio(parser)->partial_offset = 0; - http_mime_parser2fio(parser)->partial_name = fiobj_str_new(name, name_len); - - if (!filename) - return; - - fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[type]", 6); - fio_str_info_s tmp = - fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name); - http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, - mimetype, mimetype_len, 0); - - fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len); - fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[name]", 6); - tmp = fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name); - http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, - filename, filename_len, 0); - - fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len); - fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[data]", 6); -} - -/** Called when partial data is available. */ -static void http_mime_parser_on_partial_data(http_mime_parser_s *parser, - void *value, size_t value_len) { - if (!http_mime_parser2fio(parser)->partial_offset) - http_mime_parser2fio(parser)->partial_offset = - http_mime_parser2fio(parser)->pos + - ((uintptr_t)value - - (uintptr_t)http_mime_parser2fio(parser)->buffer.data); - http_mime_parser2fio(parser)->partial_length += value_len; - (void)value; -} - -/** Called when the partial data is complete. */ -static void http_mime_parser_on_partial_end(http_mime_parser_s *parser) { - - fio_str_info_s tmp = - fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name); - FIOBJ o = FIOBJ_INVALID; - if (!http_mime_parser2fio(parser)->partial_length) - return; - if (http_mime_parser2fio(parser)->partial_length < 42) { - /* short data gets a new object */ - o = fiobj_str_new(http_mime_parser2fio(parser)->buffer.data + - http_mime_parser2fio(parser)->partial_offset, - http_mime_parser2fio(parser)->partial_length); - } else { - /* longer data gets a reference object (memory collision concerns) */ - o = fiobj_data_slice(http_mime_parser2fio(parser)->h->body, - http_mime_parser2fio(parser)->partial_offset, - http_mime_parser2fio(parser)->partial_length); - } - http_add2hash2(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, o, - 0); - fiobj_free(http_mime_parser2fio(parser)->partial_name); - http_mime_parser2fio(parser)->partial_name = FIOBJ_INVALID; - http_mime_parser2fio(parser)->partial_offset = 0; -} - -/** - * Called when URL decoding is required. - * - * Should support inplace decoding (`dest == encoded`). - * - * Should return the length of the decoded string. - */ -static inline size_t http_mime_decode_url(char *dest, const char *encoded, - size_t length) { - return http_decode_url(dest, encoded, length); -} - -/** - * Attempts to decode the request's body. - * - * Supported Types include: - * * application/x-www-form-urlencoded - * * application/json - * * multipart/form-data - */ -int http_parse_body(http_s *h) { - static uint64_t content_type_hash; - if (!h->body) - return -1; - if (!content_type_hash) - content_type_hash = fiobj_hash_string("content-type", 12); - FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash); - fio_str_info_s content_type = fiobj_obj2cstr(ct); - if (content_type.len < 16) - return -1; - if (content_type.len >= 33 && - !strncasecmp("application/x-www-form-urlencoded", content_type.data, - 33)) { - if (!h->params) - h->params = fiobj_hash_new(); - FIOBJ tmp = h->query; - h->query = h->body; - http_parse_query(h); - h->query = tmp; - return 0; - } - if (content_type.len >= 16 && - !strncasecmp("application/json", content_type.data, 16)) { - content_type = fiobj_obj2cstr(h->body); - if (h->params) - return -1; - if (fiobj_json2obj(&h->params, content_type.data, content_type.len) == 0) - return -1; - if (FIOBJ_TYPE_IS(h->params, FIOBJ_T_HASH)) - return 0; - FIOBJ tmp = h->params; - FIOBJ key = fiobj_str_new("JSON", 4); - h->params = fiobj_hash_new2(4); - fiobj_hash_set(h->params, key, tmp); - fiobj_free(key); - return 0; - } - - http_fio_mime_s p = {.h = h}; - if (http_mime_parser_init(&p.p, content_type.data, content_type.len)) - return -1; - if (!h->params) - h->params = fiobj_hash_new(); - - do { - size_t cons = http_mime_parse(&p.p, p.buffer.data, p.buffer.len); - p.pos += cons; - p.buffer = fiobj_data_pread(h->body, p.pos, 4096); - } while (p.buffer.data && !p.p.done && !p.p.error); - fiobj_free(p.partial_name); - p.partial_name = FIOBJ_INVALID; - return 0; -} - -/* ***************************************************************************** -HTTP Helper functions that could be used globally -***************************************************************************** */ - -/** - * Returns a String object representing the unparsed HTTP request (HTTP - * version is capped at HTTP/1.1). Mostly usable for proxy usage and - * debugging. - */ -FIOBJ http_req2str(http_s *h) { - if (HTTP_INVALID_HANDLE(h) || !fiobj_hash_count(h->headers)) - return FIOBJ_INVALID; - - struct header_writer_s w; - w.dest = fiobj_str_buf(0); - if (h->status_str) { - fiobj_str_join(w.dest, h->version); - fiobj_str_write(w.dest, " ", 1); - fiobj_str_join(w.dest, fiobj_num_tmp(h->status)); - fiobj_str_write(w.dest, " ", 1); - fiobj_str_join(w.dest, h->status_str); - fiobj_str_write(w.dest, "\r\n", 2); - } else { - fiobj_str_join(w.dest, h->method); - fiobj_str_write(w.dest, " ", 1); - fiobj_str_join(w.dest, h->path); - if (h->query) { - fiobj_str_write(w.dest, "?", 1); - fiobj_str_join(w.dest, h->query); - } - { - fio_str_info_s t = fiobj_obj2cstr(h->version); - if (t.len < 6 || t.data[5] != '1') - fiobj_str_write(w.dest, " HTTP/1.1\r\n", 10); - else { - fiobj_str_write(w.dest, " ", 1); - fiobj_str_join(w.dest, h->version); - fiobj_str_write(w.dest, "\r\n", 2); - } - } - } - - fiobj_each1(h->headers, 0, write_header, &w); - fiobj_str_write(w.dest, "\r\n", 2); - if (h->body) { - // fiobj_data_seek(h->body, 0); - // fio_str_info_s t = fiobj_data_read(h->body, 0); - // fiobj_str_write(w.dest, t.data, t.len); - fiobj_str_join(w.dest, h->body); - } - return w.dest; -} - -void http_write_log(http_s *h) { - FIOBJ l = fiobj_str_buf(128); - - intptr_t bytes_sent = fiobj_obj2num(fiobj_hash_get2( - h->private_data.out_headers, fiobj_obj2hash(HTTP_HEADER_CONTENT_LENGTH))); - - struct timespec start, end; - clock_gettime(CLOCK_REALTIME, &end); - start = h->received_at; - - { - // TODO Guess IP address from headers (forwarded) where possible - fio_str_info_s peer = fio_peer_addr(http2protocol(h)->uuid); - fiobj_str_write(l, peer.data, peer.len); - } - fio_str_info_s buff = fiobj_obj2cstr(l); - - if (buff.len == 0) { - memcpy(buff.data, "[unknown]", 9); - buff.len = 9; - } - memcpy(buff.data + buff.len, " - - [", 6); - buff.len += 6; - fiobj_str_resize(l, buff.len); - { - FIOBJ date; - fio_lock(&date_lock); - date = fiobj_dup(current_date); - fio_unlock(&date_lock); - fiobj_str_join(l, current_date); - fiobj_free(date); - } - fiobj_str_write(l, "] \"", 3); - fiobj_str_join(l, h->method); - fiobj_str_write(l, " ", 1); - fiobj_str_join(l, h->path); - fiobj_str_write(l, " ", 1); - fiobj_str_join(l, h->version); - fiobj_str_write(l, "\" ", 2); - if (bytes_sent > 0) { - fiobj_str_write_i(l, h->status); - fiobj_str_write(l, " ", 1); - fiobj_str_write_i(l, bytes_sent); - fiobj_str_write(l, "b ", 2); - } else { - fiobj_str_join(l, fiobj_num_tmp(h->status)); - fiobj_str_write(l, " -- ", 4); - } - - bytes_sent = ((end.tv_sec - start.tv_sec) * 1000) + - ((end.tv_nsec - start.tv_nsec) / 1000000); - fiobj_str_write_i(l, bytes_sent); - fiobj_str_write(l, "ms\r\n", 4); - - buff = fiobj_obj2cstr(l); - fwrite(buff.data, 1, buff.len, stderr); - fiobj_free(l); -} - -/** -A faster (yet less localized) alternative to `gmtime_r`. - -See the libc `gmtime_r` documentation for details. - -Falls back to `gmtime_r` for dates before epoch. -*/ -struct tm *http_gmtime(time_t timer, struct tm *tm) { - ssize_t a, b; -#if HAVE_TM_TM_ZONE || defined(BSD) - *tm = (struct tm){ - .tm_isdst = 0, - .tm_zone = (char *)"UTC", - }; -#else - *tm = (struct tm){ - .tm_isdst = 0, - }; -#endif - - // convert seconds from epoch to days from epoch + extract data - if (timer >= 0) { - // for seconds up to weekdays, we reduce the reminder every step. - a = (ssize_t)timer; - b = a / 60; // b == time in minutes - tm->tm_sec = a - (b * 60); - a = b / 60; // b == time in hours - tm->tm_min = b - (a * 60); - b = a / 24; // b == time in days since epoch - tm->tm_hour = a - (b * 24); - // b == number of days since epoch - // day of epoch was a thursday. Add + 4 so sunday == 0... - tm->tm_wday = (b + 4) % 7; - } else { - // for seconds up to weekdays, we reduce the reminder every step. - a = (ssize_t)timer; - b = a / 60; // b == time in minutes - if (b * 60 != a) { - /* seconds passed */ - tm->tm_sec = (a - (b * 60)) + 60; - --b; - } else { - /* no seconds */ - tm->tm_sec = 0; - } - a = b / 60; // b == time in hours - if (a * 60 != b) { - /* minutes passed */ - tm->tm_min = (b - (a * 60)) + 60; - --a; - } else { - /* no minutes */ - tm->tm_min = 0; - } - b = a / 24; // b == time in days since epoch? - if (b * 24 != a) { - /* hours passed */ - tm->tm_hour = (a - (b * 24)) + 24; - --b; - } else { - /* no hours */ - tm->tm_hour = 0; - } - // day of epoch was a thursday. Add + 4 so sunday == 0... - tm->tm_wday = ((b - 3) % 7); - if (tm->tm_wday) - tm->tm_wday += 7; - /* b == days from epoch */ - } - - // at this point we can apply the algorithm described here: - // http://howardhinnant.github.io/date_algorithms.html#civil_from_days - // Credit to Howard Hinnant. - { - b += 719468L; // adjust to March 1st, 2000 (post leap of 400 year era) - // 146,097 = days in era (400 years) - const size_t era = (b >= 0 ? b : b - 146096) / 146097; - const uint32_t doe = (b - (era * 146097)); // day of era - const uint16_t yoe = - (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // year of era - a = yoe; - a += era * 400; // a == year number, assuming year starts on March 1st... - const uint16_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - const uint16_t mp = (5U * doy + 2) / 153; - const uint16_t d = doy - (153U * mp + 2) / 5 + 1; - const uint8_t m = mp + (mp < 10 ? 2 : -10); - a += (m <= 1); - tm->tm_year = a - 1900; // tm_year == years since 1900 - tm->tm_mon = m; - tm->tm_mday = d; - const uint8_t is_leap = (a % 4 == 0 && (a % 100 != 0 || a % 400 == 0)); - tm->tm_yday = (doy + (is_leap) + 28 + 31) % (365 + is_leap); - } - - return tm; -} - -static const char *DAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat"}; -static const char *MONTH_NAMES[] = {"Jan ", "Feb ", "Mar ", "Apr ", - "May ", "Jun ", "Jul ", "Aug ", - "Sep ", "Oct ", "Nov ", "Dec "}; -static const char *GMT_STR = "GMT"; - -size_t http_date2rfc7231(char *target, struct tm *tmbuf) { - /* note: day of month is always 2 digits */ - char *pos = target; - uint16_t tmp; - pos[0] = DAY_NAMES[tmbuf->tm_wday][0]; - pos[1] = DAY_NAMES[tmbuf->tm_wday][1]; - pos[2] = DAY_NAMES[tmbuf->tm_wday][2]; - pos[3] = ','; - pos[4] = ' '; - pos += 5; - tmp = tmbuf->tm_mday / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); - pos += 2; - *(pos++) = ' '; - pos[0] = MONTH_NAMES[tmbuf->tm_mon][0]; - pos[1] = MONTH_NAMES[tmbuf->tm_mon][1]; - pos[2] = MONTH_NAMES[tmbuf->tm_mon][2]; - pos[3] = ' '; - pos += 4; - // write year. - pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); - *(pos++) = ' '; - tmp = tmbuf->tm_hour / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); - pos[2] = ':'; - tmp = tmbuf->tm_min / 10; - pos[3] = '0' + tmp; - pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); - pos[5] = ':'; - tmp = tmbuf->tm_sec / 10; - pos[6] = '0' + tmp; - pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); - pos += 8; - pos[0] = ' '; - pos[1] = GMT_STR[0]; - pos[2] = GMT_STR[1]; - pos[3] = GMT_STR[2]; - pos[4] = 0; - pos += 4; - return pos - target; -} - -size_t http_date2str(char *target, struct tm *tmbuf); - -size_t http_date2rfc2822(char *target, struct tm *tmbuf) { - /* note: day of month is either 1 or 2 digits */ - char *pos = target; - uint16_t tmp; - pos[0] = DAY_NAMES[tmbuf->tm_wday][0]; - pos[1] = DAY_NAMES[tmbuf->tm_wday][1]; - pos[2] = DAY_NAMES[tmbuf->tm_wday][2]; - pos[3] = ','; - pos[4] = ' '; - pos += 5; - if (tmbuf->tm_mday < 10) { - *pos = '0' + tmbuf->tm_mday; - ++pos; - } else { - tmp = tmbuf->tm_mday / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); - pos += 2; - } - *(pos++) = '-'; - pos[0] = MONTH_NAMES[tmbuf->tm_mon][0]; - pos[1] = MONTH_NAMES[tmbuf->tm_mon][1]; - pos[2] = MONTH_NAMES[tmbuf->tm_mon][2]; - pos += 3; - *(pos++) = '-'; - // write year. - pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); - *(pos++) = ' '; - tmp = tmbuf->tm_hour / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); - pos[2] = ':'; - tmp = tmbuf->tm_min / 10; - pos[3] = '0' + tmp; - pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); - pos[5] = ':'; - tmp = tmbuf->tm_sec / 10; - pos[6] = '0' + tmp; - pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); - pos += 8; - pos[0] = ' '; - pos[1] = GMT_STR[0]; - pos[2] = GMT_STR[1]; - pos[3] = GMT_STR[2]; - pos[4] = 0; - pos += 4; - return pos - target; -} - -/* HTTP header format for Cookie ages */ -size_t http_date2rfc2109(char *target, struct tm *tmbuf) { - /* note: day of month is always 2 digits */ - char *pos = target; - uint16_t tmp; - pos[0] = DAY_NAMES[tmbuf->tm_wday][0]; - pos[1] = DAY_NAMES[tmbuf->tm_wday][1]; - pos[2] = DAY_NAMES[tmbuf->tm_wday][2]; - pos[3] = ','; - pos[4] = ' '; - pos += 5; - tmp = tmbuf->tm_mday / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); - pos += 2; - *(pos++) = ' '; - pos[0] = MONTH_NAMES[tmbuf->tm_mon][0]; - pos[1] = MONTH_NAMES[tmbuf->tm_mon][1]; - pos[2] = MONTH_NAMES[tmbuf->tm_mon][2]; - pos[3] = ' '; - pos += 4; - // write year. - pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); - *(pos++) = ' '; - tmp = tmbuf->tm_hour / 10; - pos[0] = '0' + tmp; - pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); - pos[2] = ':'; - tmp = tmbuf->tm_min / 10; - pos[3] = '0' + tmp; - pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); - pos[5] = ':'; - tmp = tmbuf->tm_sec / 10; - pos[6] = '0' + tmp; - pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); - pos += 8; - *pos++ = ' '; - *pos++ = '-'; - *pos++ = '0'; - *pos++ = '0'; - *pos++ = '0'; - *pos++ = '0'; - *pos = 0; - return pos - target; -} - -static pthread_key_t cached_tick_key; -static pthread_key_t cached_httpdate_key; -static pthread_key_t cached_len_key; -static pthread_once_t cached_once = PTHREAD_ONCE_INIT; -static void init_cached_key(void) { - pthread_key_create(&cached_tick_key, free); - pthread_key_create(&cached_httpdate_key, free); - pthread_key_create(&cached_len_key, free); -} -static void init_cached_key_ptr(void) { - time_t *cached_tick = malloc(sizeof(time_t)); - FIO_ASSERT_ALLOC(cached_tick); - memset(cached_tick, 0, sizeof(time_t)); - char *cached_httpdate = malloc(sizeof(char) * 48); - FIO_ASSERT_ALLOC(cached_tick); - memset(cached_httpdate, 0, 48); - size_t *cached_len = malloc(sizeof(size_t)); - *cached_len = 0; - FIO_ASSERT_ALLOC(cached_len); - pthread_setspecific(cached_tick_key, cached_tick); - pthread_setspecific(cached_httpdate_key, cached_httpdate); - pthread_setspecific(cached_len_key, cached_len); -} - -/** - * Prints Unix time to a HTTP time formatted string. - * - * This variation implements cached results for faster processing, at the - * price of a less accurate string. - */ -size_t http_time2str(char *target, const time_t t) { - /* pre-print time every 1 or 2 seconds or so. */ - pthread_once(&cached_once, init_cached_key); - char *cached_httpdate = pthread_getspecific(cached_httpdate_key); - if (!cached_httpdate) { - init_cached_key_ptr(); - cached_httpdate = pthread_getspecific(cached_httpdate_key); - } - time_t *cached_tick = pthread_getspecific(cached_tick_key); - size_t *cached_len = pthread_getspecific(cached_len_key); - time_t last_tick = fio_last_tick().tv_sec; - if ((t | 7) < last_tick) { - /* this is a custom time, not "now", pass through */ - struct tm tm; - http_gmtime(t, &tm); - return http_date2str(target, &tm); - } - if (last_tick > *cached_tick) { - struct tm tm; - *cached_tick = last_tick; /* refresh every second */ - http_gmtime(last_tick, &tm); - *cached_len = http_date2str(cached_httpdate, &tm); - } - memcpy(target, cached_httpdate, *cached_len); - return *cached_len; -} - -/* Credit to Jonathan Leffler for the idea of a unified conditional */ -#define hex_val(c) \ - (((c) >= '0' && (c) <= '9') ? ((c)-48) \ - : (((c) | 32) >= 'a' && ((c) | 32) <= 'f') ? (((c) | 32) - 87) \ - : ({ \ - return -1; \ - 0; \ - })) -static inline int hex2byte(uint8_t *dest, const uint8_t *source) { - if (source[0] >= '0' && source[0] <= '9') - *dest = (source[0] - '0'); - else if ((source[0] | 32) >= 'a' && (source[0] | 32) <= 'f') - *dest = (source[0] | 32) - ('a' - 10); - else - return -1; - *dest <<= 4; - if (source[1] >= '0' && source[1] <= '9') - *dest |= (source[1] - '0'); - else if ((source[1] | 32) >= 'a' && (source[1] | 32) <= 'f') - *dest |= (source[1] | 32) - ('a' - 10); - else - return -1; - return 0; -} - -ssize_t http_decode_url(char *dest, const char *url_data, size_t length) { - char *pos = dest; - const char *end = url_data + length; - while (url_data < end) { - if (*url_data == '+') { - // decode space - *(pos++) = ' '; - ++url_data; - } else if (*url_data == '%') { - // decode hex value - // this is a percent encoded value. - if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) - return -1; - pos++; - url_data += 3; - } else - *(pos++) = *(url_data++); - } - *pos = 0; - return pos - dest; -} - -ssize_t http_decode_url_unsafe(char *dest, const char *url_data) { - char *pos = dest; - while (*url_data) { - if (*url_data == '+') { - // decode space - *(pos++) = ' '; - ++url_data; - } else if (*url_data == '%') { - // decode hex value - // this is a percent encoded value. - if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) - return -1; - pos++; - url_data += 3; - } else - *(pos++) = *(url_data++); - } - *pos = 0; - return pos - dest; -} - -ssize_t http_decode_path(char *dest, const char *url_data, size_t length) { - char *pos = dest; - const char *end = url_data + length; - while (url_data < end) { - if (*url_data == '%') { - // decode hex value - // this is a percent encoded value. - if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) - return -1; - pos++; - url_data += 3; - } else - *(pos++) = *(url_data++); - } - *pos = 0; - return pos - dest; -} - -ssize_t http_decode_path_unsafe(char *dest, const char *url_data) { - char *pos = dest; - while (*url_data) { - if (*url_data == '%') { - // decode hex value - // this is a percent encoded value. - if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) - return -1; - pos++; - url_data += 3; - } else - *(pos++) = *(url_data++); - } - *pos = 0; - return pos - dest; -} - -/* ***************************************************************************** -Lookup Tables / functions -***************************************************************************** */ - -#define FIO_FORCE_MALLOC_TMP 1 /* use malloc for the mime registry */ -#define FIO_SET_NAME fio_mime_set -#define FIO_SET_OBJ_TYPE FIOBJ -#define FIO_SET_OBJ_COMPARE(o1, o2) (1) -#define FIO_SET_OBJ_COPY(dest, o) (dest) = fiobj_dup((o)) -#define FIO_SET_OBJ_DESTROY(o) fiobj_free((o)) - -#include - -static fio_mime_set_s fio_http_mime_types = FIO_SET_INIT; - -#define LONGEST_FILE_EXTENSION_LENGTH 15 - -/** Registers a Mime-Type to be associated with the file extension. */ -void http_mimetype_register(char *file_ext, size_t file_ext_len, - FIOBJ mime_type_str) { - uintptr_t hash = FIO_HASH_FN(file_ext, file_ext_len, 0, 0); - if (mime_type_str == FIOBJ_INVALID) { - fio_mime_set_remove(&fio_http_mime_types, hash, FIOBJ_INVALID, NULL); - } else { - FIOBJ old = FIOBJ_INVALID; - fio_mime_set_overwrite(&fio_http_mime_types, hash, mime_type_str, &old); - if (old != FIOBJ_INVALID) { - FIO_LOG_WARNING("mime-type collision: %.*s was %s, now %s", - (int)file_ext_len, file_ext, fiobj_obj2cstr(old).data, - fiobj_obj2cstr(mime_type_str).data); - fiobj_free(old); - } - fiobj_free(mime_type_str); /* move ownership to the registry */ - } -} - -/** Registers a Mime-Type to be associated with the file extension. */ -void http_mimetype_stats(void) { - FIO_LOG_DEBUG("HTTP MIME hash storage count/capa: %zu / %zu", - fio_mime_set_count(&fio_http_mime_types), - fio_mime_set_capa(&fio_http_mime_types)); -} - -/** - * Finds the mime-type associated with the file extension. - * Remember to call `fiobj_free`. - */ -FIOBJ http_mimetype_find(char *file_ext, size_t file_ext_len) { - uintptr_t hash = FIO_HASH_FN(file_ext, file_ext_len, 0, 0); - return fiobj_dup( - fio_mime_set_find(&fio_http_mime_types, hash, FIOBJ_INVALID)); -} - -static pthread_key_t buffer_key; -static pthread_once_t buffer_once = PTHREAD_ONCE_INIT; -static void init_buffer_key(void) { pthread_key_create(&buffer_key, free); } -static void init_buffer_ptr(void) { - char *buffer = malloc(sizeof(char) * (LONGEST_FILE_EXTENSION_LENGTH + 1)); - FIO_ASSERT_ALLOC(buffer); - memset(buffer, 0, sizeof(char) * (LONGEST_FILE_EXTENSION_LENGTH + 1)); - pthread_setspecific(buffer_key, buffer); -} -/** - * Finds the mime-type associated with the URL. - * Remember to call `fiobj_free`. - */ -FIOBJ http_mimetype_find2(FIOBJ url) { - pthread_once(&buffer_once, init_buffer_key); - char *buffer = pthread_getspecific(buffer_key); - if (!buffer) { - init_buffer_ptr(); - buffer = pthread_getspecific(buffer_key); - } - fio_str_info_s ext = {.data = NULL}; - FIOBJ mimetype; - if (!url) - goto finish; - fio_str_info_s tmp = fiobj_obj2cstr(url); - uint8_t steps = 1; - while (tmp.len > steps || steps >= LONGEST_FILE_EXTENSION_LENGTH) { - switch (tmp.data[tmp.len - steps]) { - case '.': - --steps; - if (steps) { - ext.len = steps; - ext.data = buffer; - buffer[steps] = 0; - for (size_t i = 1; i <= steps; ++i) { - buffer[steps - i] = tolower(tmp.data[tmp.len - i]); - } - } - /* fallthrough */ - case '/': - goto finish; - break; - } - ++steps; - } -finish: - mimetype = http_mimetype_find(ext.data, ext.len); - if (!mimetype) - mimetype = fiobj_dup(HTTP_HVALUE_CONTENT_TYPE_DEFAULT); - return mimetype; -} - -/** Clears the Mime-Type registry (it will be empty afterthis call). */ -void http_mimetype_clear(void) { - fio_mime_set_free(&fio_http_mime_types); - fiobj_free(current_date); - current_date = FIOBJ_INVALID; - last_date_added = 0; -} - -/** -* Create with Ruby using: - -a = [] -256.times {|i| a[i] = 1;} -('a'.ord..'z'.ord).each {|i| a[i] = 0;} -('A'.ord..'Z'.ord).each {|i| a[i] = 0;} -('0'.ord..'9'.ord).each {|i| a[i] = 0;} -"!#$%&'*+-.^_`|~".bytes.each {|i| a[i] = 0;} -p a; nil -"!#$%&'()*+-./:<=>?@[]^_`{|}~".bytes.each {|i| a[i] = 0;} # for values -p a; nil -*/ -static char invalid_cookie_name_char[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - -static char invalid_cookie_value_char[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - -// clang-format off -#define HTTP_SET_STATUS_STR(status, str) [status-100] = { .data = (char *)(#status " " str), .len = (sizeof( #status " " str) - 1) } -// clang-format on - -/** Returns the status as a C string struct */ -fio_str_info_s http_status2str(uintptr_t status) { - static const fio_str_info_s status2str[] = { - HTTP_SET_STATUS_STR(100, "Continue"), - HTTP_SET_STATUS_STR(101, "Switching Protocols"), - HTTP_SET_STATUS_STR(102, "Processing"), - HTTP_SET_STATUS_STR(103, "Early Hints"), - HTTP_SET_STATUS_STR(200, "OK"), - HTTP_SET_STATUS_STR(201, "Created"), - HTTP_SET_STATUS_STR(202, "Accepted"), - HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"), - HTTP_SET_STATUS_STR(204, "No Content"), - HTTP_SET_STATUS_STR(205, "Reset Content"), - HTTP_SET_STATUS_STR(206, "Partial Content"), - HTTP_SET_STATUS_STR(207, "Multi-Status"), - HTTP_SET_STATUS_STR(208, "Already Reported"), - HTTP_SET_STATUS_STR(226, "IM Used"), - HTTP_SET_STATUS_STR(300, "Multiple Choices"), - HTTP_SET_STATUS_STR(301, "Moved Permanently"), - HTTP_SET_STATUS_STR(302, "Found"), - HTTP_SET_STATUS_STR(303, "See Other"), - HTTP_SET_STATUS_STR(304, "Not Modified"), - HTTP_SET_STATUS_STR(305, "Use Proxy"), - HTTP_SET_STATUS_STR(306, "(Unused), "), - HTTP_SET_STATUS_STR(307, "Temporary Redirect"), - HTTP_SET_STATUS_STR(308, "Permanent Redirect"), - HTTP_SET_STATUS_STR(400, "Bad Request"), - HTTP_SET_STATUS_STR(403, "Forbidden"), - HTTP_SET_STATUS_STR(404, "Not Found"), - HTTP_SET_STATUS_STR(401, "Unauthorized"), - HTTP_SET_STATUS_STR(402, "Payment Required"), - HTTP_SET_STATUS_STR(405, "Method Not Allowed"), - HTTP_SET_STATUS_STR(406, "Not Acceptable"), - HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"), - HTTP_SET_STATUS_STR(408, "Request Timeout"), - HTTP_SET_STATUS_STR(409, "Conflict"), - HTTP_SET_STATUS_STR(410, "Gone"), - HTTP_SET_STATUS_STR(411, "Length Required"), - HTTP_SET_STATUS_STR(412, "Precondition Failed"), - HTTP_SET_STATUS_STR(413, "Payload Too Large"), - HTTP_SET_STATUS_STR(414, "URI Too Long"), - HTTP_SET_STATUS_STR(415, "Unsupported Media Type"), - HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"), - HTTP_SET_STATUS_STR(417, "Expectation Failed"), - HTTP_SET_STATUS_STR(418, "I'm a teapot"), - HTTP_SET_STATUS_STR(421, "Misdirected Request"), - HTTP_SET_STATUS_STR(422, "Unprocessable Entity"), - HTTP_SET_STATUS_STR(423, "Locked"), - HTTP_SET_STATUS_STR(424, "Failed Dependency"), - HTTP_SET_STATUS_STR(425, "Unassigned"), - HTTP_SET_STATUS_STR(426, "Upgrade Required"), - HTTP_SET_STATUS_STR(427, "Unassigned"), - HTTP_SET_STATUS_STR(428, "Precondition Required"), - HTTP_SET_STATUS_STR(429, "Too Many Requests"), - HTTP_SET_STATUS_STR(430, "Unassigned"), - HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"), - HTTP_SET_STATUS_STR(500, "Internal Server Error"), - HTTP_SET_STATUS_STR(501, "Not Implemented"), - HTTP_SET_STATUS_STR(502, "Bad Gateway"), - HTTP_SET_STATUS_STR(503, "Service Unavailable"), - HTTP_SET_STATUS_STR(504, "Gateway Timeout"), - HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"), - HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"), - HTTP_SET_STATUS_STR(507, "Insufficient Storage"), - HTTP_SET_STATUS_STR(508, "Loop Detected"), - HTTP_SET_STATUS_STR(509, "Unassigned"), - HTTP_SET_STATUS_STR(510, "Not Extended"), - HTTP_SET_STATUS_STR(511, "Network Authentication Required"), - }; - fio_str_info_s ret = (fio_str_info_s){.len = 0, .data = NULL}; - if (status >= 100 && - (status - 100) < sizeof(status2str) / sizeof(status2str[0])) - ret = status2str[status - 100]; - if (!ret.data) { - ret = status2str[400]; - } - return ret; -} -#undef HTTP_SET_STATUS_STR - -#if DEBUG -void http_tests(void) { - fprintf(stderr, "=== Testing HTTP helpers\n"); - FIOBJ html_mime = http_mimetype_find("html", 4); - FIO_ASSERT(html_mime, - "HTML mime-type not found! Mime-Type registry invalid!\n"); - fiobj_free(html_mime); -} -#endif diff --git a/ext/iodine/http.h b/ext/iodine/http.h deleted file mode 100644 index e13a8e20..00000000 --- a/ext/iodine/http.h +++ /dev/null @@ -1,1002 +0,0 @@ -#ifndef H_HTTP_H -/* -Copyright: Boaz Segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#define H_HTTP_H - -#include - -#include - -#include - -/* support C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/* ***************************************************************************** -Compile Time Settings -***************************************************************************** */ - -/** When a new connection is accepted, it will be immediately declined with a - * 503 service unavailable (server busy) response unless the following number of - * file descriptors is available.*/ -#ifndef HTTP_BUSY_UNLESS_HAS_FDS -#define HTTP_BUSY_UNLESS_HAS_FDS 64 -#endif - -#ifndef HTTP_DEFAULT_BODY_LIMIT -#define HTTP_DEFAULT_BODY_LIMIT (1024 * 1024 * 50) -#endif - -#ifndef HTTP_MAX_HEADER_COUNT -#define HTTP_MAX_HEADER_COUNT 128 -#endif - -#ifndef HTTP_MAX_HEADER_LENGTH -/** the default maximum length for a single header line */ -#define HTTP_MAX_HEADER_LENGTH 8192 -#endif - -#ifndef FIO_HTTP_EXACT_LOGGING -/** - * By default, facil.io logs the HTTP request cycle using a fuzzy starting point - * (a close enough timestamp). - * - * The fuzzy timestamp includes delays that aren't related to the HTTP request, - * sometimes including time that was spent waiting on the client. On the other - * hand, `FIO_HTTP_EXACT_LOGGING` excludes time that the client might have been - * waiting for facil.io to read data from the network. - * - * Due to the preference to err on the side of causion, fuzzy time-stamping is - * the default. - */ -#define FIO_HTTP_EXACT_LOGGING 0 -#endif - -/** the `http_listen settings, see details in the struct definition. */ -typedef struct http_settings_s http_settings_s; - -/* ***************************************************************************** -The Request / Response type and functions -***************************************************************************** */ - -/** - * A generic HTTP handle used for HTTP request/response data. - * - * The `http_s` data can only be accessed safely from within the `on_request` - * HTTP callback OR an `http_defer` callback. - */ -typedef struct { - /** the HTTP request's "head" starts with a private data used by facil.io */ - struct { - /** the function touting table - used by facil.io, don't use directly! */ - void *vtbl; - /** the connection's owner / uuid - used by facil.io, don't use directly! */ - uintptr_t flag; - /** The response headers, if they weren't sent. Don't access directly. */ - FIOBJ out_headers; - } private_data; - /** a time merker indicating when the request was received. */ - struct timespec received_at; - /** a String containing the method data (supports non-standard methods. */ - FIOBJ method; - /** The status string, for response objects (client mode response). */ - FIOBJ status_str; - /** The HTTP version string, if any. */ - FIOBJ version; - /** The status used for the response (or if the object is a response). - * - * When sending a request, the status should be set to 0. - */ - uintptr_t status; - /** The request path, if any. */ - FIOBJ path; - /** The request query, if any. */ - FIOBJ query; - /** a hash of general header data. When a header is set multiple times (such - * as cookie headers), an Array will be used instead of a String. */ - FIOBJ headers; - /** - * a placeholder for a hash of cookie data. - * the hash will be initialized when parsing the request. - */ - FIOBJ cookies; - /** - * a placeholder for a hash of request data. - * the hash will be initialized when parsing the request. - */ - FIOBJ params; - /** - * a reader for body data (might be a temporary file or a string or NULL). - * see fiobj_data.h for details. - */ - FIOBJ body; - /** an opaque user data pointer, to be used BEFORE calling `http_defer`. */ - void *udata; -} http_s; - -/** -* This is a helper for setting cookie data. - -This struct is used together with the `http_response_set_cookie`. i.e.: - - http_response_set_cookie(response, - .name = "my_cookie", - .value = "data" ); - -*/ -typedef struct { - /** The cookie's name (Symbol). */ - const char *name; - /** The cookie's value (leave blank to delete cookie). */ - const char *value; - /** The cookie's domain (optional). */ - const char *domain; - /** The cookie's path (optional). */ - const char *path; - /** The cookie name's size in bytes or a terminating NUL will be assumed.*/ - size_t name_len; - /** The cookie value's size in bytes or a terminating NUL will be assumed.*/ - size_t value_len; - /** The cookie domain's size in bytes or a terminating NUL will be assumed.*/ - size_t domain_len; - /** The cookie path's size in bytes or a terminating NULL will be assumed.*/ - size_t path_len; - /** Max Age (how long should the cookie persist), in seconds (0 == session).*/ - int max_age; - /** Limit cookie to secure connections.*/ - unsigned secure : 1; - /** Limit cookie to HTTP (intended to prevent javascript access/hijacking).*/ - unsigned http_only : 1; -} http_cookie_args_s; - -/** - * Sets a response header, taking ownership of the value object, but NOT the - * name object (so name objects could be reused in future responses). - * - * Returns -1 on error and 0 on success. - */ -int http_set_header(http_s *h, FIOBJ name, FIOBJ value); - -/** - * Sets a response header. - * - * Returns -1 on error and 0 on success. - */ -int http_set_header2(http_s *h, fio_str_info_s name, fio_str_info_s value); - -/** - * Sets a response cookie. - * - * Returns -1 on error and 0 on success. - * - * Note: Long cookie names and long cookie values will be considered a security - * violation and an error will be returned. It should be noted that most - * proxies and servers will refuse long cookie names or values and many impose - * total header lengths (including cookies) of ~8Kib. - */ -int http_set_cookie(http_s *h, http_cookie_args_s); -#define http_set_cookie(http___handle, ...) \ - http_set_cookie((http___handle), (http_cookie_args_s){__VA_ARGS__}) - -/** - * Sends the response headers and body. - * - * **Note**: The body is *copied* to the HTTP stream and it's memory should be - * freed by the calling function. - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -int http_send_body(http_s *h, void *data, uintptr_t length); - -/** - * Sends the response headers and the specified file (the response's body). - * - * The file is closed automatically. - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -int http_sendfile(http_s *h, int fd, uintptr_t length, uintptr_t offset); - -/** - * Sends the response headers and the specified file (the response's body). - * - * The `local` and `encoded` strings will be joined into a single string that - * represent the file name. Either or both of these strings can be empty. - * - * The `encoded` string will be URL decoded while the `local` string will used - * as is. - * - * Returns 0 on success. A success value WILL CONSUME the `http_s` handle (it - * will become invalid). - * - * Returns -1 on error (The `http_s` handle should still be used). - */ -int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len, - const char *encoded, size_t encoded_len); - -/** - * Sends an HTTP error response. - * - * Returns -1 on error and 0 on success. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - * - * The `uuid` and `settings` arguments are only required if the `http_s` handle - * is NULL. - */ -int http_send_error(http_s *h, size_t error_code); - -/** - * Sends the response headers for a header only response. - * - * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. - */ -void http_finish(http_s *h); - -/** - * Pushes a data response when supported (HTTP/2 only). - * - * Returns -1 on error and 0 on success. - */ -int http_push_data(http_s *h, void *data, uintptr_t length, FIOBJ mime_type); - -/** - * Pushes a file response when supported (HTTP/2 only). - * - * If `mime_type` is NULL, an attempt at automatic detection using `filename` - * will be made. - * - * Returns -1 on error and 0 on success. - */ -int http_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type); - -/* ***************************************************************************** -HTTP evented API (pause / resume HTTp handling) -***************************************************************************** */ - -typedef struct http_pause_handle_s http_pause_handle_s; -/** - * Pauses the request / response handling and INVALIDATES the current `http_s` - * handle (no `http` functions can be called). - * - * The `http_resume` function MUST be called (at some point) using the opaque - * `http` pointer given to the callback `task`. - * - * The opaque `http` pointer is only valid for a single call to `http_resume` - * and can't be used by any other `http` function (it's a different data type). - * - * Note: the current `http_s` handle will become invalid once this function is - * called and it's data might be deallocated, invalid or used by a different - * thread. - */ -void http_pause(http_s *h, void (*task)(http_pause_handle_s *http)); - -/** - * Resumes a request / response handling within a task and INVALIDATES the - * current `http_s` handle. - * - * The `task` MUST call one of the `http_send_*`, `http_finish`, or - * `http_pause`functions. - * - * The (optional) `fallback` will receive the opaque `udata` that was stored in - * the HTTP handle and can be used for cleanup. - * - * Note: `http_resume` can only be called after calling `http_pause` and - * entering it's task. - * - * Note: the current `http_s` handle will become invalid once this function is - * called and it's data might be deallocated, invalidated or used by a - * different thread. - */ -void http_resume(http_pause_handle_s *http, void (*task)(http_s *h), - void (*fallback)(void *udata)); - -/** Returns the `udata` associated with the paused opaque handle */ -void *http_paused_udata_get(http_pause_handle_s *http); - -/** - * Sets the `udata` associated with the paused opaque handle, returning the - * old value. - */ -void *http_paused_udata_set(http_pause_handle_s *http, void *udata); - -/* ***************************************************************************** -HTTP Connections - Listening / Connecting / Hijacking -***************************************************************************** */ - -/** The HTTP settings. */ -struct http_settings_s { - /** Callback for normal HTTP requests. */ - void (*on_request)(http_s *request); - /** - * Callback for Upgrade and EventSource (SSE) requests. - * - * SSE/EventSource requests set the `requested_protocol` string to `"sse"`. - */ - void (*on_upgrade)(http_s *request, char *requested_protocol, size_t len); - /** CLIENT REQUIRED: a callback for the HTTP response. */ - void (*on_response)(http_s *response); - /** (optional) the callback to be performed when the HTTP service closes. */ - void (*on_finish)(struct http_settings_s *settings); - /** Opaque user data. Facil.io will ignore this field, but you can use it. */ - void *udata; - /** - * A public folder for file transfers - allows to circumvent any application - * layer logic and simply serve static files. - * - * Supports automatic `gz` pre-compressed alternatives. - */ - const char *public_folder; - /** - * The length of the public_folder string. - */ - size_t public_folder_length; - /** - * The maximum number of bytes allowed for the request string (method, path, - * query), header names and fields. - * - * Defaults to 32Kib (which is about 4 times more than I would recommend). - * - * This reflects the total overall size. On HTTP/1.1, each header line (name + - * value pair) is also limitied to a hardcoded HTTP_MAX_HEADER_LENGTH bytes. - */ - size_t max_header_size; - /** - * The maximum size of an HTTP request's body (posting / downloading). - * - * Defaults to ~ 50Mb. - */ - size_t max_body_size; - /** - * The maximum number of clients that are allowed to connect concurrently. - * - * This value's default setting is usually for the best. - * - * The default value is computed according to the server's capacity, leaving - * some breathing room for other network and disk operations. - * - * Note: clients, by the nature of socket programming, are counted according - * to their internal file descriptor (`fd`) value. Open files and other - * sockets count towards a server's limit. - */ - intptr_t max_clients; - /** SSL/TLS support. */ - void *tls; - /** reserved for future use. */ - intptr_t reserved1; - /** reserved for future use. */ - intptr_t reserved2; - /** reserved for future use. */ - intptr_t reserved3; - /** - * The maximum websocket message size/buffer (in bytes) for Websocket - * connections. Defaults to ~250KB. - */ - size_t ws_max_msg_size; - /** - * An HTTP/1.x connection timeout. - * - * `http_listen` defaults to ~40s and `http_connect` defaults to ~30s. - * - * Note: the connection might be closed (by other side) before timeout occurs. - */ - uint8_t timeout; - /** - * Timeout for the websocket connections, a ping will be sent whenever the - * timeout is reached. Defaults to 40 seconds. - * - * Connections are only closed when a ping cannot be sent (the network layer - * fails). Pongs are ignored. - */ - uint8_t ws_timeout; - /** Logging flag - set to TRUE to log HTTP requests. */ - uint8_t log; - /** a read only flag set automatically to indicate the protocol's mode. */ - uint8_t is_client; -}; - -/** - * Listens to HTTP connections at the specified `port`. - * - * Leave as NULL to ignore IP binding. - * - * Returns -1 on error and the socket's uuid on success. - * - * the `on_finish` callback is always called. - */ -intptr_t http_listen(const char *port, const char *binding, - struct http_settings_s); -/** Listens to HTTP connections at the specified `port` and `binding`. */ -#define http_listen(port, binding, ...) \ - http_listen((port), (binding), (struct http_settings_s){__VA_ARGS__}) - -/** - * Connects to an HTTP server as a client. - * - * Upon a successful connection, the `on_response` callback is called with an - * empty `http_s*` handler (status == 0). Use the same API to set it's content - * and send the request to the server. The next`on_response` will contain the - * response. - * - * `address` should contain a full URL style address for the server. i.e.: - * - * "http:/www.example.com:8080/" - * - * If an `address` includes a path or query data, they will be automatically - * attached (both of them) to the HTTP handl'es `path` property. i.e. - * - * "http:/www.example.com:8080/my_path?foo=bar" - * // will result in: - * fiobj_obj2cstr(h->path).data; //=> "/my_path?foo=bar" - * - * To open a Websocket connection, it's possible to use the `ws` protocol - * signature. However, it would be better to use the `websocket_connect` - * function instead. - * - * Returns -1 on error and the socket's uuid on success. - * - * The `on_finish` callback is always called. - */ -intptr_t http_connect(const char *url, const char *unix_address, - struct http_settings_s); -#define http_connect(url, unix_address, ...) \ - http_connect((url), (unix_address), (struct http_settings_s){__VA_ARGS__}) - -/** - * Returns the settings used to setup the connection or NULL on error. - */ -struct http_settings_s *http_settings(http_s *h); - -/** - * Returns the direct address of the connected peer (likely an intermediary). - */ -fio_str_info_s http_peer_addr(http_s *h); - -/** - * Hijacks the socket away from the HTTP protocol and away from facil.io. - * - * It's possible to hijack the socket and than reconnect it to a new protocol - * object. - * - * It's possible to call `http_finish` immediately after calling `http_hijack` - * in order to send the outgoing headers. - * - * If any additional HTTP functions are called after the hijacking, the protocol - * object might attempt to continue reading data from the buffer. - * - * Returns the underlining socket connection's uuid. If `leftover` isn't NULL, - * it will be populated with any remaining data in the HTTP buffer (the data - * will be automatically deallocated, so copy the data when in need). - * - * WARNING: this isn't a good way to handle HTTP connections, especially as - * HTTP/2 enters the picture. - */ -intptr_t http_hijack(http_s *h, fio_str_info_s *leftover); - -/* ***************************************************************************** -Websocket Upgrade (Server and Client connection establishment) -***************************************************************************** */ - -/** - * The type for a Websocket handle, used to identify a Websocket connection. - * - * Similar to an `http_s` handle, it is only valid within the scope of the - * specific connection (the callbacks / tasks) and shouldn't be stored or - * accessed otherwise. - */ -typedef struct ws_s ws_s; - -/** - * This struct is used for the named arguments in the `http_upgrade2ws` - * function and macro. - */ -typedef struct { - /** - * The (optional) on_message callback will be called whenever a websocket - * message is received for this connection. - * - * The data received points to the websocket's message buffer and it will be - * overwritten once the function exits (it cannot be saved for later, but it - * can be copied). - */ - void (*on_message)(ws_s *ws, fio_str_info_s msg, uint8_t is_text); - /** - * The (optional) on_open callback will be called once the websocket - * connection is established and before is is registered with `facil`, so no - * `on_message` events are raised before `on_open` returns. - */ - void (*on_open)(ws_s *ws); - /** - * The (optional) on_ready callback will be after a the underlying socket's - * buffer changes it's state from full to empty. - * - * If the socket's buffer is never used, the callback is never called. - */ - void (*on_ready)(ws_s *ws); - /** - * The (optional) on_shutdown callback will be called if a websocket - * connection is still open while the server is shutting down (called before - * `on_close`). - */ - void (*on_shutdown)(ws_s *ws); - /** - * The (optional) on_close callback will be called once a websocket connection - * is terminated or failed to be established. - * - * The `uuid` is the connection's unique ID that can identify the Websocket. A - * value of `uuid == 0` indicates the Websocket connection wasn't established - * (an error occurred). - * - * The `udata` is the user data as set during the upgrade or using the - * `websocket_udata_set` function. - */ - void (*on_close)(intptr_t uuid, void *udata); - /** Opaque user data. */ - void *udata; -} websocket_settings_s; - -/** - * Upgrades an HTTP/1.1 connection to a Websocket connection. - * - * This function will end the HTTP stage of the connection and attempt to - * "upgrade" to a Websockets connection. - * - * Thie `http_s` handle will be invalid after this call and the `udata` will be - * set to the new Websocket `udata`. - * - * A client connection's `on_finish` callback will be called (since the HTTP - * stage has finished). - */ -int http_upgrade2ws(http_s *http, websocket_settings_s); - -/** This macro allows easy access to the `http_upgrade2ws` function. The macro - * allows the use of named arguments, using the `websocket_settings_s` struct - * members. i.e.: - * - * on_message(ws_s * ws, char * data, size_t size, int is_text) { - * ; // ... this is the websocket on_message callback - * websocket_write(ws, data, size, is_text); // a simple echo example - * } - * - * on_upgrade(http_s* h) { - * http_upgrade2ws( .http = h, .on_message = on_message); - * } - */ -#define http_upgrade2ws(http, ...) \ - http_upgrade2ws((http), (websocket_settings_s){__VA_ARGS__}) - -/** - * Connects to a Websocket service according to the provided address. - * - * This is a somewhat naive connector object, it doesn't perform any - * authentication or other logical handling. However, it's quire easy to author - * a complext authentication logic using a combination of `http_connect` and - * `http_upgrade2ws`. - * - * Returns the uuid for the future websocket on success. - * - * Returns -1 on error; - */ -int websocket_connect(const char *url, websocket_settings_s settings); -#define websocket_connect(url, ...) \ - websocket_connect((url), (websocket_settings_s){__VA_ARGS__}) - -#include - -/* ***************************************************************************** -EventSource Support (SSE) -***************************************************************************** */ - -/** - * The type for the EventSource (SSE) handle, used to identify an SSE - * connection. - */ -typedef struct http_sse_s http_sse_s; - -/** - * This struct is used for the named arguments in the `http_upgrade2sse` - * function and macro. - */ -struct http_sse_s { - /** - * The (optional) on_open callback will be called once the EventSource - * connection is established. - */ - void (*on_open)(http_sse_s *sse); - /** - * The (optional) on_ready callback will be after a the underlying socket's - * buffer changes it's state to empty. - * - * If the socket's buffer is never used, the callback is never called. - */ - void (*on_ready)(http_sse_s *sse); - /** - * The (optional) on_shutdown callback will be called if a connection is still - * open while the server is shutting down (called before `on_close`). - */ - void (*on_shutdown)(http_sse_s *sse); - /** - * The (optional) on_close callback will be called once a connection is - * terminated or failed to be established. - * - * The `udata` passed to the `http_upgrade2sse` function is available - * through the `http_sse_s` pointer (`sse->udata`). - */ - void (*on_close)(http_sse_s *sse); - /** Opaque user data. */ - void *udata; -}; - -/** - * Upgrades an HTTP connection to an EventSource (SSE) connection. - * - * The `http_s` handle will be invalid after this call. - * - * On HTTP/1.1 connections, this will preclude future requests using the same - * connection. - */ -int http_upgrade2sse(http_s *h, http_sse_s); - -/** This macro allows easy access to the `http_upgrade2sse` function. The macro - * allows the use of named arguments, using the `websocket_settings_s` struct - * members. i.e.: - * - * on_open_sse(sse_s * sse) { - * http_sse_subscribe(sse, .channel = CHANNEL_NAME); - * } - * - * on_upgrade(http_s* h) { - * http_upgrade2sse(h, .on_open = on_open_sse); - * } - */ -#define http_upgrade2sse(h, ...) \ - http_upgrade2sse((h), (http_sse_s){__VA_ARGS__}) - -/** - * Sets the ping interval for SSE connections. - */ -void http_sse_set_timout(http_sse_s *sse, uint8_t timeout); - -struct http_sse_subscribe_args { - /** The channel name used for the subscription. */ - fio_str_info_s channel; - /** The optional on message callback. If missing, Data is directly writen. */ - void (*on_message)(http_sse_s *sse, fio_str_info_s channel, - fio_str_info_s msg, void *udata); - /** An optional callback for when a subscription is fully canceled. */ - void (*on_unsubscribe)(void *udata); - /** Opaque user */ - void *udata; - /** A callback for pattern matching. */ - fio_match_fn match; -}; - -/** - * Subscribes to a channel for direct message deliverance. See {struct - * http_sse_subscribe_args} for possible arguments. - * - * Returns a subscription ID on success and 0 on failure. - * - * To unsubscripbe from the channel, use `http_sse_unsubscribe` (NOT - * `fio_unsubscribe`). - * - * All subscriptions are automatically cleared once the connection is closed. - */ -uintptr_t http_sse_subscribe(http_sse_s *sse, - struct http_sse_subscribe_args args); - -/** This macro allows easy access to the `http_sse_subscribe` function. */ -#define http_sse_subscribe(sse, ...) \ - http_sse_subscribe((sse), (struct http_sse_subscribe_args){__VA_ARGS__}) - -/** - * Cancels a subscription and invalidates the subscription object. - */ -void http_sse_unsubscribe(http_sse_s *sse, uintptr_t subscription); - -/** - * Named arguments for the {http_sse_write} function. - * - * These arguments list the possible fields for the SSE event. - * - * Event fields listed here: - * https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events - */ -struct http_sse_write_args { - fio_str_info_s id; /* (optional) sets the `id` event property. */ - fio_str_info_s event; /* (optional) sets the `event` event property. */ - fio_str_info_s data; /* (optional) sets the `data` event property. */ - intptr_t retry; /* (optional) sets the `retry` event property. */ -}; - -/** - * Writes data to an EventSource (SSE) connection. - * - * See the {struct http_sse_write_args} for possible named arguments. - */ -int http_sse_write(http_sse_s *sse, struct http_sse_write_args); -#define http_sse_write(sse, ...) \ - http_sse_write((sse), (struct http_sse_write_args){__VA_ARGS__}) - -/** - * Get the connection's UUID (for `fio_defer_io_task`, pub/sub, etc'). - */ -intptr_t http_sse2uuid(http_sse_s *sse); - -/** - * Closes an EventSource (SSE) connection. - */ -int http_sse_close(http_sse_s *sse); - -/** - * Duplicates an SSE handle by reference, remember to http_sse_free. - * - * Returns the same object (increases a reference count, no allocation is made). - */ -http_sse_s *http_sse_dup(http_sse_s *sse); - -/** - * Frees an SSE handle by reference (decreases the reference count). - */ -void http_sse_free(http_sse_s *sse); - -/* ***************************************************************************** -HTTP GET and POST parsing helpers -***************************************************************************** */ - -/** - * Attempts to decode the request's body. - * - * Supported Types include: - * * application/x-www-form-urlencoded - * * application/json - * * multipart/form-data - * - * This should be called before `http_parse_query`, in order to support JSON - * data. - * - * If the JSON data isn't an object, it will be saved under the key "JSON" in - * the `params` hash. - * - * If the `multipart/form-data` type contains JSON files, they will NOT be - * parsed (they will behave like any other file, with `data`, `type` and - * `filename` keys assigned). This allows non-object JSON data (such as array) - * to be handled by the app. - */ -int http_parse_body(http_s *h); - -/** - * Parses the query part of an HTTP request/response. Uses `http_add2hash`. - * - * This should be called after the `http_parse_body` function, just in case the - * body is JSON that doesn't have an object at it's root. - */ -void http_parse_query(http_s *h); - -/** Parses any Cookie / Set-Cookie headers, using the `http_add2hash` scheme. */ -void http_parse_cookies(http_s *h, uint8_t is_url_encoded); - -/** - * Adds a named parameter to the hash, converting a string to an object and - * resolving nesting references and URL decoding if required. - * - * i.e.: - * - * * "name[]" references a nested Array (nested in the Hash). - * * "name[key]" references a nested Hash. - * * "name[][key]" references a nested Hash within an array. Hash keys will be - * unique (repeating a key advances the hash). - * * These rules can be nested (i.e. "name[][key1][][key2]...") - * * "name[][]" is an error (there's no way for the parser to analyze - * dimensions) - * - * Note: names can't begin with "[" or end with "]" as these are reserved - * characters. - */ -int http_add2hash(FIOBJ dest, char *name, size_t name_len, char *value, - size_t value_len, uint8_t encoded); - -/** - * Adds a named parameter to the hash, using an existing object and resolving - * nesting references. - * - * i.e.: - * - * * "name[]" references a nested Array (nested in the Hash). - * * "name[key]" references a nested Hash. - * * "name[][key]" references a nested Hash within an array. Hash keys will be - * unique (repeating a key advances the array). - * * These rules can be nested (i.e. "name[][key1][][key2]...") - * * "name[][]" is an error (there's no way for the parser to analyze - * dimensions) - * - * Note: names can't begin with "[" or end with "]" as these are reserved - * characters. - */ -int http_add2hash2(FIOBJ dest, char *name, size_t name_len, FIOBJ value, - uint8_t encoded); - -/* ***************************************************************************** -HTTP Status Strings and Mime-Type helpers -***************************************************************************** */ - -/** Returns a human readable string related to the HTTP status number. */ -fio_str_info_s http_status2str(uintptr_t status); - -/** Registers a Mime-Type to be associated with the file extension. */ -void http_mimetype_register(char *file_ext, size_t file_ext_len, - FIOBJ mime_type_str); - -/** - * Finds the mime-type associated with the file extension, returning a String on - * success and FIOBJ_INVALID on failure. - * - * Remember to call `fiobj_free`. - */ -FIOBJ http_mimetype_find(char *file_ext, size_t file_ext_len); - -/** - * Returns the mime-type associated with the URL or the default mime-type for - * HTTP. - * - * Remember to call `fiobj_free`. - */ -FIOBJ http_mimetype_find2(FIOBJ url); - -/** Clears the Mime-Type registry (it will be empty after this call). */ -void http_mimetype_clear(void); - -/* ***************************************************************************** -Commonly used headers (fiobj Symbol objects) -***************************************************************************** */ - -extern FIOBJ HTTP_HEADER_ACCEPT; -extern FIOBJ HTTP_HEADER_CACHE_CONTROL; -extern FIOBJ HTTP_HEADER_CONNECTION; -extern FIOBJ HTTP_HEADER_CONTENT_ENCODING; -extern FIOBJ HTTP_HEADER_CONTENT_LENGTH; -extern FIOBJ HTTP_HEADER_CONTENT_RANGE; -extern FIOBJ HTTP_HEADER_CONTENT_TYPE; -extern FIOBJ HTTP_HEADER_COOKIE; -extern FIOBJ HTTP_HEADER_DATE; -extern FIOBJ HTTP_HEADER_ETAG; -extern FIOBJ HTTP_HEADER_HOST; -extern FIOBJ HTTP_HEADER_LAST_MODIFIED; -extern FIOBJ HTTP_HEADER_ORIGIN; -extern FIOBJ HTTP_HEADER_SET_COOKIE; -extern FIOBJ HTTP_HEADER_UPGRADE; - -/* ***************************************************************************** -HTTP General Helper functions that could be used globally -***************************************************************************** */ - -/** - * Returns a String object representing the unparsed HTTP request (HTTP version - * is capped at HTTP/1.1). Mostly usable for proxy usage and debugging. - */ -FIOBJ http_req2str(http_s *h); - -/** - * Writes a log line to `stderr` about the request / response object. - * - * This function is called automatically if the `.log` setting is enabled. - */ -void http_write_log(http_s *h); -/* ***************************************************************************** -HTTP Time related helper functions that could be used globally -***************************************************************************** */ - -/** -A faster (yet less localized) alternative to `gmtime_r`. - -See the libc `gmtime_r` documentation for details. - -Falls back to `gmtime_r` for dates before epoch. -*/ -struct tm *http_gmtime(time_t timer, struct tm *tmbuf); - -/** Writes an RFC 7231 date representation (HTTP date format) to target. */ -size_t http_date2rfc7231(char *target, struct tm *tmbuf); -/** Writes an RFC 2109 date representation to target. */ -size_t http_date2rfc2109(char *target, struct tm *tmbuf); -/** Writes an RFC 2822 date representation to target. */ -size_t http_date2rfc2822(char *target, struct tm *tmbuf); -/** -Writes an HTTP date string to the `target` buffer. - -This requires ~32 bytes of space to be available at the target buffer (unless -it's a super funky year, 32 bytes is about 3 more than you need). - -Returns the number of bytes actually written. -*/ -static inline size_t http_date2str(char *target, struct tm *tmbuf) { - return http_date2rfc7231(target, tmbuf); -} - -/** - * Prints Unix time to a HTTP time formatted string. - * - * This variation implements cached results for faster processing, at the - * price of a less accurate string. - */ -size_t http_time2str(char *target, const time_t t); - -/* ***************************************************************************** -HTTP URL decoding helper functions that might be used globally -***************************************************************************** */ - -/** Decodes a URL encoded string, no buffer overflow protection. */ -ssize_t http_decode_url_unsafe(char *dest, const char *url_data); - -/** Decodes a URL encoded string (query / form data). */ -ssize_t http_decode_url(char *dest, const char *url_data, size_t length); - -/** Decodes the "path" part of a request, no buffer overflow protection. */ -ssize_t http_decode_path_unsafe(char *dest, const char *url_data); - -/** - * Decodes the "path" part of an HTTP request, no buffer overflow protection. - */ -ssize_t http_decode_path(char *dest, const char *url_data, size_t length); - -/* ***************************************************************************** -HTTP URL parsing -***************************************************************************** */ - -/** the result returned by `http_url_parse` */ -typedef fio_url_s http_url_s - __attribute__((deprecated("use fio_url_s instead"))); - -/** - * Parses the URI returning it's components and their lengths (no decoding - * performed, doesn't accept decoded URIs). - * - * The returned string are NOT NUL terminated, they are merely locations within - * the original string. - * - * This function expects any of the following formats: - * - * * `/complete_path?query#target` - * - * i.e.: /index.html?page=1#list - * - * * `host:port/complete_path?query#target` - * - * i.e.: - * example.com - * example.com/index.html - * example.com:8080/index.html - * example.com:8080/index.html?key=val#target - * - * * `user:password@host:port/path?query#target` - * - * i.e.: user:1234@example.com:8080/index.html - * - * * `schema://user:password@host:port/path?query#target` - * - * i.e.: http://example.com/index.html?page=1#list - * - * Invalid formats might produce unexpected results. No error testing performed. - */ -#define http_url_parse(url, len) fio_url_parse((url), (len)) - -#if DEBUG -void http_tests(void); -#endif - -/* support C++ */ -#ifdef __cplusplus -} -#endif - -#endif /* H_HTTP_H */ diff --git a/ext/iodine/http1.c b/ext/iodine/http1.c deleted file mode 100644 index 0e46663e..00000000 --- a/ext/iodine/http1.c +++ /dev/null @@ -1,825 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#include - -#include -#include -#include -#include - -#include - -#include -#include - -/* ***************************************************************************** -The HTTP/1.1 Protocol Object -***************************************************************************** */ - -typedef struct http1pr_s { - http_fio_protocol_s p; - http1_parser_s parser; - http_s request; - uintptr_t buf_len; - uintptr_t max_header_size; - uintptr_t header_size; - uint8_t close; - uint8_t is_client; - uint8_t stop; - uint8_t buf[]; -} http1pr_s; - -struct http_vtable_s HTTP1_VTABLE; /* initialized later on */ - -/* ***************************************************************************** -Internal Helpers -***************************************************************************** */ - -#define parser2http(x) \ - ((http1pr_s *)((uintptr_t)(x) - (uintptr_t)(&((http1pr_s *)0)->parser))) - -inline static void h1_reset(http1pr_s *p) { p->header_size = 0; } - -#define http1_pr2handle(pr) (((http1pr_s *)(pr))->request) -#define handle2pr(h) ((http1pr_s *)h->private_data.flag) - -/* cleanup an HTTP/1.1 handler object */ -static inline void http1_after_finish(http_s *h) { - http1pr_s *p = handle2pr(h); - p->stop = p->stop & (~1UL); - if (h != &p->request) { - http_s_destroy(h, 0); - fio_free(h); - } else { - http_s_clear(h, p->p.settings->log); - } - if (p->close) - fio_close(p->p.uuid); -} - -/* ***************************************************************************** -HTTP Request / Response (Virtual) Functions -***************************************************************************** */ -struct header_writer_s { - FIOBJ dest; - FIOBJ name; - FIOBJ value; -}; - -static int write_header(FIOBJ o, void *w_) { - struct header_writer_s *w = w_; - if (!o) - return 0; - if (fiobj_hash_key_in_loop()) { - w->name = fiobj_hash_key_in_loop(); - } - if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) { - fiobj_each1(o, 0, write_header, w); - return 0; - } - fio_str_info_s name = fiobj_obj2cstr(w->name); - fio_str_info_s str = fiobj_obj2cstr(o); - if (!str.data) - return 0; - // fiobj_str_capa_assert(w->dest, - // fiobj_obj2cstr(w->dest).len + name.len + str.len + - // 5); - fiobj_str_write(w->dest, name.data, name.len); - fiobj_str_write(w->dest, ":", 1); - fiobj_str_write(w->dest, str.data, str.len); - fiobj_str_write(w->dest, "\r\n", 2); - return 0; -} - -static FIOBJ headers2str(http_s *h, uintptr_t padding) { - if (!h->method && !!h->status_str) - return FIOBJ_INVALID; - - static uintptr_t connection_hash; - if (!connection_hash) - connection_hash = fiobj_hash_string("connection", 10); - - struct header_writer_s w; - { - const uintptr_t header_length_guess = - fiobj_hash_count(h->private_data.out_headers) * 64; - w.dest = fiobj_str_buf(header_length_guess + padding); - } - http1pr_s *p = handle2pr(h); - - if (p->is_client == 0) { - fio_str_info_s t = http_status2str(h->status); - fiobj_str_write(w.dest, "HTTP/1.1 ", 9); - fiobj_str_write(w.dest, t.data, t.len); - fiobj_str_write(w.dest, "\r\n", 2); - FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, connection_hash); - if (tmp) { - t = fiobj_obj2cstr(tmp); - if (t.data[0] == 'c' || t.data[0] == 'C') - p->close = 1; - } else { - tmp = fiobj_hash_get2(h->headers, connection_hash); - if (tmp) { - t = fiobj_obj2cstr(tmp); - if (!t.data || !t.len || t.data[0] == 'k' || t.data[0] == 'K') - fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); - else { - fiobj_str_write(w.dest, "connection:close\r\n", 18); - p->close = 1; - } - } else { - t = fiobj_obj2cstr(h->version); - if (!p->close && t.len > 7 && t.data && t.data[5] == '1' && - t.data[6] == '.' && t.data[7] == '1') - fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); - else { - fiobj_str_write(w.dest, "connection:close\r\n", 18); - p->close = 1; - } - } - } - } else { - if (h->method) { - fiobj_str_join(w.dest, h->method); - fiobj_str_write(w.dest, " ", 1); - } else { - fiobj_str_write(w.dest, "GET ", 4); - } - fiobj_str_join(w.dest, h->path); - if (h->query) { - fiobj_str_write(w.dest, "?", 1); - fiobj_str_join(w.dest, h->query); - } - fiobj_str_write(w.dest, " HTTP/1.1\r\n", 11); - /* make sure we have a host header? */ - static uint64_t host_hash; - if (!host_hash) - host_hash = fiobj_hash_string("host", 4); - FIOBJ tmp; - if (!fiobj_hash_get2(h->private_data.out_headers, host_hash) && - (tmp = fiobj_hash_get2(h->headers, host_hash))) { - fiobj_str_write(w.dest, "host:", 5); - fiobj_str_join(w.dest, tmp); - fiobj_str_write(w.dest, "\r\n", 2); - } - if (!fiobj_hash_get2(h->private_data.out_headers, connection_hash)) - fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); - } - - fiobj_each1(h->private_data.out_headers, 0, write_header, &w); - fiobj_str_write(w.dest, "\r\n", 2); - return w.dest; -} - -/** Should send existing headers and data */ -static int http1_send_body(http_s *h, void *data, uintptr_t length) { - - FIOBJ packet = headers2str(h, length); - if (!packet) { - http1_after_finish(h); - return -1; - } - fiobj_str_write(packet, data, length); - fiobj_send_free((handle2pr(h)->p.uuid), packet); - http1_after_finish(h); - return 0; -} -/** Should send existing headers and file */ -static int http1_sendfile(http_s *h, int fd, uintptr_t length, - uintptr_t offset) { - FIOBJ packet = headers2str(h, 0); - if (!packet) { - close(fd); - http1_after_finish(h); - return -1; - } - if (length < HTTP_MAX_HEADER_LENGTH) { - /* optimize away small files */ - fio_str_info_s s = fiobj_obj2cstr(packet); - fiobj_str_capa_assert(packet, s.len + length); - s = fiobj_obj2cstr(packet); - intptr_t i = pread(fd, s.data + s.len, length, offset); - if (i < 0) { - close(fd); - fiobj_send_free((handle2pr(h)->p.uuid), packet); - fio_close((handle2pr(h)->p.uuid)); - return -1; - } - close(fd); - fiobj_str_resize(packet, s.len + i); - fiobj_send_free((handle2pr(h)->p.uuid), packet); - http1_after_finish(h); - return 0; - } - fiobj_send_free((handle2pr(h)->p.uuid), packet); - fio_sendfile((handle2pr(h)->p.uuid), fd, offset, length); - http1_after_finish(h); - return 0; -} - -/** Should send existing headers or complete streaming */ -static void htt1p_finish(http_s *h) { - FIOBJ packet = headers2str(h, 0); - if (packet) - fiobj_send_free((handle2pr(h)->p.uuid), packet); - else { - // fprintf(stderr, "WARNING: invalid call to `htt1p_finish`\n"); - } - http1_after_finish(h); -} -/** Push for data - unsupported. */ -static int http1_push_data(http_s *h, void *data, uintptr_t length, - FIOBJ mime_type) { - return -1; - (void)h; - (void)data; - (void)length; - (void)mime_type; -} -/** Push for files - unsupported. */ -static int http1_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) { - return -1; - (void)h; - (void)filename; - (void)mime_type; -} - -/** - * Called befor a pause task, - */ -static void http1_on_pause(http_s *h, http_fio_protocol_s *pr) { - ((http1pr_s *)pr)->stop = 1; - fio_suspend(pr->uuid); - (void)h; -} - -/** - * called after the resume task had completed. - */ -static void http1_on_resume(http_s *h, http_fio_protocol_s *pr) { - if (!((http1pr_s *)pr)->stop) { - fio_force_event(pr->uuid, FIO_EVENT_ON_DATA); - } - (void)h; -} - -static intptr_t http1_hijack(http_s *h, fio_str_info_s *leftover) { - if (leftover) { - intptr_t len = - handle2pr(h)->buf_len - - (intptr_t)(handle2pr(h)->parser.state.next - handle2pr(h)->buf); - if (len) { - *leftover = (fio_str_info_s){ - .len = len, .data = (char *)handle2pr(h)->parser.state.next}; - } else { - *leftover = (fio_str_info_s){.len = 0, .data = NULL}; - } - } - - handle2pr(h)->stop = 3; - intptr_t uuid = handle2pr(h)->p.uuid; - fio_attach(uuid, NULL); - return uuid; -} - -/* ***************************************************************************** -Websockets Upgrading -***************************************************************************** */ - -static void http1_websocket_client_on_upgrade(http_s *h, char *proto, - size_t len) { - http1pr_s *p = handle2pr(h); - websocket_settings_s *args = h->udata; - const intptr_t uuid = handle2pr(h)->p.uuid; - http_settings_s *set = handle2pr(h)->p.settings; - set->udata = NULL; - http_finish(h); - p->stop = 1; - websocket_attach(uuid, set, args, p->parser.state.next, - p->buf_len - (intptr_t)(p->parser.state.next - p->buf)); - fio_free(args); - (void)proto; - (void)len; -} -static void http1_websocket_client_on_failed(http_s *h) { - websocket_settings_s *s = h->udata; - if (s->on_close) - s->on_close(0, s->udata); - fio_free(h->udata); - h->udata = http_settings(h)->udata = NULL; -} -static void http1_websocket_client_on_hangup(http_settings_s *settings) { - websocket_settings_s *s = settings->udata; - if (s) { - if (s->on_close) - s->on_close(0, s->udata); - fio_free(settings->udata); - settings->udata = NULL; - } -} - -static int http1_http2websocket_server(http_s *h, websocket_settings_s *args) { - // A static data used for all websocket connections. - static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - static uintptr_t sec_version = 0; - static uintptr_t sec_key = 0; - if (!sec_version) - sec_version = fiobj_hash_string("sec-websocket-version", 21); - if (!sec_key) - sec_key = fiobj_hash_string("sec-websocket-key", 17); - - FIOBJ tmp = fiobj_hash_get2(h->headers, sec_version); - if (!tmp) - goto bad_request; - fio_str_info_s stmp = fiobj_obj2cstr(tmp); - if (stmp.len != 2 || stmp.data[0] != '1' || stmp.data[1] != '3') - goto bad_request; - - tmp = fiobj_hash_get2(h->headers, sec_key); - if (!tmp) - goto bad_request; - stmp = fiobj_obj2cstr(tmp); - - fio_sha1_s sha1 = fio_sha1_init(); - fio_sha1_write(&sha1, stmp.data, stmp.len); - fio_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1); - tmp = fiobj_str_buf(32); - stmp = fiobj_obj2cstr(tmp); - fiobj_str_resize(tmp, - fio_base64_encode(stmp.data, fio_sha1_result(&sha1), 20)); - http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE)); - http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET)); - http_set_header(h, HTTP_HEADER_WS_SEC_KEY, tmp); - h->status = 101; - http1pr_s *pr = handle2pr(h); - const intptr_t uuid = handle2pr(h)->p.uuid; - http_settings_s *set = handle2pr(h)->p.settings; - http_finish(h); - pr->stop = 1; - websocket_attach(uuid, set, args, pr->parser.state.next, - pr->buf_len - (intptr_t)(pr->parser.state.next - pr->buf)); - return 0; -bad_request: - http_send_error(h, 400); - if (args->on_close) - args->on_close(0, args->udata); - return -1; -} - -static int http1_http2websocket_client(http_s *h, websocket_settings_s *args) { - http1pr_s *p = handle2pr(h); - /* We're done with the HTTP stage, so we call the `on_finish` */ - if (p->p.settings->on_finish) - p->p.settings->on_finish(p->p.settings); - /* Copy the Websocket setting arguments to the HTTP settings `udata` */ - p->p.settings->udata = fio_malloc(sizeof(*args)); - FIO_ASSERT_ALLOC(p->p.settings->udata); - ((websocket_settings_s *)(p->p.settings->udata))[0] = *args; - /* Set callbacks */ - p->p.settings->on_finish = http1_websocket_client_on_hangup; /* unknown */ - p->p.settings->on_upgrade = http1_websocket_client_on_upgrade; /* sucess */ - p->p.settings->on_response = http1_websocket_client_on_failed; /* failed */ - p->p.settings->on_request = http1_websocket_client_on_failed; /* failed */ - /* Set headers */ - http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE)); - http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET)); - http_set_header(h, HTTP_HVALUE_WS_SEC_VERSION, - fiobj_dup(HTTP_HVALUE_WS_VERSION)); - - /* we don't set the Origin header since we're not a browser... should we? */ - // http_set_header( - // h, HTTP_HEADER_ORIGIN, - // fiobj_dup(fiobj_hash_get2(h->private_data.out_headers, - // fiobj_obj2hash(HTTP_HEADER_HOST)))); - - /* create nonce */ - uint64_t key[2]; /* 16 bytes */ - key[0] = (uintptr_t)h ^ (uint64_t)fio_last_tick().tv_sec; - key[1] = (uintptr_t)args->udata ^ (uint64_t)fio_last_tick().tv_nsec; - FIOBJ encoded = fiobj_str_buf(26); /* we need 24 really. */ - fio_str_info_s tmp = fiobj_obj2cstr(encoded); - tmp.len = fio_base64_encode(tmp.data, (char *)key, 16); - fiobj_str_resize(encoded, tmp.len); - http_set_header(h, HTTP_HEADER_WS_SEC_CLIENT_KEY, encoded); - http_finish(h); - return 0; -} - -static int http1_http2websocket(http_s *h, websocket_settings_s *args) { - assert(h); - http1pr_s *p = handle2pr(h); - - if (p->is_client == 0) { - return http1_http2websocket_server(h, args); - } - return http1_http2websocket_client(h, args); -} - -/* ***************************************************************************** -EventSource Support (SSE) -***************************************************************************** */ - -#undef http_upgrade2sse - -typedef struct { - fio_protocol_s p; - http_sse_internal_s *sse; -} http1_sse_fio_protocol_s; - -static void http1_sse_on_ready(intptr_t uuid, fio_protocol_s *p_) { - http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; - if (p->sse->sse.on_ready) - p->sse->sse.on_ready(&p->sse->sse); - (void)uuid; -} -static uint8_t http1_sse_on_shutdown(intptr_t uuid, fio_protocol_s *p_) { - http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; - if (p->sse->sse.on_shutdown) - p->sse->sse.on_shutdown(&p->sse->sse); - return 0; - (void)uuid; -} -static void http1_sse_on_close(intptr_t uuid, fio_protocol_s *p_) { - http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; - http_sse_destroy(p->sse); - fio_free(p); - (void)uuid; -} -static void http1_sse_ping(intptr_t uuid, fio_protocol_s *p_) { - fio_write2(uuid, .data.buffer = ": ping\n\n", .length = 8, - .after.dealloc = FIO_DEALLOC_NOOP); - (void)p_; -} - -/** - * Upgrades an HTTP connection to an EventSource (SSE) connection. - * - * Thie `http_s` handle will be invalid after this call. - * - * On HTTP/1.1 connections, this will preclude future requests using the same - * connection. - */ -static int http1_upgrade2sse(http_s *h, http_sse_s *sse) { - const intptr_t uuid = handle2pr(h)->p.uuid; - /* send response */ - h->status = 200; - http_set_header(h, HTTP_HEADER_CONTENT_TYPE, fiobj_dup(HTTP_HVALUE_SSE_MIME)); - http_set_header(h, HTTP_HEADER_CACHE_CONTROL, - fiobj_dup(HTTP_HVALUE_NO_CACHE)); - http_set_header(h, HTTP_HEADER_CONTENT_ENCODING, - fiobj_str_new("identity", 8)); - handle2pr(h)->stop = 1; - htt1p_finish(h); /* avoid the enforced content length in http_finish */ - - /* switch protocol to SSE */ - http1_sse_fio_protocol_s *sse_pr = fio_malloc(sizeof(*sse_pr)); - if (!sse_pr) - goto failed; - *sse_pr = (http1_sse_fio_protocol_s){ - .p = - { - .on_ready = http1_sse_on_ready, - .on_shutdown = http1_sse_on_shutdown, - .on_close = http1_sse_on_close, - .ping = http1_sse_ping, - }, - .sse = fio_malloc(sizeof(*(sse_pr->sse))), - }; - - if (!sse_pr->sse) - goto failed; - - http_sse_init(sse_pr->sse, uuid, &HTTP1_VTABLE, sse); - fio_timeout_set(uuid, handle2pr(h)->p.settings->ws_timeout); - if (sse->on_open) - sse->on_open(&sse_pr->sse->sse); - fio_attach(uuid, &sse_pr->p); - return 0; - -failed: - fio_close(handle2pr(h)->p.uuid); - if (sse->on_close) - sse->on_close(sse); - return -1; - (void)sse; -} - -#undef http_sse_write -/** - * Writes data to an EventSource (SSE) connection. - * - * See the {struct http_sse_write_args} for possible named arguments. - */ -static int http1_sse_write(http_sse_s *sse, FIOBJ str) { - return fiobj_send_free(((http_sse_internal_s *)sse)->uuid, str); -} - -/** - * Closes an EventSource (SSE) connection. - */ -static int http1_sse_close(http_sse_s *sse) { - fio_close(((http_sse_internal_s *)sse)->uuid); - return 0; -} -/* ***************************************************************************** -Virtual Table Decleration -***************************************************************************** */ - -struct http_vtable_s HTTP1_VTABLE = { - .http_send_body = http1_send_body, - .http_sendfile = http1_sendfile, - .http_finish = htt1p_finish, - .http_push_data = http1_push_data, - .http_push_file = http1_push_file, - .http_on_pause = http1_on_pause, - .http_on_resume = http1_on_resume, - .http_hijack = http1_hijack, - .http2websocket = http1_http2websocket, - .http_upgrade2sse = http1_upgrade2sse, - .http_sse_write = http1_sse_write, - .http_sse_close = http1_sse_close, -}; - -void *http1_vtable(void) { return (void *)&HTTP1_VTABLE; } - -/* ***************************************************************************** -Parser Callbacks -***************************************************************************** */ - -/** called when a request was received. */ -static int http1_on_request(http1_parser_s *parser) { - http1pr_s *p = parser2http(parser); - http_on_request_handler______internal(&http1_pr2handle(p), p->p.settings); - if (p->request.method && !p->stop) - http_finish(&p->request); - h1_reset(p); - return fio_is_closed(p->p.uuid); -} -/** called when a response was received. */ -static int http1_on_response(http1_parser_s *parser) { - http1pr_s *p = parser2http(parser); - http_on_response_handler______internal(&http1_pr2handle(p), p->p.settings); - if (p->request.status_str && !p->stop) - http_finish(&p->request); - h1_reset(p); - return fio_is_closed(p->p.uuid); -} -/** called when a request method is parsed. */ -static int http1_on_method(http1_parser_s *parser, char *method, - size_t method_len) { - http1_pr2handle(parser2http(parser)).method = - fiobj_str_new(method, method_len); - parser2http(parser)->header_size += method_len; - return 0; -} - -/** called when a response status is parsed. the status_str is the string - * without the prefixed numerical status indicator.*/ -static int http1_on_status(http1_parser_s *parser, size_t status, - char *status_str, size_t len) { - http1_pr2handle(parser2http(parser)).status_str = - fiobj_str_new(status_str, len); - http1_pr2handle(parser2http(parser)).status = status; - parser2http(parser)->header_size += len; - return 0; -} - -/** called when a request path (excluding query) is parsed. */ -static int http1_on_path(http1_parser_s *parser, char *path, size_t len) { - http1_pr2handle(parser2http(parser)).path = fiobj_str_new(path, len); - parser2http(parser)->header_size += len; - return 0; -} - -/** called when a request path (excluding query) is parsed. */ -static int http1_on_query(http1_parser_s *parser, char *query, size_t len) { - http1_pr2handle(parser2http(parser)).query = fiobj_str_new(query, len); - parser2http(parser)->header_size += len; - return 0; -} -/** called when a the HTTP/1.x version is parsed. */ -static int http1_on_version(http1_parser_s *parser, char *version, size_t len) { - http1_pr2handle(parser2http(parser)).version = fiobj_str_new(version, len); - parser2http(parser)->header_size += len; -/* start counting - occurs on the first line of both requests and responses */ -#if FIO_HTTP_EXACT_LOGGING - clock_gettime(CLOCK_REALTIME, - &http1_pr2handle(parser2http(parser)).received_at); -#else - http1_pr2handle(parser2http(parser)).received_at = fio_last_tick(); -#endif - return 0; -} -/** called when a header is parsed. */ -static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len, - char *data, size_t data_len) { - FIOBJ sym; - FIOBJ obj; - if (!http1_pr2handle(parser2http(parser)).headers) { - FIO_LOG_ERROR("(http1 parse ordering error) missing HashMap for header " - "%s: %s", - name, data); - http_send_error2(500, parser2http(parser)->p.uuid, - parser2http(parser)->p.settings); - return -1; - } - parser2http(parser)->header_size += name_len + data_len; - if (parser2http(parser)->header_size >= - parser2http(parser)->max_header_size || - fiobj_hash_count(http1_pr2handle(parser2http(parser)).headers) > - HTTP_MAX_HEADER_COUNT) { - if (parser2http(parser)->p.settings->log) { - FIO_LOG_WARNING("(HTTP) security alert - header flood detected."); - } - http_send_error(&http1_pr2handle(parser2http(parser)), 413); - return -1; - } - sym = fiobj_str_new(name, name_len); - obj = fiobj_str_new(data, data_len); - set_header_add(http1_pr2handle(parser2http(parser)).headers, sym, obj); - fiobj_free(sym); - return 0; -} -/** called when a body chunk is parsed. */ -static int http1_on_body_chunk(http1_parser_s *parser, char *data, - size_t data_len) { - if (parser->state.content_length > - (ssize_t)parser2http(parser)->p.settings->max_body_size || - parser->state.read > - (ssize_t)parser2http(parser)->p.settings->max_body_size) { - http_send_error(&http1_pr2handle(parser2http(parser)), 413); - return -1; /* test every time, in case of chunked data */ - } - if (!parser->state.read) { - if (parser->state.content_length > 0 && - parser->state.content_length <= HTTP_MAX_HEADER_LENGTH) { - http1_pr2handle(parser2http(parser)).body = fiobj_data_newstr(); - } else { - http1_pr2handle(parser2http(parser)).body = fiobj_data_newtmpfile(); - } - } - fiobj_data_write(http1_pr2handle(parser2http(parser)).body, data, data_len); - return 0; -} - -/** called when a protocol error occurred. */ -static int http1_on_error(http1_parser_s *parser) { - if (parser2http(parser)->close) - return -1; - FIO_LOG_DEBUG("HTTP parser error."); - fio_close(parser2http(parser)->p.uuid); - return -1; -} - -/* ***************************************************************************** -Connection Callbacks -***************************************************************************** */ - -static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) { - if (fio_pending(uuid) > 4) { - goto throttle; - } - ssize_t i = 0; - size_t org_len = p->buf_len; - int pipeline_limit = 8; - if (!p->buf_len) - return; - do { - i = http1_parse(&p->parser, p->buf + (org_len - p->buf_len), p->buf_len); - p->buf_len -= i; - --pipeline_limit; - } while (i && p->buf_len && pipeline_limit && !p->stop); - - if (p->buf_len && org_len != p->buf_len) { - memmove(p->buf, p->buf + (org_len - p->buf_len), p->buf_len); - } - - if (p->buf_len == HTTP_MAX_HEADER_LENGTH) { - /* no room to read... parser not consuming data */ - if (p->request.method) - http_send_error(&p->request, 413); - else { - p->request.method = fiobj_str_tmp(); - http_send_error(&p->request, 413); - } - } - - if (!pipeline_limit) { - fio_force_event(uuid, FIO_EVENT_ON_DATA); - } - return; - -throttle: - /* throttle busy clients (slowloris) */ - p->stop |= 4; - fio_suspend(uuid); - FIO_LOG_DEBUG("(HTTP/1,1) throttling client at %.*s", - (int)fio_peer_addr(uuid).len, fio_peer_addr(uuid).data); -} - -/** called when a data is available, but will not run concurrently */ -static void http1_on_data(intptr_t uuid, fio_protocol_s *protocol) { - http1pr_s *p = (http1pr_s *)protocol; - if (p->stop) { - fio_suspend(uuid); - return; - } - ssize_t i = 0; - if (HTTP_MAX_HEADER_LENGTH - p->buf_len) - i = fio_read(uuid, p->buf + p->buf_len, - HTTP_MAX_HEADER_LENGTH - p->buf_len); - if (i > 0) { - p->buf_len += i; - } - http1_consume_data(uuid, p); -} - -/** called when the connection was closed, but will not run concurrently */ -static void http1_on_close(intptr_t uuid, fio_protocol_s *protocol) { - http1_destroy(protocol); - (void)uuid; -} - -/** called when the connection was closed, but will not run concurrently */ -static void http1_on_ready(intptr_t uuid, fio_protocol_s *protocol) { - /* resume slow clients from suspension */ - http1pr_s *p = (http1pr_s *)protocol; - if (p->stop & 4) { - p->stop ^= 4; /* flip back the bit, so it's zero */ - fio_force_event(uuid, FIO_EVENT_ON_DATA); - } - (void)protocol; -} - -/** called when a data is available for the first time */ -static void http1_on_data_first_time(intptr_t uuid, fio_protocol_s *protocol) { - http1pr_s *p = (http1pr_s *)protocol; - ssize_t i; - - i = fio_read(uuid, p->buf + p->buf_len, HTTP_MAX_HEADER_LENGTH - p->buf_len); - - if (i <= 0) - return; - p->buf_len += i; - - /* ensure future reads skip this first time HTTP/2.0 test */ - p->p.protocol.on_data = http1_on_data; - if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) { - FIO_LOG_WARNING("client claimed unsupported HTTP/2 prior knowledge."); - fio_close(uuid); - return; - } - - /* Finish handling the same way as the normal `on_data` */ - http1_consume_data(uuid, p); -} - -/* ***************************************************************************** -Public API -***************************************************************************** */ - -/** Creates an HTTP1 protocol object and handles any unread data in the buffer - * (if any). */ -fio_protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings, - void *unread_data, size_t unread_length) { - if (unread_data && unread_length > HTTP_MAX_HEADER_LENGTH) - return NULL; - http1pr_s *p = fio_malloc(sizeof(*p) + HTTP_MAX_HEADER_LENGTH); - // FIO_LOG_DEBUG("Allocated HTTP/1.1 protocol %p(%d)=>%p", (void *)uuid, - // (int)fio_uuid2fd(uuid), (void *)p); - FIO_ASSERT_ALLOC(p); - *p = (http1pr_s){ - .p.protocol = - { - .on_data = http1_on_data_first_time, - .on_close = http1_on_close, - .on_ready = http1_on_ready, - }, - .p.uuid = uuid, - .p.settings = settings, - .max_header_size = settings->max_header_size, - .is_client = settings->is_client, - }; - http_s_new(&p->request, &p->p, &HTTP1_VTABLE); - if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) { - memcpy(p->buf, unread_data, unread_length); - p->buf_len = unread_length; - } - fio_attach(uuid, &p->p.protocol); - if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) { - fio_force_event(uuid, FIO_EVENT_ON_DATA); - } - return &p->p.protocol; -} - -/** Manually destroys the HTTP1 protocol object. */ -void http1_destroy(fio_protocol_s *pr) { - http1pr_s *p = (http1pr_s *)pr; - http1_pr2handle(p).status = 0; - http_s_destroy(&http1_pr2handle(p), 0); - // FIO_LOG_DEBUG("Deallocating HTTP/1.1 protocol %p(%d)=>%p", (void - // *)p->p.uuid, (int)fio_uuid2fd(p->p.uuid), (void *)p); - fio_free(p); // occasional Windows crash bug -} diff --git a/ext/iodine/http1.h b/ext/iodine/http1.h deleted file mode 100644 index d9ee93b1..00000000 --- a/ext/iodine/http1.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2019 -License: MIT -*/ -#ifndef H_HTTP1_H -#define H_HTTP1_H - -#include - -#ifndef HTTP1_READ_BUFFER -/** - * The size of a single `read` command, it sets the limit for an HTTP/1.1 - * header line. - */ -#define HTTP1_READ_BUFFER (8 * 1024) /* ~8kb */ -#endif - -/** Creates an HTTP1 protocol object and handles any unread data in the buffer - * (if any). */ -fio_protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings, - void *unread_data, size_t unread_length); - -/** Manually destroys the HTTP1 protocol object. */ -void http1_destroy(fio_protocol_s *); - -/** returns the HTTP/1.1 protocol's VTable. */ -void *http1_vtable(void); - -#endif diff --git a/ext/iodine/http1_parser.h b/ext/iodine/http1_parser.h deleted file mode 100644 index 5fb50269..00000000 --- a/ext/iodine/http1_parser.h +++ /dev/null @@ -1,1835 +0,0 @@ -/* -Copyright: Boaz Segev, 2017-2020 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ - -/** -This is a callback based parser. It parses the skeleton of the HTTP/1.x protocol -and leaves most of the work (validation, error checks, etc') to the callbacks. - -This is an attempt to replace the existing HTTP/1.x parser with something easier -to maintain and that could be used for an HTTP/1.x client as well. -*/ -#include -#include -#include -#include -#include -#include - -/* ***************************************************************************** -Parser Settings -***************************************************************************** */ - -#ifndef HTTP_HEADERS_LOWERCASE -/** - * When defined, HTTP headers will be converted to lowercase and header - * searches will be case sensitive. - * - * This is highly recommended, required by facil.io and helps with HTTP/2 - * compatibility. - */ -#define HTTP_HEADERS_LOWERCASE 1 -#endif - -#ifndef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING -#define HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING 1 -#endif - -#ifndef FIO_MEMCHAR -/** Prefer a custom memchr implementation. Usually memchr is better. */ -#define FIO_MEMCHAR 0 -#endif - -#ifndef HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED -/** Preforms some optimizations assuming unaligned memory access is okay. */ -#define HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED 0 -#endif - -#ifndef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER -/** The RFC doesn't allow this, but this parser can manage... probably... */ -#define HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER 0 -#endif -/* ***************************************************************************** -Parser API -***************************************************************************** */ - -/** this struct contains the state of the parser. */ -typedef struct http1_parser_s { - struct http1_parser_protected_read_only_state_s { - long long content_length; /* negative values indicate chuncked data state */ - ssize_t read; /* total number of bytes read so far (body only) */ - uint8_t *next; /* the known position for the end of request/response */ - uint8_t reserved; /* for internal use */ - } state; -} http1_parser_s; - -#define HTTP1_PARSER_INIT \ - { \ - { 0 } \ - } - -/** - * Returns the amount of data actually consumed by the parser. - * - * The value 0 indicates there wasn't enough data to be parsed and the same - * buffer (with more data) should be resubmitted. - * - * A value smaller than the buffer size indicates that EITHER a request / - * response was detected OR that the leftover could not be consumed because more - * data was required. - * - * Simply resubmit the reminder of the data to continue parsing. - * - * A request / response callback automatically stops the parsing process, - * allowing the user to adjust or refresh the state of the data. - */ -static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length); - -/** Returns true if the parsing stopped after a complete request / response. */ -inline static int http1_complete(http1_parser_s *parser); - -/* ***************************************************************************** -Required Callbacks (MUST be implemented by including file) -***************************************************************************** */ -// clang-format off - -/** called when a request was received. */ -static int http1_on_request(http1_parser_s *parser); -/** called when a response was received. */ -static int http1_on_response(http1_parser_s *parser); -/** called when a request method is parsed. */ -static int -http1_on_method(http1_parser_s *parser, char *method, size_t method_len); -/** called when a response status is parsed. the status_str is the string - * without the prefixed numerical status indicator.*/ -static int http1_on_status(http1_parser_s *parser, size_t status, char *status_str, size_t len); -/** called when a request path (excluding query) is parsed. */ -static int http1_on_path(http1_parser_s *parser, char *path, size_t path_len); -/** called when a request path (excluding query) is parsed. */ -static int -http1_on_query(http1_parser_s *parser, char *query, size_t query_len); -/** called when a the HTTP/1.x version is parsed. */ -static int http1_on_version(http1_parser_s *parser, char *version, size_t len); -/** called when a header is parsed. */ -static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len, char *data, size_t data_len); -/** called when a body chunk is parsed. */ -static int -http1_on_body_chunk(http1_parser_s *parser, char *data, size_t data_len); -/** called when a protocol error occurred. */ -static int http1_on_error(http1_parser_s *parser); - -// clang-format on -/* ***************************************************************************** - - - - - - - - - - - - - - - - - - Implementation Details - - - - - - - - - - - - - - - - - -***************************************************************************** */ - -#if HTTP_HEADERS_LOWERCASE -#define HEADER_NAME_IS_EQ(var_name, const_name, len) \ - (!memcmp((var_name), (const_name), (len))) -#else -#define HEADER_NAME_IS_EQ(var_name, const_name, len) \ - (!strncasecmp((var_name), (const_name), (len))) -#endif - -#define HTTP1_P_FLAG_STATUS_LINE 1 -#define HTTP1_P_FLAG_HEADER_COMPLETE 2 -#define HTTP1_P_FLAG_COMPLETE 4 -#define HTTP1_P_FLAG_CLENGTH 8 -#define HTTP1_PARSER_BIT_16 16 -#define HTTP1_PARSER_BIT_32 32 -#define HTTP1_P_FLAG_CHUNKED 64 -#define HTTP1_P_FLAG_RESPONSE 128 - -#ifdef __cplusplus -#define _Bool bool -#endif -/* ***************************************************************************** -Seeking for characters in a string -***************************************************************************** */ - -#if FIO_MEMCHAR - -/** - * This seems to be faster on some systems, especially for smaller distances. - * - * On newer systems, `memchr` should be faster. - */ -static int seek2ch(uint8_t **buffer, - register uint8_t *const limit, - const uint8_t c) { - if (*buffer >= limit) - return 0; - if (**buffer == c) { - return 1; - } - -#if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED - /* too short for this mess */ - if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7))) - goto finish; - - /* align memory */ - { - const uint8_t *alignment = - (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8); - if (*buffer < alignment) - *buffer += 1; /* we already tested this char */ - if (limit >= alignment) { - while (*buffer < alignment) { - if (**buffer == c) { - return 1; - } - *buffer += 1; - } - } - } - const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7)); -#else - const uint8_t *limit64 = (uint8_t *)limit - 7; -#endif - uint64_t wanted1 = 0x0101010101010101ULL * c; - for (; *buffer < limit64; *buffer += 8) { - const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1); - const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu; - const uint64_t t1 = (eq1 & 0x8080808080808080llu); - if ((t0 & t1)) { - break; - } - } -#if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED -finish: -#endif - while (*buffer < limit) { - if (**buffer == c) { - return 1; - } - (*buffer)++; - } - return 0; -} - -#else - -/* a helper that seeks any char, converts it to NUL and returns 1 if found. */ -inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) { - /* This is library based alternative that is sometimes slower */ - if (*pos >= limit) - return 0; - if (**pos == ch) { - return 1; - } - uint8_t *tmp = memchr(*pos, ch, limit - (*pos)); - if (tmp) { - *pos = tmp; - return 1; - } - *pos = limit; - return 0; -} - -#endif - -/* a helper that seeks the EOL, converts it to NUL and returns it's length */ -inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) { - /* single char lookup using memchr might be better when target is far... */ - if (!seek2ch(pos, limit, '\n')) - return 0; - if ((*pos)[-1] == '\r') { - return 2; - } - return 1; -} - -/* ***************************************************************************** -Change a letter to lower case (latin only) -***************************************************************************** */ - -static uint8_t http_tolower(uint8_t c) { - if (((c >= 'A') & (c <= 'Z'))) - c |= 32; - return c; -} - -/* ***************************************************************************** -String to Number -***************************************************************************** */ - -/** Converts a String to a number using base 10 */ -static long long http1_atol(const uint8_t *buf, const uint8_t **end) { - register unsigned long long i = 0; - uint8_t inv = 0; - while (*buf == ' ' || *buf == '\t' || *buf == '\f') - ++buf; - while (*buf == '-' || *buf == '+') - inv ^= (*(buf++) == '-'); - while (i <= ((((~0ULL) >> 1) / 10)) && *buf >= '0' && *buf <= '9') { - i = i * 10; - i += *buf - '0'; - ++buf; - } - /* test for overflow */ - if (i >= (~((~0ULL) >> 1)) || (*buf >= '0' && *buf <= '9')) - i = (~0ULL >> 1); - if (inv) - i = 0ULL - i; - if (end) - *end = buf; - return i; -} - -/** Converts a String to a number using base 16, overflow limited to 113bytes */ -static long long http1_atol16(const uint8_t *buf, const uint8_t **end) { - register unsigned long long i = 0; - uint8_t inv = 0; - for (int limit_ = 0; - (*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32; - ++limit_) - ++buf; - for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_) - inv ^= (*(buf++) == '-'); - if (*buf == '0') - ++buf; - if ((*buf | 32) == 'x') - ++buf; - for (int limit_ = 0; (*buf == '0') && limit_ < 32; ++limit_) - ++buf; - while (!(i & (~((~(0ULL)) >> 4)))) { - if (*buf >= '0' && *buf <= '9') { - i <<= 4; - i |= *buf - '0'; - } else if ((*buf | 32) >= 'a' && (*buf | 32) <= 'f') { - i <<= 4; - i |= (*buf | 32) - ('a' - 10); - } else - break; - ++buf; - } - if (inv) - i = 0ULL - i; - if (end) - *end = buf; - return i; -} - -/* ***************************************************************************** -HTTP/1.1 parsre stages -***************************************************************************** */ - -inline static int http1_consume_response_line(http1_parser_s *parser, - uint8_t *start, - uint8_t *end) { - parser->state.reserved |= HTTP1_P_FLAG_RESPONSE; - uint8_t *tmp = start; - if (!seek2ch(&tmp, end, ' ')) - return -1; - if (http1_on_version(parser, (char *)start, tmp - start)) - return -1; - tmp = start = tmp + 1; - if (!seek2ch(&tmp, end, ' ')) - return -1; - if (http1_on_status(parser, - http1_atol(start, NULL), - (char *)(tmp + 1), - end - tmp)) - return -1; - return 0; -} - -inline static int http1_consume_request_line(http1_parser_s *parser, - uint8_t *start, - uint8_t *end) { - uint8_t *tmp = start; - uint8_t *host_start = NULL; - uint8_t *host_end = NULL; - if (!seek2ch(&tmp, end, ' ')) - return -1; - if (http1_on_method(parser, (char *)start, tmp - start)) - return -1; - tmp = start = tmp + 1; - if (start[0] == 'h' && start[1] == 't' && start[2] == 't' && - start[3] == 'p') { - if (start[4] == ':' && start[5] == '/' && start[6] == '/') { - /* Request URI is in long form... emulate Host header instead. */ - tmp = host_end = host_start = (start += 7); - } else if (start[4] == 's' && start[5] == ':' && start[6] == '/' && - start[7] == '/') { - /* Secure request is in long form... emulate Host header instead. */ - tmp = host_end = host_start = (start += 8); - } else - goto review_path; - if (!seek2ch(&tmp, end, ' ')) - return -1; - *tmp = ' '; - if (!seek2ch(&host_end, tmp, '/')) { - if (http1_on_path(parser, (char *)"/", 1)) - return -1; - goto start_version; - } - host_end[0] = '/'; - start = host_end; - } -review_path: - tmp = start; - if (seek2ch(&tmp, end, '?')) { - if (http1_on_path(parser, (char *)start, tmp - start)) - return -1; - tmp = start = tmp + 1; - if (!seek2ch(&tmp, end, ' ')) - return -1; - if (tmp - start > 0 && http1_on_query(parser, (char *)start, tmp - start)) - return -1; - } else { - tmp = start; - if (!seek2ch(&tmp, end, ' ')) - return -1; - if (http1_on_path(parser, (char *)start, tmp - start)) - return -1; - } -start_version: - start = tmp + 1; - if (start + 5 >= end) /* require "HTTP/" */ - return -1; - if (http1_on_version(parser, (char *)start, end - start)) - return -1; - /* */ - if (host_start && http1_on_header(parser, - (char *)"host", - 4, - (char *)host_start, - host_end - host_start)) - return -1; - return 0; -} - -#if !HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER -inline /* inline the function if it's short enough */ -#endif - static int - http1_consume_header_transfer_encoding(http1_parser_s *parser, - uint8_t *start, - uint8_t *end_name, - uint8_t *start_value, - uint8_t *end) { - /* this removes the `chunked` marker and prepares to "unchunk" the data */ - while (start_value < end && (end[-1] == ',' || end[-1] == ' ')) - --end; - if ((end - start_value) == 7 && -#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED - (((uint32_t *)(start_value))[0] | 0x20202020) == - ((uint32_t *)"chun")[0] && - (((uint32_t *)(start_value + 3))[0] | 0x20202020) == - ((uint32_t *)"nked")[0] -#else - ((start_value[0] | 32) == 'c' & (start_value[1] | 32) == 'h' & - (start_value[2] | 32) == 'u' & (start_value[3] | 32) == 'n' & - (start_value[4] | 32) == 'k' & (start_value[5] | 32) == 'e' & - (start_value[6] | 32) == 'd') -#endif - ) { - /* simple case,only `chunked` as a value */ - parser->state.reserved |= HTTP1_P_FLAG_CHUNKED; - parser->state.content_length = 0; - start_value += 7; - while (start_value < end && (*start_value == ',' || *start_value == ' ')) - ++start_value; - if (!(end - start_value)) - return 0; - } else if ((end - start_value) > 7 && - ((end[(-7 + 0)] | 32) == 'c' & (end[(-7 + 1)] | 32) == 'h' & - (end[(-7 + 2)] | 32) == 'u' & (end[(-7 + 3)] | 32) == 'n' & - (end[(-7 + 4)] | 32) == 'k' & (end[(-7 + 5)] | 32) == 'e' & - (end[(-7 + 6)] | 32) == 'd')) { - /* simple case,`chunked` at the end of list (RFC required) */ - parser->state.reserved |= HTTP1_P_FLAG_CHUNKED; - parser->state.content_length = 0; - end -= 7; - while (start_value < end && (end[-1] == ',' || end[-1] == ' ')) - --end; - if (!(end - start_value)) - return 0; - } -#if HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER /* RFC disallows this */ - else if ((end - start_value) > 7 && (end - start_value) < 256) { - /* complex case, `the, chunked, marker, is in the middle of list */ - uint8_t val[256]; - size_t val_len = 0; - while (start_value < end && val_len < 256) { - if ((end - start_value) >= 7) { - if ( -#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED - (((uint32_t *)(start_value))[0] | 0x20202020) == - ((uint32_t *)"chun")[0] && - (((uint32_t *)(start_value + 3))[0] | 0x20202020) == - ((uint32_t *)"nked")[0] -#else - ((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' && - (start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' && - (start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' && - (start_value[6] | 32) == 'd') -#endif - - ) { - parser->state.reserved |= HTTP1_P_FLAG_CHUNKED; - parser->state.content_length = 0; - start_value += 7; - /* skip comma / white space */ - while (start_value < end && - (*start_value == ',' || *start_value == ' ')) - ++start_value; - continue; - } - } - /* copy value */ - while (start_value < end && val_len < 256 && start_value[0] != ',') { - val[val_len++] = *start_value; - ++start_value; - } - /* copy comma */ - if (start_value[0] == ',' && val_len < 256) { - val[val_len++] = *start_value; - ++start_value; - } - /* skip spaces */ - while (start_value < end && start_value[0] == ' ') { - ++start_value; - } - } - if (val_len < 256) { - while (start_value < end && val_len < 256) { - val[val_len++] = *start_value; - ++start_value; - } - if (val_len < 255) - val[val_len] = 0; - } - /* perform callback with `val` or indicate error */ - if (val_len == 256 || (val_len && http1_on_header(parser, - (char *)start, - (end_name - start), - (char *)val, - val_len))) - return -1; - return 0; - } -#endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */ - /* perform callback */ - if (http1_on_header(parser, - (char *)start, - (end_name - start), - (char *)start_value, - end - start_value)) - return -1; - return 0; -} -inline static int http1_consume_header_top(http1_parser_s *parser, - uint8_t *start, - uint8_t *end_name, - uint8_t *start_value, - uint8_t *end) { - if ((end_name - start) == 14 && -#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE - *((uint64_t *)start) == *((uint64_t *)"content-") && - *((uint64_t *)(start + 6)) == *((uint64_t *)"t-length") -#else - HEADER_NAME_IS_EQ((char *)start, "content-length", 14) -#endif - ) { - /* handle the special `content-length` header */ - if ((parser->state.reserved & HTTP1_P_FLAG_CHUNKED)) - return 0; /* ignore if `chunked` */ - long long old_clen = parser->state.content_length; - parser->state.content_length = http1_atol(start_value, NULL); - if ((parser->state.reserved & HTTP1_P_FLAG_CLENGTH) && - old_clen != parser->state.content_length) { - /* content-length header repeated with conflict */ - return -1; - } - parser->state.reserved |= HTTP1_P_FLAG_CLENGTH; - } else if ((end_name - start) == 17 && (end - start_value) >= 7 && - !parser->state.content_length && -#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE - ((*((uint64_t *)start) == *((uint64_t *)"transfer")) & - ((*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")) & - (start[16] == 'g'))) -#else - HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17) -#endif - ) { - /* handle the special `transfer-encoding: chunked` header */ - return http1_consume_header_transfer_encoding(parser, - start, - end_name, - start_value, - end); - } - /* perform callback */ - if (http1_on_header(parser, - (char *)start, - (end_name - start), - (char *)start_value, - end - start_value)) - return -1; - return 0; -} - -inline static int http1_consume_header_trailer(http1_parser_s *parser, - uint8_t *start, - uint8_t *end_name, - uint8_t *start_value, - uint8_t *end) { - /* white listed trailer names */ - const struct { - char *name; - long len; - } http1_trailer_allowed_list[] = { - {(char *)"server-timing", 13}, /* specific for client data... */ - {NULL, 0}, /* end of list marker */ - }; - if ((end_name - start) > 1 && start[0] == 'x') { - /* X- headers are allowed */ - goto allowed_list; - } - for (size_t i = 0; http1_trailer_allowed_list[i].name; ++i) { - if ((long)(end_name - start) == http1_trailer_allowed_list[i].len && - HEADER_NAME_IS_EQ((char *)start, - http1_trailer_allowed_list[i].name, - http1_trailer_allowed_list[i].len)) { - /* header disallowed here */ - goto allowed_list; - } - } - return 0; -allowed_list: - /* perform callback */ - if (http1_on_header(parser, - (char *)start, - (end_name - start), - (char *)start_value, - end - start_value)) - return -1; - return 0; -} - -inline static int http1_consume_header(http1_parser_s *parser, - uint8_t *start, - uint8_t *end) { - static const _Bool forbidden_name_chars[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t *end_name = start; - /* divide header name from data */ - if (!seek2ch(&end_name, end, ':')) - return -1; - if (end_name[-1] == ' ' || end_name[-1] == '\t') - return -1; - if (forbidden_name_chars[start[0] & 0xFF]) - return -1; -#if HTTP_HEADERS_LOWERCASE - for (uint8_t *t = start; t < end_name; t++) - *t = http_tolower(*t); -#endif - uint8_t *start_value = end_name + 1; - // clear away leading white space from value. - while (start_value < end && - ((start_value[0] == ' ') | (start_value[0] == '\t'))) { - start_value++; - }; - // clear away added white space from value. - while (start_value < end && ((end[0] == ' ') | (end[0] == '\t'))) { - end++; - }; - return (parser->state.read ? http1_consume_header_trailer - : http1_consume_header_top)(parser, - start, - end_name, - start_value, - end); -} - -/* ***************************************************************************** -HTTP/1.1 Body handling -***************************************************************************** */ - -inline static int http1_consume_body_streamed(http1_parser_s *parser, - void *buffer, - size_t length, - uint8_t **start) { - uint8_t *end = *start + parser->state.content_length - parser->state.read; - uint8_t *const stop = ((uint8_t *)buffer) + length; - if (end > stop) - end = stop; - if (end > *start && - http1_on_body_chunk(parser, (char *)(*start), end - *start)) - return -1; - parser->state.read += (end - *start); - *start = end; - if (parser->state.content_length <= parser->state.read) - parser->state.reserved |= HTTP1_P_FLAG_COMPLETE; - return 0; -} - -inline static int http1_consume_body_chunked(http1_parser_s *parser, - void *buffer, - size_t length, - uint8_t **start) { - uint8_t *const stop = ((uint8_t *)buffer) + length; - uint8_t *end = *start; - while (*start < stop) { - if (parser->state.content_length == 0) { - if (end + 2 >= stop) - return 0; - if ((end[0] == '\r' && end[1] == '\n')) { - /* remove tailing EOL that wasn't processed and retest */ - end += 2; - *start = end; - if (end + 2 >= stop) - return 0; - } - long long chunk_len = http1_atol16(end, (const uint8_t **)&end); - if (end + 2 > stop) /* overflowed? */ - return 0; - if ((end[0] != '\r') | (end[1] != '\n') | (chunk_len < 0)) - return -1; /* required EOL after content length */ - end += 2; - - parser->state.content_length = 0 - chunk_len; - *start = end; - if (parser->state.content_length == 0) { - /* all chunked data was parsed */ - /* update content-length */ - parser->state.content_length = parser->state.read; -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { /* add virtual header ... ? */ - char buf[512]; - size_t buf_len = 512; - size_t tmp_len = parser->state.read; - buf[--buf_len] = 0; - if (tmp_len) { - while (tmp_len) { - size_t mod = tmp_len / 10; - buf[--buf_len] = '0' + (tmp_len - (mod * 10)); - tmp_len = mod; - } - } else { - buf[--buf_len] = '0'; - } - if (!(parser->state.reserved & HTTP1_P_FLAG_CLENGTH) && - http1_on_header(parser, - (char *)"content-length", - 14, - (char *)buf + buf_len, - 511 - buf_len)) { - return -1; - } - } -#endif - /* consume trailing EOL */ - if (*start + 2 <= stop && (start[0][0] == '\r' || start[0][0] == '\n')) - *start += 1 + (start[0][1] == '\r' || start[0][1] == '\n'); - else { - /* remove the "headers complete" and "trailer" flags */ - parser->state.reserved = - HTTP1_P_FLAG_STATUS_LINE | HTTP1_P_FLAG_CLENGTH; - return -2; - } - /* the parsing complete flag */ - parser->state.reserved |= HTTP1_P_FLAG_COMPLETE; - return 0; - } - } - end = *start + (0 - parser->state.content_length); - if (end > stop) - end = stop; - if (end > *start && - http1_on_body_chunk(parser, (char *)(*start), end - *start)) { - return -1; - } - parser->state.read += (end - *start); - parser->state.content_length += (end - *start); - *start = end; - } - return 0; -} - -inline static int http1_consume_body(http1_parser_s *parser, - void *buffer, - size_t length, - uint8_t **start) { - if (parser->state.content_length > 0 && - parser->state.content_length > parser->state.read) { - /* normal, streamed data */ - return http1_consume_body_streamed(parser, buffer, length, start); - } else if (parser->state.content_length <= 0 && - (parser->state.reserved & HTTP1_P_FLAG_CHUNKED)) { - /* chuncked encoding */ - return http1_consume_body_chunked(parser, buffer, length, start); - } else { - /* nothing to do - parsing complete */ - parser->state.reserved |= HTTP1_P_FLAG_COMPLETE; - } - return 0; -} - -/* ***************************************************************************** -HTTP/1.1 parsre function -***************************************************************************** */ -#if DEBUG -#include -#define HTTP1_ASSERT assert -#else -#define HTTP1_ASSERT(...) -#endif - -/** - * Returns the amount of data actually consumed by the parser. - * - * The value 0 indicates there wasn't enough data to be parsed and the same - * buffer (with more data) should be resubmitted. - * - * A value smaller than the buffer size indicates that EITHER a request / - * response was detected OR that the leftover could not be consumed because more - * data was required. - * - * Simply resubmit the reminder of the data to continue parsing. - * - * A request / response callback automatically stops the parsing process, - * allowing the user to adjust or refresh the state of the data. - */ -static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length) { - if (!length) - return 0; - HTTP1_ASSERT(parser && buffer); - parser->state.next = NULL; - uint8_t *start = (uint8_t *)buffer; - uint8_t *end = start; - uint8_t *const stop = start + length; - uint8_t eol_len = 0; -#define HTTP1_CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)buffer)) - -re_eval: - switch ((parser->state.reserved & 7)) { - - case 0: /* request / response line */ - /* clear out any leading white space */ - while ((start < stop) && - (*start == '\r' || *start == '\n' || *start == ' ' || *start == 0)) { - ++start; - } - end = start; - /* make sure the whole line is available*/ - if (!(eol_len = seek2eol(&end, stop))) - return HTTP1_CONSUMED; - - if (start[0] == 'H' && start[1] == 'T' && start[2] == 'T' && - start[3] == 'P') { - /* HTTP response */ - if (http1_consume_response_line(parser, start, end - eol_len + 1)) - goto error; - } else if (http_tolower(start[0]) >= 'a' && http_tolower(start[0]) <= 'z') { - /* HTTP request */ - if (http1_consume_request_line(parser, start, end - eol_len + 1)) - goto error; - } else - goto error; - end = start = end + 1; - parser->state.reserved |= HTTP1_P_FLAG_STATUS_LINE; - - /* fallthrough */ - case 1: /* headers */ - do { - if (start >= stop) - return HTTP1_CONSUMED; /* buffer ended on header line */ - if ((*start == '\n') | - (start + 1 < stop && ((start[0] == '\r') & (start[1] == '\n')))) { - goto finished_headers; /* empty line, end of headers */ - } - end = start; - if (!(eol_len = seek2eol(&end, stop))) - return HTTP1_CONSUMED; - if (http1_consume_header(parser, start, end - eol_len + 1)) - goto error; - end = start = end + 1; - } while ((parser->state.reserved & HTTP1_P_FLAG_HEADER_COMPLETE) == 0); - finished_headers: - ++start; - if (*start == '\n') - ++start; - end = start; - parser->state.reserved |= HTTP1_P_FLAG_HEADER_COMPLETE; - /* fall through */ - case (HTTP1_P_FLAG_HEADER_COMPLETE | HTTP1_P_FLAG_STATUS_LINE): - /* request body */ - { - int t3 = http1_consume_body(parser, buffer, length, &start); - switch (t3) { - case -1: - goto error; - case -2: - goto re_eval; - } - break; - } - } - /* are we done ? */ - if (parser->state.reserved & HTTP1_P_FLAG_COMPLETE) { - parser->state.next = start; - if (((parser->state.reserved & HTTP1_P_FLAG_RESPONSE) - ? http1_on_response - : http1_on_request)(parser)) - goto error; - parser->state = (struct http1_parser_protected_read_only_state_s){0}; - } - return HTTP1_CONSUMED; -error: - http1_on_error(parser); - parser->state = (struct http1_parser_protected_read_only_state_s){0}; - return length; -#undef HTTP1_CONSUMED -} - -/** Returns true if the parsing stopped after a complete request / response. */ -inline static int http1_complete(http1_parser_s *parser) { - return !parser->state.reserved; -} - -/* ***************************************************************************** - - - - -HTTP/1.1 TESTING - - - - -***************************************************************************** */ -#ifdef HTTP1_TEST_PARSER -#include "signal.h" - -#define HTTP1_TEST_ASSERT(cond, ...) \ - if (!(cond)) { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - kill(0, SIGINT); \ - exit(-1); \ - } - -static size_t http1_test_pos; -static char http1_test_temp_buf[8092]; -static size_t http1_test_temp_buf_pos; -static struct { - char *test_name; - char *request[16]; - struct { - char body[1024]; - size_t body_len; - const char *method; - ssize_t status; - const char *path; - const char *query; - const char *version; - struct http1_test_header_s { - const char *name; - size_t name_len; - const char *val; - size_t val_len; - } headers[12]; - } result, expect; -} http1_test_data[] = { - { - .test_name = "simple empty request", - .request = {"GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "GET", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - {.name = "host", - .name_len = 4, - .val = "localhost", - .val_len = 9}, - }, - }, - }, - { - .test_name = "space before header data", - .request = {"POST /my/path HTTP/1.2\r\nHost: localhost\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "POST", - .path = "/my/path", - .query = NULL, - .version = "HTTP/1.2", - .headers = - { - {.name = "host", - .name_len = 4, - .val = "localhost", - .val_len = 9}, - }, - }, - }, - { - .test_name = "simple request, fragmented header (in new line)", - .request = {"GET / HTTP/1.1\r\n", "Host:localhost\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "GET", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - {.name = "host", - .name_len = 4, - .val = "localhost", - .val_len = 9}, - }, - }, - }, - { - .test_name = "request with query", - .request = {"METHOD /path?q=query HTTP/1.3\r\nHost:localhost\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "METHOD", - .path = "/path", - .query = "q=query", - .version = "HTTP/1.3", - .headers = - { - {.name = "host", - .name_len = 4, - .val = "localhost", - .val_len = 9}, - }, - }, - }, - { - .test_name = "mid-fragmented header", - .request = {"GET / HTTP/1.1\r\nHost: loca", "lhost\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "GET", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - {.name = "host", - .name_len = 4, - .val = "localhost", - .val_len = 9}, - }, - }, - }, - { - .test_name = "simple with body", - .request = {"GET / HTTP/1.1\r\nHost:with body\r\n" - "Content-lEnGth: 5\r\n\r\nHello"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "GET", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, - }, - }, - }, - { - .test_name = "fragmented body", - .request = {"GET / HTTP/1.1\r\nHost:with body\r\n", - "Content-lEnGth: 5\r\n\r\nHe", - "llo"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "GET", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, - }, - }, - }, - { - .test_name = "fragmented body 2 (cuts EOL)", - .request = {"POST / HTTP/1.1\r\nHost:with body\r\n", - "Content-lEnGth: 5\r\n", - "\r\n", - "He", - "llo"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, - }, - }, - }, - { - .test_name = "chunked body (simple)", - .request = {"POST / HTTP/1.1\r\nHost:with body\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5\r\n" - "Hello" - "\r\n0\r\n\r\n"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, - { - .test_name = "chunked body (empty)", - .request = {"POST / HTTP/1.1\r\nHost:with body\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n0\r\n\r\n"}, - .expect = - { - .body = "", - .body_len = 0, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "0", - .val_len = 1, - }, -#endif - }, - }, - }, - { - .test_name = "chunked body (end of list)", - .request = {"POST / HTTP/1.1\r\nHost:with body\r\n" - "Transfer-Encoding: gzip, foo, chunked\r\n" - "\r\n" - "5\r\n" - "Hello" - "\r\n0\r\n\r\n"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, - { - .name = "transfer-encoding", - .name_len = 17, - .val = "gzip, foo", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, -#if HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER - { - .test_name = "chunked body (middle of list - RFC violation)", - .request = {"POST / HTTP/1.1\r\nHost:with body\r\n" - "Transfer-Encoding: gzip, chunked, foo\r\n" - "\r\n", - "5\r\n" - "Hello" - "\r\n0\r\n\r\n"}, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, - { - .name = "transfer-encoding", - .name_len = 17, - .val = "gzip,foo", - .val_len = 8, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, -#endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */ - { - .test_name = "chunked body (fragmented)", - .request = - { - "POST / HTTP/1.1\r\nHost:with body\r\n", - "Transfer-Encoding: chunked\r\n", - "\r\n" - "5\r\n", - "He", - "llo", - "\r\n0\r\n\r\n", - }, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, - { - .test_name = "chunked body (fragmented + multi-message)", - .request = - { - "POST / HTTP/1.1\r\nHost:with body\r\n", - "Transfer-Encoding: chunked\r\n", - "\r\n" - "2\r\n", - "He", - "3\r\nl", - "lo", - "\r\n0\r\n\r\n", - }, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, - { - .test_name = "chunked body (fragmented + broken-multi-message)", - .request = - { - "POST / HTTP/1.1\r\nHost:with body\r\n", - "Transfer-Encoding: chunked\r\n", - "\r\n", - "2\r\n", - "H", - "e", - "3\r\nl", - "l" - "o", - "\r\n0\r\n\r\n", - }, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - }, - }, - }, - { - .test_name = "chunked body (...longer + trailer + empty value...)", - .request = - { - "POST / HTTP/1.1\r\nHost:with body\r\n", - "Transfer-Encoding: chunked\r\n", - "\r\n", - "4\r\n", - "Wiki\r\n", - "5\r\n", - "pedia\r\n", - "E\r\n", - " in\r\n", - "\r\n", - "chunks.\r\n", - "0\r\n", - "X-Foo: trailer\r\n", - "sErvEr-tiMing: \r\n", - "\r\n", - }, - .expect = - { - .body = "Wikipedia in\r\n\r\nchunks.", - .body_len = 23, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "23", - .val_len = 2, - }, -#endif - { - .name = "x-foo", - .name_len = 5, - .val = "trailer", - .val_len = 7, - }, - { - .name = "server-timing", - .name_len = 13, - .val = "", - .val_len = 0, - }, - }, - }, - }, - { - .test_name = "chunked body (fragmented + surprize trailer)", - .request = - { - "POST / HTTP/1.1\r\nHost:with body\r\n", - "Transfer-Encoding: chunked\r\n", - "\r\n" - "5\r\n", - "He", - "llo", - "\r\n0\r\nX-Foo: trailer\r\n\r\n", - }, - .expect = - { - .body = "Hello", - .body_len = 5, - .method = "POST", - .path = "/", - .query = NULL, - .version = "HTTP/1.1", - .headers = - { - { - .name = "host", - .name_len = 4, - .val = "with body", - .val_len = 9, - }, -#if HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING - { - .name = "content-length", - .name_len = 14, - .val = "5", - .val_len = 1, - }, -#endif - { - .name = "x-foo", - .name_len = 5, - .val = "trailer", - .val_len = 7, - }, - }, - }, - }, - /* stop marker */ - { - .request = {NULL}, - }, -}; - -/** called when a request was received. */ -static int http1_on_request(http1_parser_s *parser) { - (void)parser; - return 0; -} -/** called when a response was received. */ -static int http1_on_response(http1_parser_s *parser) { - (void)parser; - return 0; -} -/** called when a request method is parsed. */ -static int http1_on_method(http1_parser_s *parser, - char *method, - size_t method_len) { - (void)parser; - http1_test_data[http1_test_pos].result.method = method; - HTTP1_TEST_ASSERT(method_len == - strlen(http1_test_data[http1_test_pos].expect.method), - "method_len test error for: %s", - http1_test_data[http1_test_pos].test_name); - return 0; -} -/** called when a response status is parsed. the status_str is the string - * without the prefixed numerical status indicator.*/ -static int http1_on_status(http1_parser_s *parser, - size_t status, - char *status_str, - size_t len) { - (void)parser; - http1_test_data[http1_test_pos].result.status = status; - http1_test_data[http1_test_pos].result.method = status_str; - HTTP1_TEST_ASSERT(len == - strlen(http1_test_data[http1_test_pos].expect.method), - "status length test error for: %s", - http1_test_data[http1_test_pos].test_name); - return 0; -} -/** called when a request path (excluding query) is parsed. */ -static int http1_on_path(http1_parser_s *parser, char *path, size_t len) { - (void)parser; - http1_test_data[http1_test_pos].result.path = path; - HTTP1_TEST_ASSERT(len == strlen(http1_test_data[http1_test_pos].expect.path), - "path length test error for: %s", - http1_test_data[http1_test_pos].test_name); - return 0; -} -/** called when a request path (excluding query) is parsed. */ -static int http1_on_query(http1_parser_s *parser, char *query, size_t len) { - (void)parser; - http1_test_data[http1_test_pos].result.query = query; - HTTP1_TEST_ASSERT(len == strlen(http1_test_data[http1_test_pos].expect.query), - "query length test error for: %s", - http1_test_data[http1_test_pos].test_name); - return 0; -} -/** called when a the HTTP/1.x version is parsed. */ -static int http1_on_version(http1_parser_s *parser, char *version, size_t len) { - (void)parser; - http1_test_data[http1_test_pos].result.version = version; - HTTP1_TEST_ASSERT(len == - strlen(http1_test_data[http1_test_pos].expect.version), - "version length test error for: %s", - http1_test_data[http1_test_pos].test_name); - return 0; -} -/** called when a header is parsed. */ -static int http1_on_header(http1_parser_s *parser, - char *name, - size_t name_len, - char *val, - size_t val_len) { - (void)parser; - size_t pos = 0; - while (pos < 12 && http1_test_data[http1_test_pos].result.headers[pos].name) - ++pos; - HTTP1_TEST_ASSERT(pos < 12, - "header result overflow for: %s", - http1_test_data[http1_test_pos].test_name); - memcpy(http1_test_temp_buf + http1_test_temp_buf_pos, name, name_len); - name = http1_test_temp_buf + http1_test_temp_buf_pos; - http1_test_temp_buf_pos += name_len; - http1_test_temp_buf[http1_test_temp_buf_pos++] = 0; - memcpy(http1_test_temp_buf + http1_test_temp_buf_pos, val, val_len); - val = http1_test_temp_buf + http1_test_temp_buf_pos; - http1_test_temp_buf_pos += val_len; - http1_test_temp_buf[http1_test_temp_buf_pos++] = 0; - http1_test_data[http1_test_pos].result.headers[pos].name = name; - http1_test_data[http1_test_pos].result.headers[pos].name_len = name_len; - http1_test_data[http1_test_pos].result.headers[pos].val = val; - http1_test_data[http1_test_pos].result.headers[pos].val_len = val_len; - return 0; -} -/** called when a body chunk is parsed. */ -static int http1_on_body_chunk(http1_parser_s *parser, - char *data, - size_t data_len) { - (void)parser; - http1_test_data[http1_test_pos] - .result.body[http1_test_data[http1_test_pos].result.body_len] = 0; - HTTP1_TEST_ASSERT(data_len + - http1_test_data[http1_test_pos].result.body_len <= - http1_test_data[http1_test_pos].expect.body_len, - "body overflow for: %s" - "\r\n Expect:\n%s\nGot:\n%s%s\n", - http1_test_data[http1_test_pos].test_name, - http1_test_data[http1_test_pos].expect.body, - http1_test_data[http1_test_pos].result.body, - data); - memcpy(http1_test_data[http1_test_pos].result.body + - http1_test_data[http1_test_pos].result.body_len, - data, - data_len); - http1_test_data[http1_test_pos].result.body_len += data_len; - http1_test_data[http1_test_pos] - .result.body[http1_test_data[http1_test_pos].result.body_len] = 0; - return 0; -} - -/** called when a protocol error occurred. */ -static int http1_on_error(http1_parser_s *parser) { - (void)parser; - http1_test_data[http1_test_pos].result.status = -1; - return 0; -} - -#define HTTP1_TEST_STRING_FIELD(field, i) \ - HTTP1_TEST_ASSERT((!http1_test_data[i].expect.field && \ - !http1_test_data[i].result.field) || \ - http1_test_data[i].expect.field && \ - http1_test_data[i].result.field && \ - !memcmp(http1_test_data[i].expect.field, \ - http1_test_data[i].result.field, \ - strlen(http1_test_data[i].expect.field)), \ - "string field error for %s - " #field " \n%s\n%s", \ - http1_test_data[i].test_name, \ - http1_test_data[i].expect.field, \ - http1_test_data[i].result.field); -static void http1_parser_test(void) { - http1_test_pos = 0; - struct { - const char *str; - long long num; - long long (*fn)(const uint8_t *, const uint8_t **); - } atol_test[] = { - { - .str = "0", - .num = 0, - .fn = http1_atol, - }, - { - .str = "-0", - .num = 0, - .fn = http1_atol, - }, - { - .str = "1", - .num = 1, - .fn = http1_atol, - }, - { - .str = "-1", - .num = -1, - .fn = http1_atol, - }, - { - .str = "123456789", - .num = 123456789, - .fn = http1_atol, - }, - { - .str = "-123456789", - .num = -123456789, - .fn = http1_atol, - }, - { - .str = "0x0", - .num = 0, - .fn = http1_atol16, - }, - { - .str = "-0x0", - .num = 0, - .fn = http1_atol16, - }, - { - .str = "-0x1", - .num = -1, - .fn = http1_atol16, - }, - { - .str = "-f", - .num = -15, - .fn = http1_atol16, - }, - { - .str = "-20", - .num = -32, - .fn = http1_atol16, - }, - { - .str = "0xf0EAf9ff", - .num = 0xf0eaf9ff, - .fn = http1_atol16, - }, - /* stop marker */ - { - .str = NULL, - }, - }; - fprintf(stderr, "* testing string=>number conversion\n"); - for (size_t i = 0; atol_test[i].str; ++i) { - const uint8_t *end; - fprintf(stderr, " %s", atol_test[i].str); - HTTP1_TEST_ASSERT(atol_test[i].fn((const uint8_t *)atol_test[i].str, - &end) == atol_test[i].num, - "\nhttp1_atol error: %s != %lld", - atol_test[i].str, - atol_test[i].num); - HTTP1_TEST_ASSERT((char *)end == - (atol_test[i].str + strlen(atol_test[i].str)), - "\nhttp1_atol error: didn't end after (%s): %s", - atol_test[i].str, - (char *)end) - } - fprintf(stderr, "\n"); - for (unsigned long long i = 1; i; i <<= 1) { - char tmp[128]; - size_t tmp_len = sprintf(tmp, "%llx", i); - uint8_t *pos = (uint8_t *)tmp; - HTTP1_TEST_ASSERT(http1_atol16(pos, (const uint8_t **)&pos) == - (long long)i && - pos == (uint8_t *)(tmp + tmp_len), - "http1_atol16 roundtrip error."); - } - - for (size_t i = 0; http1_test_data[i].request[0]; ++i) { - fprintf(stderr, "* http1 parser test: %s\n", http1_test_data[i].test_name); - /* parse each request / response */ - http1_parser_s parser = HTTP1_PARSER_INIT; - char buf[4096]; - size_t r = 0; - size_t w = 0; - http1_test_temp_buf_pos = 0; - for (int j = 0; http1_test_data[i].request[j]; ++j) { - memcpy(buf + w, - http1_test_data[i].request[j], - strlen(http1_test_data[i].request[j])); - w += strlen(http1_test_data[i].request[j]); - size_t p = http1_parse(&parser, buf + r, w - r); - r += p; - HTTP1_TEST_ASSERT(r <= w, "parser consumed more than the buffer holds!"); - } - /* test each request / response before overwriting the buffer */ - HTTP1_TEST_STRING_FIELD(body, i); - HTTP1_TEST_STRING_FIELD(method, i); - HTTP1_TEST_STRING_FIELD(path, i); - HTTP1_TEST_STRING_FIELD(version, i); - r = 0; - while (http1_test_data[i].result.headers[r].name) { - HTTP1_TEST_STRING_FIELD(headers[r].name, i); - HTTP1_TEST_STRING_FIELD(headers[r].val, i); - HTTP1_TEST_ASSERT(http1_test_data[i].expect.headers[r].val_len == - http1_test_data[i].result.headers[r].val_len && - http1_test_data[i].expect.headers[r].name_len == - http1_test_data[i].result.headers[r].name_len, - "--- name / value length error"); - ++r; - } - HTTP1_TEST_ASSERT(!http1_test_data[i].expect.headers[r].name, - "Expected header missing:\n\t%s: %s", - http1_test_data[i].expect.headers[r].name, - http1_test_data[i].expect.headers[r].val); - /* advance counter */ - ++http1_test_pos; - } -} - -#endif diff --git a/ext/iodine/http_internal.c b/ext/iodine/http_internal.c deleted file mode 100644 index 5fab78ef..00000000 --- a/ext/iodine/http_internal.c +++ /dev/null @@ -1,1279 +0,0 @@ -/* -Copyright: Boaz Segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include - -#include - -/* ***************************************************************************** -Internal Request / Response Handlers -***************************************************************************** */ - -static uint64_t http_upgrade_hash = 0; -/** Use this function to handle HTTP requests.*/ -void http_on_request_handler______internal(http_s *h, - http_settings_s *settings) { - if (!http_upgrade_hash) - http_upgrade_hash = fiobj_hash_string("upgrade", 7); - h->udata = settings->udata; - - static uint64_t host_hash = 0; - if (!host_hash) - host_hash = fiobj_hash_string("host", 4); - - if (1) { - /* test for Host header and avoid duplicates */ - FIOBJ tmp = fiobj_hash_get2(h->headers, host_hash); - if (!tmp) - goto missing_host; - if (FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY)) { - fiobj_hash_set(h->headers, HTTP_HEADER_HOST, fiobj_ary_pop(tmp)); - } - } - - FIOBJ t = fiobj_hash_get2(h->headers, http_upgrade_hash); - if (t) - goto upgrade; - - if (fiobj_iseq( - fiobj_hash_get2(h->headers, fiobj_obj2hash(HTTP_HEADER_ACCEPT)), - HTTP_HVALUE_SSE_MIME)) - goto eventsource; - if (settings->public_folder && - (fiobj_obj2cstr(h->method).len != 4 || strncasecmp("post", fiobj_obj2cstr(h->method).data, 4))) { - fio_str_info_s path_str = fiobj_obj2cstr(h->path); - if (!http_sendfile2(h, settings->public_folder, - settings->public_folder_length, path_str.data, - path_str.len)) { - return; - } - } - settings->on_request(h); - return; - -upgrade: - if (1) { - fiobj_dup(t); /* allow upgrade name access after http_finish */ - fio_str_info_s val = fiobj_obj2cstr(t); - if (val.data[0] == 'h' && val.data[1] == '2') { - http_send_error(h, 400); - } else { - settings->on_upgrade(h, val.data, val.len); - } - fiobj_free(t); - return; - } -eventsource: - settings->on_upgrade(h, (char *)"sse", 3); - return; -missing_host: - FIO_LOG_DEBUG("missing Host header"); - http_send_error(h, 400); - return; -} - -void http_on_response_handler______internal(http_s *h, - http_settings_s *settings) { - if (!http_upgrade_hash) - http_upgrade_hash = fiobj_hash_string("upgrade", 7); - h->udata = settings->udata; - FIOBJ t = fiobj_hash_get2(h->headers, http_upgrade_hash); - if (t == FIOBJ_INVALID) { - settings->on_response(h); - return; - } else { - fio_str_info_s val = fiobj_obj2cstr(t); - settings->on_upgrade(h, val.data, val.len); - } -} - -/* ***************************************************************************** -Internal helpers -***************************************************************************** */ - -int http_send_error2(size_t error, intptr_t uuid, http_settings_s *settings) { - if (!uuid || !settings || !error) - return -1; - fio_protocol_s *pr = http1_new(uuid, settings, NULL, 0); - http_s *r = fio_malloc(sizeof(*r)); - FIO_ASSERT_ALLOC(r); - FIO_ASSERT(pr, "Couldn't allocate response object for error report.") - http_s_new(r, (http_fio_protocol_s *)pr, http1_vtable()); - int ret = http_send_error(r, error); - fio_close(uuid); - return ret; -} - -/* ***************************************************************************** -Library initialization -***************************************************************************** */ - -FIOBJ HTTP_HEADER_ACCEPT; -FIOBJ HTTP_HEADER_ACCEPT_RANGES; -FIOBJ HTTP_HEADER_CACHE_CONTROL; -FIOBJ HTTP_HEADER_CONNECTION; -FIOBJ HTTP_HEADER_CONTENT_ENCODING; -FIOBJ HTTP_HEADER_CONTENT_LENGTH; -FIOBJ HTTP_HEADER_CONTENT_RANGE; -FIOBJ HTTP_HEADER_CONTENT_TYPE; -FIOBJ HTTP_HEADER_COOKIE; -FIOBJ HTTP_HEADER_DATE; -FIOBJ HTTP_HEADER_ETAG; -FIOBJ HTTP_HEADER_HOST; -FIOBJ HTTP_HEADER_LAST_MODIFIED; -FIOBJ HTTP_HEADER_ORIGIN; -FIOBJ HTTP_HEADER_SET_COOKIE; -FIOBJ HTTP_HEADER_UPGRADE; -FIOBJ HTTP_HEADER_WS_SEC_CLIENT_KEY; -FIOBJ HTTP_HEADER_WS_SEC_KEY; -FIOBJ HTTP_HVALUE_BYTES; -FIOBJ HTTP_HVALUE_CLOSE; -FIOBJ HTTP_HVALUE_CONTENT_TYPE_DEFAULT; -FIOBJ HTTP_HVALUE_GZIP; -FIOBJ HTTP_HVALUE_KEEP_ALIVE; -FIOBJ HTTP_HVALUE_MAX_AGE; -FIOBJ HTTP_HVALUE_NO_CACHE; -FIOBJ HTTP_HVALUE_SSE_MIME; -FIOBJ HTTP_HVALUE_WEBSOCKET; -FIOBJ HTTP_HVALUE_WS_SEC_VERSION; -FIOBJ HTTP_HVALUE_WS_UPGRADE; -FIOBJ HTTP_HVALUE_WS_VERSION; - -static void http_lib_init(void *ignr_); -static void http_lib_cleanup(void *ignr_); -static __attribute__((constructor)) void http_lib_constructor(void) { - fio_state_callback_add(FIO_CALL_ON_INITIALIZE, http_lib_init, NULL); - fio_state_callback_add(FIO_CALL_AT_EXIT, http_lib_cleanup, NULL); -} - -void http_mimetype_stats(void); - -static void http_lib_cleanup(void *ignr_) { - (void)ignr_; - http_mimetype_clear(); -#define HTTPLIB_RESET(x) \ - fiobj_free(x); \ - x = FIOBJ_INVALID; - HTTPLIB_RESET(HTTP_HEADER_ACCEPT); - HTTPLIB_RESET(HTTP_HEADER_ACCEPT_RANGES); - HTTPLIB_RESET(HTTP_HEADER_CACHE_CONTROL); - HTTPLIB_RESET(HTTP_HEADER_CONNECTION); - HTTPLIB_RESET(HTTP_HEADER_CONTENT_ENCODING); - HTTPLIB_RESET(HTTP_HEADER_CONTENT_LENGTH); - HTTPLIB_RESET(HTTP_HEADER_CONTENT_RANGE); - HTTPLIB_RESET(HTTP_HEADER_CONTENT_TYPE); - HTTPLIB_RESET(HTTP_HEADER_COOKIE); - HTTPLIB_RESET(HTTP_HEADER_DATE); - HTTPLIB_RESET(HTTP_HEADER_ETAG); - HTTPLIB_RESET(HTTP_HEADER_HOST); - HTTPLIB_RESET(HTTP_HEADER_LAST_MODIFIED); - HTTPLIB_RESET(HTTP_HEADER_ORIGIN); - HTTPLIB_RESET(HTTP_HEADER_SET_COOKIE); - HTTPLIB_RESET(HTTP_HEADER_UPGRADE); - HTTPLIB_RESET(HTTP_HEADER_WS_SEC_CLIENT_KEY); - HTTPLIB_RESET(HTTP_HEADER_WS_SEC_KEY); - HTTPLIB_RESET(HTTP_HVALUE_BYTES); - HTTPLIB_RESET(HTTP_HVALUE_CLOSE); - HTTPLIB_RESET(HTTP_HVALUE_CONTENT_TYPE_DEFAULT); - HTTPLIB_RESET(HTTP_HVALUE_GZIP); - HTTPLIB_RESET(HTTP_HVALUE_KEEP_ALIVE); - HTTPLIB_RESET(HTTP_HVALUE_MAX_AGE); - HTTPLIB_RESET(HTTP_HVALUE_NO_CACHE); - HTTPLIB_RESET(HTTP_HVALUE_SSE_MIME); - HTTPLIB_RESET(HTTP_HVALUE_WEBSOCKET); - HTTPLIB_RESET(HTTP_HVALUE_WS_SEC_VERSION); - HTTPLIB_RESET(HTTP_HVALUE_WS_UPGRADE); - HTTPLIB_RESET(HTTP_HVALUE_WS_VERSION); - -#undef HTTPLIB_RESET - http_mimetype_stats(); -} - -void http_mimetype_stats(void); - -static void http_lib_init(void *ignr_) { - (void)ignr_; - if (HTTP_HEADER_ACCEPT_RANGES) - return; - HTTP_HEADER_ACCEPT = fiobj_str_new("accept", 6); - HTTP_HEADER_ACCEPT_RANGES = fiobj_str_new("accept-ranges", 13); - HTTP_HEADER_CACHE_CONTROL = fiobj_str_new("cache-control", 13); - HTTP_HEADER_CONNECTION = fiobj_str_new("connection", 10); - HTTP_HEADER_CONTENT_ENCODING = fiobj_str_new("content-encoding", 16); - HTTP_HEADER_CONTENT_LENGTH = fiobj_str_new("content-length", 14); - HTTP_HEADER_CONTENT_RANGE = fiobj_str_new("content-range", 13); - HTTP_HEADER_CONTENT_TYPE = fiobj_str_new("content-type", 12); - HTTP_HEADER_COOKIE = fiobj_str_new("cookie", 6); - HTTP_HEADER_DATE = fiobj_str_new("date", 4); - HTTP_HEADER_ETAG = fiobj_str_new("etag", 4); - HTTP_HEADER_HOST = fiobj_str_new("host", 4); - HTTP_HEADER_LAST_MODIFIED = fiobj_str_new("last-modified", 13); - HTTP_HEADER_ORIGIN = fiobj_str_new("origin", 6); - HTTP_HEADER_SET_COOKIE = fiobj_str_new("set-cookie", 10); - HTTP_HEADER_UPGRADE = fiobj_str_new("upgrade", 7); - HTTP_HEADER_WS_SEC_CLIENT_KEY = fiobj_str_new("sec-websocket-key", 17); - HTTP_HEADER_WS_SEC_KEY = fiobj_str_new("sec-websocket-accept", 20); - HTTP_HVALUE_BYTES = fiobj_str_new("bytes", 5); - HTTP_HVALUE_CLOSE = fiobj_str_new("close", 5); - HTTP_HVALUE_CONTENT_TYPE_DEFAULT = - fiobj_str_new("application/octet-stream", 24); - HTTP_HVALUE_GZIP = fiobj_str_new("gzip", 4); - HTTP_HVALUE_KEEP_ALIVE = fiobj_str_new("keep-alive", 10); - HTTP_HVALUE_MAX_AGE = fiobj_str_new("max-age=3600", 12); - HTTP_HVALUE_NO_CACHE = fiobj_str_new("no-cache, max-age=0", 19); - HTTP_HVALUE_SSE_MIME = fiobj_str_new("text/event-stream", 17); - HTTP_HVALUE_WEBSOCKET = fiobj_str_new("websocket", 9); - HTTP_HVALUE_WS_SEC_VERSION = fiobj_str_new("sec-websocket-version", 21); - HTTP_HVALUE_WS_UPGRADE = fiobj_str_new("Upgrade", 7); - HTTP_HVALUE_WS_VERSION = fiobj_str_new("13", 2); - - fiobj_obj2hash(HTTP_HEADER_ACCEPT_RANGES); - fiobj_obj2hash(HTTP_HEADER_CACHE_CONTROL); - fiobj_obj2hash(HTTP_HEADER_CONNECTION); - fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING); - fiobj_obj2hash(HTTP_HEADER_CONTENT_LENGTH); - fiobj_obj2hash(HTTP_HEADER_CONTENT_RANGE); - fiobj_obj2hash(HTTP_HEADER_CONTENT_TYPE); - fiobj_obj2hash(HTTP_HEADER_COOKIE); - fiobj_obj2hash(HTTP_HEADER_DATE); - fiobj_obj2hash(HTTP_HEADER_ETAG); - fiobj_obj2hash(HTTP_HEADER_HOST); - fiobj_obj2hash(HTTP_HEADER_LAST_MODIFIED); - fiobj_obj2hash(HTTP_HEADER_ORIGIN); - fiobj_obj2hash(HTTP_HEADER_SET_COOKIE); - fiobj_obj2hash(HTTP_HEADER_UPGRADE); - fiobj_obj2hash(HTTP_HEADER_WS_SEC_CLIENT_KEY); - fiobj_obj2hash(HTTP_HEADER_WS_SEC_KEY); - fiobj_obj2hash(HTTP_HVALUE_BYTES); - fiobj_obj2hash(HTTP_HVALUE_CLOSE); - fiobj_obj2hash(HTTP_HVALUE_CONTENT_TYPE_DEFAULT); - fiobj_obj2hash(HTTP_HVALUE_GZIP); - fiobj_obj2hash(HTTP_HVALUE_KEEP_ALIVE); - fiobj_obj2hash(HTTP_HVALUE_MAX_AGE); - fiobj_obj2hash(HTTP_HVALUE_NO_CACHE); - fiobj_obj2hash(HTTP_HVALUE_SSE_MIME); - fiobj_obj2hash(HTTP_HVALUE_WEBSOCKET); - fiobj_obj2hash(HTTP_HVALUE_WS_SEC_VERSION); - fiobj_obj2hash(HTTP_HVALUE_WS_UPGRADE); - fiobj_obj2hash(HTTP_HVALUE_WS_VERSION); - -#define REGISTER_MIME(ext, type) \ - http_mimetype_register((char *)ext, sizeof(ext) - 1, \ - fiobj_str_new((char *)type, sizeof(type) - 1)) - - REGISTER_MIME("123", "application/vnd.lotus-1-2-3"); - REGISTER_MIME("3dml", "text/vnd.in3d.3dml"); - REGISTER_MIME("3ds", "image/x-3ds"); - REGISTER_MIME("3g2", "video/3gpp2"); - REGISTER_MIME("3gp", "video/3gpp"); - REGISTER_MIME("7z", "application/x-7z-compressed"); - REGISTER_MIME("aab", "application/x-authorware-bin"); - REGISTER_MIME("aac", "audio/x-aac"); - REGISTER_MIME("aam", "application/x-authorware-map"); - REGISTER_MIME("aas", "application/x-authorware-seg"); - REGISTER_MIME("abw", "application/x-abiword"); - REGISTER_MIME("ac", "application/pkix-attr-cert"); - REGISTER_MIME("acc", "application/vnd.americandynamics.acc"); - REGISTER_MIME("ace", "application/x-ace-compressed"); - REGISTER_MIME("acu", "application/vnd.acucobol"); - REGISTER_MIME("acutc", "application/vnd.acucorp"); - REGISTER_MIME("adp", "audio/adpcm"); - REGISTER_MIME("aep", "application/vnd.audiograph"); - REGISTER_MIME("afm", "application/x-font-type1"); - REGISTER_MIME("afp", "application/vnd.ibm.modcap"); - REGISTER_MIME("ahead", "application/vnd.ahead.space"); - REGISTER_MIME("ai", "application/postscript"); - REGISTER_MIME("aif", "audio/x-aiff"); - REGISTER_MIME("aifc", "audio/x-aiff"); - REGISTER_MIME("aiff", "audio/x-aiff"); - REGISTER_MIME("air", - "application/vnd.adobe.air-application-installer-package+zip"); - REGISTER_MIME("ait", "application/vnd.dvb.ait"); - REGISTER_MIME("ami", "application/vnd.amiga.ami"); - REGISTER_MIME("apk", "application/vnd.android.package-archive"); - REGISTER_MIME("appcache", "text/cache-manifest"); - REGISTER_MIME("application", "application/x-ms-application"); - REGISTER_MIME("pptx", "application/" - "vnd.openxmlformats-officedocument.presentationml." - "presentation"); - REGISTER_MIME("apr", "application/vnd.lotus-approach"); - REGISTER_MIME("arc", "application/x-freearc"); - REGISTER_MIME("asc", "application/pgp-signature"); - REGISTER_MIME("asf", "video/x-ms-asf"); - REGISTER_MIME("asm", "text/x-asm"); - REGISTER_MIME("aso", "application/vnd.accpac.simply.aso"); - REGISTER_MIME("asx", "video/x-ms-asf"); - REGISTER_MIME("atc", "application/vnd.acucorp"); - REGISTER_MIME("atom", "application/atom+xml"); - REGISTER_MIME("atomcat", "application/atomcat+xml"); - REGISTER_MIME("atomsvc", "application/atomsvc+xml"); - REGISTER_MIME("atx", "application/vnd.antix.game-component"); - REGISTER_MIME("au", "audio/basic"); - REGISTER_MIME("avi", "video/x-msvideo"); - REGISTER_MIME("aw", "application/applixware"); - REGISTER_MIME("azf", "application/vnd.airzip.filesecure.azf"); - REGISTER_MIME("azs", "application/vnd.airzip.filesecure.azs"); - REGISTER_MIME("azw", "application/vnd.amazon.ebook"); - REGISTER_MIME("bat", "application/x-msdownload"); - REGISTER_MIME("bcpio", "application/x-bcpio"); - REGISTER_MIME("bdf", "application/x-font-bdf"); - REGISTER_MIME("bdm", "application/vnd.syncml.dm+wbxml"); - REGISTER_MIME("bed", "application/vnd.realvnc.bed"); - REGISTER_MIME("bh2", "application/vnd.fujitsu.oasysprs"); - REGISTER_MIME("bin", "application/octet-stream"); - REGISTER_MIME("blb", "application/x-blorb"); - REGISTER_MIME("blorb", "application/x-blorb"); - REGISTER_MIME("bmi", "application/vnd.bmi"); - REGISTER_MIME("bmp", "image/bmp"); - REGISTER_MIME("book", "application/vnd.framemaker"); - REGISTER_MIME("box", "application/vnd.previewsystems.box"); - REGISTER_MIME("boz", "application/x-bzip2"); - REGISTER_MIME("bpk", "application/octet-stream"); - REGISTER_MIME("btif", "image/prs.btif"); - REGISTER_MIME("bz", "application/x-bzip"); - REGISTER_MIME("bz2", "application/x-bzip2"); - REGISTER_MIME("c", "text/x-c"); - REGISTER_MIME("c11amc", "application/vnd.cluetrust.cartomobile-config"); - REGISTER_MIME("c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"); - REGISTER_MIME("c4d", "application/vnd.clonk.c4group"); - REGISTER_MIME("c4f", "application/vnd.clonk.c4group"); - REGISTER_MIME("c4g", "application/vnd.clonk.c4group"); - REGISTER_MIME("c4p", "application/vnd.clonk.c4group"); - REGISTER_MIME("c4u", "application/vnd.clonk.c4group"); - REGISTER_MIME("cab", "application/vnd.ms-cab-compressed"); - REGISTER_MIME("caf", "audio/x-caf"); - REGISTER_MIME("cap", "application/vnd.tcpdump.pcap"); - REGISTER_MIME("car", "application/vnd.curl.car"); - REGISTER_MIME("cat", "application/vnd.ms-pki.seccat"); - REGISTER_MIME("cb7", "application/x-cbr"); - REGISTER_MIME("cba", "application/x-cbr"); - REGISTER_MIME("cbr", "application/x-cbr"); - REGISTER_MIME("cbt", "application/x-cbr"); - REGISTER_MIME("cbz", "application/x-cbr"); - REGISTER_MIME("cc", "text/x-c"); - REGISTER_MIME("cct", "application/x-director"); - REGISTER_MIME("ccxml", "application/ccxml+xml"); - REGISTER_MIME("cdbcmsg", "application/vnd.contact.cmsg"); - REGISTER_MIME("cdf", "application/x-netcdf"); - REGISTER_MIME("cdkey", "application/vnd.mediastation.cdkey"); - REGISTER_MIME("cdmia", "application/cdmi-capability"); - REGISTER_MIME("cdmic", "application/cdmi-container"); - REGISTER_MIME("cdmid", "application/cdmi-domain"); - REGISTER_MIME("cdmio", "application/cdmi-object"); - REGISTER_MIME("cdmiq", "application/cdmi-queue"); - REGISTER_MIME("cdx", "chemical/x-cdx"); - REGISTER_MIME("cdxml", "application/vnd.chemdraw+xml"); - REGISTER_MIME("cdy", "application/vnd.cinderella"); - REGISTER_MIME("cer", "application/pkix-cert"); - REGISTER_MIME("cfs", "application/x-cfs-compressed"); - REGISTER_MIME("cgm", "image/cgm"); - REGISTER_MIME("chat", "application/x-chat"); - REGISTER_MIME("chm", "application/vnd.ms-htmlhelp"); - REGISTER_MIME("chrt", "application/vnd.kde.kchart"); - REGISTER_MIME("cif", "chemical/x-cif"); - REGISTER_MIME("cii", - "application/vnd.anser-web-certificate-issue-initiation"); - REGISTER_MIME("cil", "application/vnd.ms-artgalry"); - REGISTER_MIME("cla", "application/vnd.claymore"); - REGISTER_MIME("class", "application/java-vm"); - REGISTER_MIME("clkk", "application/vnd.crick.clicker.keyboard"); - REGISTER_MIME("clkp", "application/vnd.crick.clicker.palette"); - REGISTER_MIME("clkt", "application/vnd.crick.clicker.template"); - REGISTER_MIME("clkw", "application/vnd.crick.clicker.wordbank"); - REGISTER_MIME("clkx", "application/vnd.crick.clicker"); - REGISTER_MIME("clp", "application/x-msclip"); - REGISTER_MIME("cmc", "application/vnd.cosmocaller"); - REGISTER_MIME("cmdf", "chemical/x-cmdf"); - REGISTER_MIME("cml", "chemical/x-cml"); - REGISTER_MIME("cmp", "application/vnd.yellowriver-custom-menu"); - REGISTER_MIME("cmx", "image/x-cmx"); - REGISTER_MIME("cod", "application/vnd.rim.cod"); - REGISTER_MIME("com", "application/x-msdownload"); - REGISTER_MIME("conf", "text/plain"); - REGISTER_MIME("cpio", "application/x-cpio"); - REGISTER_MIME("cpp", "text/x-c"); - REGISTER_MIME("cpt", "application/mac-compactpro"); - REGISTER_MIME("crd", "application/x-mscardfile"); - REGISTER_MIME("crl", "application/pkix-crl"); - REGISTER_MIME("crt", "application/x-x509-ca-cert"); - REGISTER_MIME("cryptonote", "application/vnd.rig.cryptonote"); - REGISTER_MIME("csh", "application/x-csh"); - REGISTER_MIME("csml", "chemical/x-csml"); - REGISTER_MIME("csp", "application/vnd.commonspace"); - REGISTER_MIME("css", "text/css"); - REGISTER_MIME("cst", "application/x-director"); - REGISTER_MIME("csv", "text/csv"); - REGISTER_MIME("cu", "application/cu-seeme"); - REGISTER_MIME("curl", "text/vnd.curl"); - REGISTER_MIME("cww", "application/prs.cww"); - REGISTER_MIME("cxt", "application/x-director"); - REGISTER_MIME("cxx", "text/x-c"); - REGISTER_MIME("dae", "model/vnd.collada+xml"); - REGISTER_MIME("daf", "application/vnd.mobius.daf"); - REGISTER_MIME("dart", "application/vnd.dart"); - REGISTER_MIME("dataless", "application/vnd.fdsn.seed"); - REGISTER_MIME("davmount", "application/davmount+xml"); - REGISTER_MIME("dbk", "application/docbook+xml"); - REGISTER_MIME("dcr", "application/x-director"); - REGISTER_MIME("dcurl", "text/vnd.curl.dcurl"); - REGISTER_MIME("dd2", "application/vnd.oma.dd2+xml"); - REGISTER_MIME("ddd", "application/vnd.fujixerox.ddd"); - REGISTER_MIME("deb", "application/x-debian-package"); - REGISTER_MIME("def", "text/plain"); - REGISTER_MIME("deploy", "application/octet-stream"); - REGISTER_MIME("der", "application/x-x509-ca-cert"); - REGISTER_MIME("dfac", "application/vnd.dreamfactory"); - REGISTER_MIME("dgc", "application/x-dgc-compressed"); - REGISTER_MIME("dic", "text/x-c"); - REGISTER_MIME("dir", "application/x-director"); - REGISTER_MIME("dis", "application/vnd.mobius.dis"); - REGISTER_MIME("dist", "application/octet-stream"); - REGISTER_MIME("distz", "application/octet-stream"); - REGISTER_MIME("djv", "image/vnd.djvu"); - REGISTER_MIME("djvu", "image/vnd.djvu"); - REGISTER_MIME("dll", "application/x-msdownload"); - REGISTER_MIME("dmg", "application/x-apple-diskimage"); - REGISTER_MIME("dmp", "application/vnd.tcpdump.pcap"); - REGISTER_MIME("dms", "application/octet-stream"); - REGISTER_MIME("dna", "application/vnd.dna"); - REGISTER_MIME("doc", "application/msword"); - REGISTER_MIME("docm", "application/vnd.ms-word.document.macroenabled.12"); - REGISTER_MIME("docx", "application/" - "vnd.openxmlformats-officedocument.wordprocessingml." - "document"); - REGISTER_MIME("dot", "application/msword"); - REGISTER_MIME("dotm", "application/vnd.ms-word.template.macroenabled.12"); - REGISTER_MIME("dotx", "application/" - "vnd.openxmlformats-officedocument.wordprocessingml." - "template"); - REGISTER_MIME("dp", "application/vnd.osgi.dp"); - REGISTER_MIME("dpg", "application/vnd.dpgraph"); - REGISTER_MIME("dra", "audio/vnd.dra"); - REGISTER_MIME("dsc", "text/prs.lines.tag"); - REGISTER_MIME("dssc", "application/dssc+der"); - REGISTER_MIME("dtb", "application/x-dtbook+xml"); - REGISTER_MIME("dtd", "application/xml-dtd"); - REGISTER_MIME("dts", "audio/vnd.dts"); - REGISTER_MIME("dtshd", "audio/vnd.dts.hd"); - REGISTER_MIME("dump", "application/octet-stream"); - REGISTER_MIME("dvb", "video/vnd.dvb.file"); - REGISTER_MIME("dvi", "application/x-dvi"); - REGISTER_MIME("dwf", "model/vnd.dwf"); - REGISTER_MIME("dwg", "image/vnd.dwg"); - REGISTER_MIME("dxf", "image/vnd.dxf"); - REGISTER_MIME("dxp", "application/vnd.spotfire.dxp"); - REGISTER_MIME("dxr", "application/x-director"); - REGISTER_MIME("ecelp4800", "audio/vnd.nuera.ecelp4800"); - REGISTER_MIME("ecelp7470", "audio/vnd.nuera.ecelp7470"); - REGISTER_MIME("ecelp9600", "audio/vnd.nuera.ecelp9600"); - REGISTER_MIME("ecma", "application/ecmascript"); - REGISTER_MIME("edm", "application/vnd.novadigm.edm"); - REGISTER_MIME("edx", "application/vnd.novadigm.edx"); - REGISTER_MIME("efif", "application/vnd.picsel"); - REGISTER_MIME("ei6", "application/vnd.pg.osasli"); - REGISTER_MIME("elc", "application/octet-stream"); - REGISTER_MIME("emf", "application/x-msmetafile"); - REGISTER_MIME("eml", "message/rfc822"); - REGISTER_MIME("emma", "application/emma+xml"); - REGISTER_MIME("emz", "application/x-msmetafile"); - REGISTER_MIME("eol", "audio/vnd.digital-winds"); - REGISTER_MIME("eot", "application/vnd.ms-fontobject"); - REGISTER_MIME("eps", "application/postscript"); - REGISTER_MIME("epub", "application/epub+zip"); - REGISTER_MIME("es3", "application/vnd.eszigno3+xml"); - REGISTER_MIME("esa", "application/vnd.osgi.subsystem"); - REGISTER_MIME("esf", "application/vnd.epson.esf"); - REGISTER_MIME("et3", "application/vnd.eszigno3+xml"); - REGISTER_MIME("etx", "text/x-setext"); - REGISTER_MIME("eva", "application/x-eva"); - REGISTER_MIME("evy", "application/x-envoy"); - REGISTER_MIME("exe", "application/x-msdownload"); - REGISTER_MIME("exi", "application/exi"); - REGISTER_MIME("ext", "application/vnd.novadigm.ext"); - REGISTER_MIME("ez", "application/andrew-inset"); - REGISTER_MIME("ez2", "application/vnd.ezpix-album"); - REGISTER_MIME("ez3", "application/vnd.ezpix-package"); - REGISTER_MIME("f", "text/x-fortran"); - REGISTER_MIME("f4v", "video/x-f4v"); - REGISTER_MIME("f77", "text/x-fortran"); - REGISTER_MIME("f90", "text/x-fortran"); - REGISTER_MIME("fbs", "image/vnd.fastbidsheet"); - REGISTER_MIME("fcdt", "application/vnd.adobe.formscentral.fcdt"); - REGISTER_MIME("fcs", "application/vnd.isac.fcs"); - REGISTER_MIME("fdf", "application/vnd.fdf"); - REGISTER_MIME("fe_launch", "application/vnd.denovo.fcselayout-link"); - REGISTER_MIME("fg5", "application/vnd.fujitsu.oasysgp"); - REGISTER_MIME("fgd", "application/x-director"); - REGISTER_MIME("fh", "image/x-freehand"); - REGISTER_MIME("fh4", "image/x-freehand"); - REGISTER_MIME("fh5", "image/x-freehand"); - REGISTER_MIME("fh7", "image/x-freehand"); - REGISTER_MIME("fhc", "image/x-freehand"); - REGISTER_MIME("fig", "application/x-xfig"); - REGISTER_MIME("flac", "audio/x-flac"); - REGISTER_MIME("fli", "video/x-fli"); - REGISTER_MIME("flo", "application/vnd.micrografx.flo"); - REGISTER_MIME("flv", "video/x-flv"); - REGISTER_MIME("flw", "application/vnd.kde.kivio"); - REGISTER_MIME("flx", "text/vnd.fmi.flexstor"); - REGISTER_MIME("fly", "text/vnd.fly"); - REGISTER_MIME("fm", "application/vnd.framemaker"); - REGISTER_MIME("fnc", "application/vnd.frogans.fnc"); - REGISTER_MIME("for", "text/x-fortran"); - REGISTER_MIME("fpx", "image/vnd.fpx"); - REGISTER_MIME("frame", "application/vnd.framemaker"); - REGISTER_MIME("fsc", "application/vnd.fsc.weblaunch"); - REGISTER_MIME("fst", "image/vnd.fst"); - REGISTER_MIME("ftc", "application/vnd.fluxtime.clip"); - REGISTER_MIME("fti", "application/vnd.anser-web-funds-transfer-initiation"); - REGISTER_MIME("fvt", "video/vnd.fvt"); - REGISTER_MIME("fxp", "application/vnd.adobe.fxp"); - REGISTER_MIME("fxpl", "application/vnd.adobe.fxp"); - REGISTER_MIME("fzs", "application/vnd.fuzzysheet"); - REGISTER_MIME("g2w", "application/vnd.geoplan"); - REGISTER_MIME("g3", "image/g3fax"); - REGISTER_MIME("g3w", "application/vnd.geospace"); - REGISTER_MIME("gac", "application/vnd.groove-account"); - REGISTER_MIME("gam", "application/x-tads"); - REGISTER_MIME("gbr", "application/rpki-ghostbusters"); - REGISTER_MIME("gca", "application/x-gca-compressed"); - REGISTER_MIME("gdl", "model/vnd.gdl"); - REGISTER_MIME("geo", "application/vnd.dynageo"); - REGISTER_MIME("gex", "application/vnd.geometry-explorer"); - REGISTER_MIME("ggb", "application/vnd.geogebra.file"); - REGISTER_MIME("ggt", "application/vnd.geogebra.tool"); - REGISTER_MIME("ghf", "application/vnd.groove-help"); - REGISTER_MIME("gif", "image/gif"); - REGISTER_MIME("gim", "application/vnd.groove-identity-message"); - REGISTER_MIME("gml", "application/gml+xml"); - REGISTER_MIME("gmx", "application/vnd.gmx"); - REGISTER_MIME("gnumeric", "application/x-gnumeric"); - REGISTER_MIME("gph", "application/vnd.flographit"); - REGISTER_MIME("gpx", "application/gpx+xml"); - REGISTER_MIME("gqf", "application/vnd.grafeq"); - REGISTER_MIME("gqs", "application/vnd.grafeq"); - REGISTER_MIME("gram", "application/srgs"); - REGISTER_MIME("gramps", "application/x-gramps-xml"); - REGISTER_MIME("gre", "application/vnd.geometry-explorer"); - REGISTER_MIME("grv", "application/vnd.groove-injector"); - REGISTER_MIME("grxml", "application/srgs+xml"); - REGISTER_MIME("gsf", "application/x-font-ghostscript"); - REGISTER_MIME("gtar", "application/x-gtar"); - REGISTER_MIME("gtm", "application/vnd.groove-tool-message"); - REGISTER_MIME("gtw", "model/vnd.gtw"); - REGISTER_MIME("gv", "text/vnd.graphviz"); - REGISTER_MIME("gxf", "application/gxf"); - REGISTER_MIME("gxt", "application/vnd.geonext"); - REGISTER_MIME("h", "text/x-c"); - REGISTER_MIME("h261", "video/h261"); - REGISTER_MIME("h263", "video/h263"); - REGISTER_MIME("h264", "video/h264"); - REGISTER_MIME("hal", "application/vnd.hal+xml"); - REGISTER_MIME("hbci", "application/vnd.hbci"); - REGISTER_MIME("hdf", "application/x-hdf"); - REGISTER_MIME("hh", "text/x-c"); - REGISTER_MIME("hlp", "application/winhlp"); - REGISTER_MIME("hpgl", "application/vnd.hp-hpgl"); - REGISTER_MIME("hpid", "application/vnd.hp-hpid"); - REGISTER_MIME("hps", "application/vnd.hp-hps"); - REGISTER_MIME("hqx", "application/mac-binhex40"); - REGISTER_MIME("htke", "application/vnd.kenameaapp"); - REGISTER_MIME("htm", "text/html"); - REGISTER_MIME("html", "text/html"); - REGISTER_MIME("hvd", "application/vnd.yamaha.hv-dic"); - REGISTER_MIME("hvp", "application/vnd.yamaha.hv-voice"); - REGISTER_MIME("hvs", "application/vnd.yamaha.hv-script"); - REGISTER_MIME("i2g", "application/vnd.intergeo"); - REGISTER_MIME("icc", "application/vnd.iccprofile"); - REGISTER_MIME("ice", "x-conference/x-cooltalk"); - REGISTER_MIME("icm", "application/vnd.iccprofile"); - REGISTER_MIME("ico", "image/x-icon"); - REGISTER_MIME("ics", "text/calendar"); - REGISTER_MIME("ief", "image/ief"); - REGISTER_MIME("ifb", "text/calendar"); - REGISTER_MIME("ifm", "application/vnd.shana.informed.formdata"); - REGISTER_MIME("iges", "model/iges"); - REGISTER_MIME("igl", "application/vnd.igloader"); - REGISTER_MIME("igm", "application/vnd.insors.igm"); - REGISTER_MIME("igs", "model/iges"); - REGISTER_MIME("igx", "application/vnd.micrografx.igx"); - REGISTER_MIME("iif", "application/vnd.shana.informed.interchange"); - REGISTER_MIME("imp", "application/vnd.accpac.simply.imp"); - REGISTER_MIME("ims", "application/vnd.ms-ims"); - REGISTER_MIME("in", "text/plain"); - REGISTER_MIME("ink", "application/inkml+xml"); - REGISTER_MIME("inkml", "application/inkml+xml"); - REGISTER_MIME("install", "application/x-install-instructions"); - REGISTER_MIME("iota", "application/vnd.astraea-software.iota"); - REGISTER_MIME("ipfix", "application/ipfix"); - REGISTER_MIME("ipk", "application/vnd.shana.informed.package"); - REGISTER_MIME("irm", "application/vnd.ibm.rights-management"); - REGISTER_MIME("irp", "application/vnd.irepository.package+xml"); - REGISTER_MIME("iso", "application/x-iso9660-image"); - REGISTER_MIME("itp", "application/vnd.shana.informed.formtemplate"); - REGISTER_MIME("ivp", "application/vnd.immervision-ivp"); - REGISTER_MIME("ivu", "application/vnd.immervision-ivu"); - REGISTER_MIME("jad", "text/vnd.sun.j2me.app-descriptor"); - REGISTER_MIME("jam", "application/vnd.jam"); - REGISTER_MIME("jar", "application/java-archive"); - REGISTER_MIME("java", "text/x-java-source"); - REGISTER_MIME("jisp", "application/vnd.jisp"); - REGISTER_MIME("jlt", "application/vnd.hp-jlyt"); - REGISTER_MIME("jnlp", "application/x-java-jnlp-file"); - REGISTER_MIME("joda", "application/vnd.joost.joda-archive"); - REGISTER_MIME("jpe", "image/jpeg"); - REGISTER_MIME("jpeg", "image/jpeg"); - REGISTER_MIME("jpg", "image/jpeg"); - REGISTER_MIME("jpgm", "video/jpm"); - REGISTER_MIME("jpgv", "video/jpeg"); - REGISTER_MIME("jpm", "video/jpm"); - REGISTER_MIME("js", "application/javascript"); - REGISTER_MIME("json", "application/json"); - REGISTER_MIME("jsonml", "application/jsonml+json"); - REGISTER_MIME("kar", "audio/midi"); - REGISTER_MIME("karbon", "application/vnd.kde.karbon"); - REGISTER_MIME("kfo", "application/vnd.kde.kformula"); - REGISTER_MIME("kia", "application/vnd.kidspiration"); - REGISTER_MIME("kml", "application/vnd.google-earth.kml+xml"); - REGISTER_MIME("kmz", "application/vnd.google-earth.kmz"); - REGISTER_MIME("kne", "application/vnd.kinar"); - REGISTER_MIME("knp", "application/vnd.kinar"); - REGISTER_MIME("kon", "application/vnd.kde.kontour"); - REGISTER_MIME("kpr", "application/vnd.kde.kpresenter"); - REGISTER_MIME("kpt", "application/vnd.kde.kpresenter"); - REGISTER_MIME("kpxx", "application/vnd.ds-keypoint"); - REGISTER_MIME("ksp", "application/vnd.kde.kspread"); - REGISTER_MIME("ktr", "application/vnd.kahootz"); - REGISTER_MIME("ktx", "image/ktx"); - REGISTER_MIME("ktz", "application/vnd.kahootz"); - REGISTER_MIME("kwd", "application/vnd.kde.kword"); - REGISTER_MIME("kwt", "application/vnd.kde.kword"); - REGISTER_MIME("lasxml", "application/vnd.las.las+xml"); - REGISTER_MIME("latex", "application/x-latex"); - REGISTER_MIME("lbd", "application/vnd.llamagraphics.life-balance.desktop"); - REGISTER_MIME("lbe", - "application/vnd.llamagraphics.life-balance.exchange+xml"); - REGISTER_MIME("les", "application/vnd.hhe.lesson-player"); - REGISTER_MIME("lha", "application/x-lzh-compressed"); - REGISTER_MIME("link66", "application/vnd.route66.link66+xml"); - REGISTER_MIME("list", "text/plain"); - REGISTER_MIME("list3820", "application/vnd.ibm.modcap"); - REGISTER_MIME("listafp", "application/vnd.ibm.modcap"); - REGISTER_MIME("lnk", "application/x-ms-shortcut"); - REGISTER_MIME("log", "text/plain"); - REGISTER_MIME("lostxml", "application/lost+xml"); - REGISTER_MIME("lrf", "application/octet-stream"); - REGISTER_MIME("lrm", "application/vnd.ms-lrm"); - REGISTER_MIME("ltf", "application/vnd.frogans.ltf"); - REGISTER_MIME("lvp", "audio/vnd.lucent.voice"); - REGISTER_MIME("lwp", "application/vnd.lotus-wordpro"); - REGISTER_MIME("lzh", "application/x-lzh-compressed"); - REGISTER_MIME("m13", "application/x-msmediaview"); - REGISTER_MIME("m14", "application/x-msmediaview"); - REGISTER_MIME("m1v", "video/mpeg"); - REGISTER_MIME("m21", "application/mp21"); - REGISTER_MIME("m2a", "audio/mpeg"); - REGISTER_MIME("m2v", "video/mpeg"); - REGISTER_MIME("m3a", "audio/mpeg"); - REGISTER_MIME("m3u", "audio/x-mpegurl"); - REGISTER_MIME("m3u8", "application/vnd.apple.mpegurl"); - REGISTER_MIME("m4a", "audio/mp4"); - REGISTER_MIME("m4u", "video/vnd.mpegurl"); - REGISTER_MIME("m4v", "video/x-m4v"); - REGISTER_MIME("ma", "application/mathematica"); - REGISTER_MIME("mads", "application/mads+xml"); - REGISTER_MIME("mag", "application/vnd.ecowin.chart"); - REGISTER_MIME("maker", "application/vnd.framemaker"); - REGISTER_MIME("man", "text/troff"); - REGISTER_MIME("mar", "application/octet-stream"); - REGISTER_MIME("markdown", "text/markdown"); - REGISTER_MIME("mathml", "application/mathml+xml"); - REGISTER_MIME("mb", "application/mathematica"); - REGISTER_MIME("mbk", "application/vnd.mobius.mbk"); - REGISTER_MIME("mbox", "application/mbox"); - REGISTER_MIME("mc1", "application/vnd.medcalcdata"); - REGISTER_MIME("mcd", "application/vnd.mcd"); - REGISTER_MIME("mcurl", "text/vnd.curl.mcurl"); - REGISTER_MIME("md", "text/markdown"); - REGISTER_MIME("mdb", "application/x-msaccess"); - REGISTER_MIME("mdi", "image/vnd.ms-modi"); - REGISTER_MIME("me", "text/troff"); - REGISTER_MIME("mesh", "model/mesh"); - REGISTER_MIME("meta4", "application/metalink4+xml"); - REGISTER_MIME("metalink", "application/metalink+xml"); - REGISTER_MIME("mets", "application/mets+xml"); - REGISTER_MIME("mfm", "application/vnd.mfmp"); - REGISTER_MIME("mft", "application/rpki-manifest"); - REGISTER_MIME("mgp", "application/vnd.osgeo.mapguide.package"); - REGISTER_MIME("mgz", "application/vnd.proteus.magazine"); - REGISTER_MIME("mid", "audio/midi"); - REGISTER_MIME("midi", "audio/midi"); - REGISTER_MIME("mie", "application/x-mie"); - REGISTER_MIME("mif", "application/vnd.mif"); - REGISTER_MIME("mime", "message/rfc822"); - REGISTER_MIME("mj2", "video/mj2"); - REGISTER_MIME("mjp2", "video/mj2"); - REGISTER_MIME("mk3d", "video/x-matroska"); - REGISTER_MIME("mka", "audio/x-matroska"); - REGISTER_MIME("mks", "video/x-matroska"); - REGISTER_MIME("mkv", "video/x-matroska"); - REGISTER_MIME("mlp", "application/vnd.dolby.mlp"); - REGISTER_MIME("mmd", "application/vnd.chipnuts.karaoke-mmd"); - REGISTER_MIME("mmf", "application/vnd.smaf"); - REGISTER_MIME("mmr", "image/vnd.fujixerox.edmics-mmr"); - REGISTER_MIME("mng", "video/x-mng"); - REGISTER_MIME("mny", "application/x-msmoney"); - REGISTER_MIME("mobi", "application/x-mobipocket-ebook"); - REGISTER_MIME("mods", "application/mods+xml"); - REGISTER_MIME("mov", "video/quicktime"); - REGISTER_MIME("movie", "video/x-sgi-movie"); - REGISTER_MIME("mp2", "audio/mpeg"); - REGISTER_MIME("mp21", "application/mp21"); - REGISTER_MIME("mp2a", "audio/mpeg"); - REGISTER_MIME("mp3", "audio/mpeg"); - REGISTER_MIME("mp4", "video/mp4"); - REGISTER_MIME("mp4a", "audio/mp4"); - REGISTER_MIME("mp4s", "application/mp4"); - REGISTER_MIME("mp4v", "video/mp4"); - REGISTER_MIME("mpc", "application/vnd.mophun.certificate"); - REGISTER_MIME("mpe", "video/mpeg"); - REGISTER_MIME("mpeg", "video/mpeg"); - REGISTER_MIME("mpg", "video/mpeg"); - REGISTER_MIME("mpg4", "video/mp4"); - REGISTER_MIME("mpga", "audio/mpeg"); - REGISTER_MIME("mpkg", "application/vnd.apple.installer+xml"); - REGISTER_MIME("mpm", "application/vnd.blueice.multipass"); - REGISTER_MIME("mpn", "application/vnd.mophun.application"); - REGISTER_MIME("mpp", "application/vnd.ms-project"); - REGISTER_MIME("mpt", "application/vnd.ms-project"); - REGISTER_MIME("mpy", "application/vnd.ibm.minipay"); - REGISTER_MIME("mqy", "application/vnd.mobius.mqy"); - REGISTER_MIME("mrc", "application/marc"); - REGISTER_MIME("mrcx", "application/marcxml+xml"); - REGISTER_MIME("ms", "text/troff"); - REGISTER_MIME("mscml", "application/mediaservercontrol+xml"); - REGISTER_MIME("mseed", "application/vnd.fdsn.mseed"); - REGISTER_MIME("mseq", "application/vnd.mseq"); - REGISTER_MIME("msf", "application/vnd.epson.msf"); - REGISTER_MIME("msh", "model/mesh"); - REGISTER_MIME("msi", "application/x-msdownload"); - REGISTER_MIME("msl", "application/vnd.mobius.msl"); - REGISTER_MIME("msty", "application/vnd.muvee.style"); - REGISTER_MIME("mts", "model/vnd.mts"); - REGISTER_MIME("mus", "application/vnd.musician"); - REGISTER_MIME("musicxml", "application/vnd.recordare.musicxml+xml"); - REGISTER_MIME("mvb", "application/x-msmediaview"); - REGISTER_MIME("mwf", "application/vnd.mfer"); - REGISTER_MIME("mxf", "application/mxf"); - REGISTER_MIME("mxl", "application/vnd.recordare.musicxml"); - REGISTER_MIME("mxml", "application/xv+xml"); - REGISTER_MIME("mxs", "application/vnd.triscape.mxs"); - REGISTER_MIME("mxu", "video/vnd.mpegurl"); - REGISTER_MIME("n-gage", "application/vnd.nokia.n-gage.symbian.install"); - REGISTER_MIME("n3", "text/n3"); - REGISTER_MIME("nb", "application/mathematica"); - REGISTER_MIME("nbp", "application/vnd.wolfram.player"); - REGISTER_MIME("nc", "application/x-netcdf"); - REGISTER_MIME("ncx", "application/x-dtbncx+xml"); - REGISTER_MIME("nfo", "text/x-nfo"); - REGISTER_MIME("ngdat", "application/vnd.nokia.n-gage.data"); - REGISTER_MIME("nitf", "application/vnd.nitf"); - REGISTER_MIME("nlu", "application/vnd.neurolanguage.nlu"); - REGISTER_MIME("nml", "application/vnd.enliven"); - REGISTER_MIME("nnd", "application/vnd.noblenet-directory"); - REGISTER_MIME("nns", "application/vnd.noblenet-sealer"); - REGISTER_MIME("nnw", "application/vnd.noblenet-web"); - REGISTER_MIME("npx", "image/vnd.net-fpx"); - REGISTER_MIME("nsc", "application/x-conference"); - REGISTER_MIME("nsf", "application/vnd.lotus-notes"); - REGISTER_MIME("ntf", "application/vnd.nitf"); - REGISTER_MIME("nzb", "application/x-nzb"); - REGISTER_MIME("oa2", "application/vnd.fujitsu.oasys2"); - REGISTER_MIME("oa3", "application/vnd.fujitsu.oasys3"); - REGISTER_MIME("oas", "application/vnd.fujitsu.oasys"); - REGISTER_MIME("obd", "application/x-msbinder"); - REGISTER_MIME("obj", "application/x-tgif"); - REGISTER_MIME("oda", "application/oda"); - REGISTER_MIME("odb", "application/vnd.oasis.opendocument.database"); - REGISTER_MIME("odc", "application/vnd.oasis.opendocument.chart"); - REGISTER_MIME("odf", "application/vnd.oasis.opendocument.formula"); - REGISTER_MIME("odft", "application/vnd.oasis.opendocument.formula-template"); - REGISTER_MIME("odg", "application/vnd.oasis.opendocument.graphics"); - REGISTER_MIME("odi", "application/vnd.oasis.opendocument.image"); - REGISTER_MIME("odm", "application/vnd.oasis.opendocument.text-master"); - REGISTER_MIME("odp", "application/vnd.oasis.opendocument.presentation"); - REGISTER_MIME("ods", "application/vnd.oasis.opendocument.spreadsheet"); - REGISTER_MIME("odt", "application/vnd.oasis.opendocument.text"); - REGISTER_MIME("oga", "audio/ogg"); - REGISTER_MIME("ogg", "audio/ogg"); - REGISTER_MIME("ogv", "video/ogg"); - REGISTER_MIME("ogx", "application/ogg"); - REGISTER_MIME("omdoc", "application/omdoc+xml"); - REGISTER_MIME("onepkg", "application/onenote"); - REGISTER_MIME("onetmp", "application/onenote"); - REGISTER_MIME("onetoc", "application/onenote"); - REGISTER_MIME("onetoc2", "application/onenote"); - REGISTER_MIME("opf", "application/oebps-package+xml"); - REGISTER_MIME("opml", "text/x-opml"); - REGISTER_MIME("oprc", "application/vnd.palm"); - REGISTER_MIME("org", "application/vnd.lotus-organizer"); - REGISTER_MIME("osf", "application/vnd.yamaha.openscoreformat"); - REGISTER_MIME("osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"); - REGISTER_MIME("otc", "application/vnd.oasis.opendocument.chart-template"); - REGISTER_MIME("otf", "application/x-font-otf"); - REGISTER_MIME("otg", "application/vnd.oasis.opendocument.graphics-template"); - REGISTER_MIME("oth", "application/vnd.oasis.opendocument.text-web"); - REGISTER_MIME("oti", "application/vnd.oasis.opendocument.image-template"); - REGISTER_MIME("otp", - "application/vnd.oasis.opendocument.presentation-template"); - REGISTER_MIME("ots", - "application/vnd.oasis.opendocument.spreadsheet-template"); - REGISTER_MIME("ott", "application/vnd.oasis.opendocument.text-template"); - REGISTER_MIME("oxps", "application/oxps"); - REGISTER_MIME("oxt", "application/vnd.openofficeorg.extension"); - REGISTER_MIME("p", "text/x-pascal"); - REGISTER_MIME("p10", "application/pkcs10"); - REGISTER_MIME("p12", "application/x-pkcs12"); - REGISTER_MIME("p7b", "application/x-pkcs7-certificates"); - REGISTER_MIME("p7c", "application/pkcs7-mime"); - REGISTER_MIME("p7m", "application/pkcs7-mime"); - REGISTER_MIME("p7r", "application/x-pkcs7-certreqresp"); - REGISTER_MIME("p7s", "application/pkcs7-signature"); - REGISTER_MIME("p8", "application/pkcs8"); - REGISTER_MIME("pas", "text/x-pascal"); - REGISTER_MIME("paw", "application/vnd.pawaafile"); - REGISTER_MIME("pbd", "application/vnd.powerbuilder6"); - REGISTER_MIME("pbm", "image/x-portable-bitmap"); - REGISTER_MIME("pcap", "application/vnd.tcpdump.pcap"); - REGISTER_MIME("pcf", "application/x-font-pcf"); - REGISTER_MIME("pcl", "application/vnd.hp-pcl"); - REGISTER_MIME("pclxl", "application/vnd.hp-pclxl"); - REGISTER_MIME("pct", "image/x-pict"); - REGISTER_MIME("pcurl", "application/vnd.curl.pcurl"); - REGISTER_MIME("pcx", "image/x-pcx"); - REGISTER_MIME("pdb", "application/vnd.palm"); - REGISTER_MIME("pdf", "application/pdf"); - REGISTER_MIME("pfa", "application/x-font-type1"); - REGISTER_MIME("pfb", "application/x-font-type1"); - REGISTER_MIME("pfm", "application/x-font-type1"); - REGISTER_MIME("pfr", "application/font-tdpfr"); - REGISTER_MIME("pfx", "application/x-pkcs12"); - REGISTER_MIME("pgm", "image/x-portable-graymap"); - REGISTER_MIME("pgn", "application/x-chess-pgn"); - REGISTER_MIME("pgp", "application/pgp-encrypted"); - REGISTER_MIME("pic", "image/x-pict"); - REGISTER_MIME("pkg", "application/octet-stream"); - REGISTER_MIME("pki", "application/pkixcmp"); - REGISTER_MIME("pkipath", "application/pkix-pkipath"); - REGISTER_MIME("plb", "application/vnd.3gpp.pic-bw-large"); - REGISTER_MIME("plc", "application/vnd.mobius.plc"); - REGISTER_MIME("plf", "application/vnd.pocketlearn"); - REGISTER_MIME("pls", "application/pls+xml"); - REGISTER_MIME("pml", "application/vnd.ctc-posml"); - REGISTER_MIME("png", "image/png"); - REGISTER_MIME("pnm", "image/x-portable-anymap"); - REGISTER_MIME("portpkg", "application/vnd.macports.portpkg"); - REGISTER_MIME("pot", "application/vnd.ms-powerpoint"); - REGISTER_MIME("potm", - "application/vnd.ms-powerpoint.template.macroenabled.12"); - REGISTER_MIME( - "potx", - "application/vnd.openxmlformats-officedocument.presentationml.template"); - REGISTER_MIME("ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"); - REGISTER_MIME("ppd", "application/vnd.cups-ppd"); - REGISTER_MIME("ppm", "image/x-portable-pixmap"); - REGISTER_MIME("pps", "application/vnd.ms-powerpoint"); - REGISTER_MIME("ppsm", - "application/vnd.ms-powerpoint.slideshow.macroenabled.12"); - REGISTER_MIME( - "ppsx", - "application/vnd.openxmlformats-officedocument.presentationml.slideshow"); - REGISTER_MIME("ppt", "application/vnd.ms-powerpoint"); - REGISTER_MIME("pptm", - "application/vnd.ms-powerpoint.presentation.macroenabled.12"); - REGISTER_MIME("pqa", "application/vnd.palm"); - REGISTER_MIME("prc", "application/x-mobipocket-ebook"); - REGISTER_MIME("pre", "application/vnd.lotus-freelance"); - REGISTER_MIME("prf", "application/pics-rules"); - REGISTER_MIME("ps", "application/postscript"); - REGISTER_MIME("psb", "application/vnd.3gpp.pic-bw-small"); - REGISTER_MIME("psd", "image/vnd.adobe.photoshop"); - REGISTER_MIME("psf", "application/x-font-linux-psf"); - REGISTER_MIME("pskcxml", "application/pskc+xml"); - REGISTER_MIME("ptid", "application/vnd.pvi.ptid1"); - REGISTER_MIME("pub", "application/x-mspublisher"); - REGISTER_MIME("pvb", "application/vnd.3gpp.pic-bw-var"); - REGISTER_MIME("pwn", "application/vnd.3m.post-it-notes"); - REGISTER_MIME("pya", "audio/vnd.ms-playready.media.pya"); - REGISTER_MIME("pyv", "video/vnd.ms-playready.media.pyv"); - REGISTER_MIME("qam", "application/vnd.epson.quickanime"); - REGISTER_MIME("qbo", "application/vnd.intu.qbo"); - REGISTER_MIME("qfx", "application/vnd.intu.qfx"); - REGISTER_MIME("qps", "application/vnd.publishare-delta-tree"); - REGISTER_MIME("qt", "video/quicktime"); - REGISTER_MIME("qwd", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("qwt", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("qxb", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("qxd", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("qxl", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("qxt", "application/vnd.quark.quarkxpress"); - REGISTER_MIME("ra", "audio/x-pn-realaudio"); - REGISTER_MIME("ram", "audio/x-pn-realaudio"); - REGISTER_MIME("rar", "application/x-rar-compressed"); - REGISTER_MIME("ras", "image/x-cmu-raster"); - REGISTER_MIME("rcprofile", "application/vnd.ipunplugged.rcprofile"); - REGISTER_MIME("rdf", "application/rdf+xml"); - REGISTER_MIME("rdz", "application/vnd.data-vision.rdz"); - REGISTER_MIME("rep", "application/vnd.businessobjects"); - REGISTER_MIME("res", "application/x-dtbresource+xml"); - REGISTER_MIME("rgb", "image/x-rgb"); - REGISTER_MIME("rif", "application/reginfo+xml"); - REGISTER_MIME("rip", "audio/vnd.rip"); - REGISTER_MIME("ris", "application/x-research-info-systems"); - REGISTER_MIME("rl", "application/resource-lists+xml"); - REGISTER_MIME("rlc", "image/vnd.fujixerox.edmics-rlc"); - REGISTER_MIME("rld", "application/resource-lists-diff+xml"); - REGISTER_MIME("rm", "application/vnd.rn-realmedia"); - REGISTER_MIME("rmi", "audio/midi"); - REGISTER_MIME("rmp", "audio/x-pn-realaudio-plugin"); - REGISTER_MIME("rms", "application/vnd.jcp.javame.midlet-rms"); - REGISTER_MIME("rmvb", "application/vnd.rn-realmedia-vbr"); - REGISTER_MIME("rnc", "application/relax-ng-compact-syntax"); - REGISTER_MIME("roa", "application/rpki-roa"); - REGISTER_MIME("roff", "text/troff"); - REGISTER_MIME("rp9", "application/vnd.cloanto.rp9"); - REGISTER_MIME("rpss", "application/vnd.nokia.radio-presets"); - REGISTER_MIME("rpst", "application/vnd.nokia.radio-preset"); - REGISTER_MIME("rq", "application/sparql-query"); - REGISTER_MIME("rs", "application/rls-services+xml"); - REGISTER_MIME("rsd", "application/rsd+xml"); - REGISTER_MIME("rss", "application/rss+xml"); - REGISTER_MIME("rtf", "application/rtf"); - REGISTER_MIME("rtx", "text/richtext"); - REGISTER_MIME("s", "text/x-asm"); - REGISTER_MIME("s3m", "audio/s3m"); - REGISTER_MIME("saf", "application/vnd.yamaha.smaf-audio"); - REGISTER_MIME("sbml", "application/sbml+xml"); - REGISTER_MIME("sc", "application/vnd.ibm.secure-container"); - REGISTER_MIME("scd", "application/x-msschedule"); - REGISTER_MIME("scm", "application/vnd.lotus-screencam"); - REGISTER_MIME("scq", "application/scvp-cv-request"); - REGISTER_MIME("scs", "application/scvp-cv-response"); - REGISTER_MIME("scurl", "text/vnd.curl.scurl"); - REGISTER_MIME("sda", "application/vnd.stardivision.draw"); - REGISTER_MIME("sdc", "application/vnd.stardivision.calc"); - REGISTER_MIME("sdd", "application/vnd.stardivision.impress"); - REGISTER_MIME("sdkd", "application/vnd.solent.sdkm+xml"); - REGISTER_MIME("sdkm", "application/vnd.solent.sdkm+xml"); - REGISTER_MIME("sdp", "application/sdp"); - REGISTER_MIME("sdw", "application/vnd.stardivision.writer"); - REGISTER_MIME("see", "application/vnd.seemail"); - REGISTER_MIME("seed", "application/vnd.fdsn.seed"); - REGISTER_MIME("sema", "application/vnd.sema"); - REGISTER_MIME("semd", "application/vnd.semd"); - REGISTER_MIME("semf", "application/vnd.semf"); - REGISTER_MIME("ser", "application/java-serialized-object"); - REGISTER_MIME("setpay", "application/set-payment-initiation"); - REGISTER_MIME("setreg", "application/set-registration-initiation"); - REGISTER_MIME("sfd-hdstx", "application/vnd.hydrostatix.sof-data"); - REGISTER_MIME("sfs", "application/vnd.spotfire.sfs"); - REGISTER_MIME("sfv", "text/x-sfv"); - REGISTER_MIME("sgi", "image/sgi"); - REGISTER_MIME("sgl", "application/vnd.stardivision.writer-global"); - REGISTER_MIME("sgm", "text/sgml"); - REGISTER_MIME("sgml", "text/sgml"); - REGISTER_MIME("sh", "application/x-sh"); - REGISTER_MIME("shar", "application/x-shar"); - REGISTER_MIME("shf", "application/shf+xml"); - REGISTER_MIME("sid", "image/x-mrsid-image"); - REGISTER_MIME("sig", "application/pgp-signature"); - REGISTER_MIME("sil", "audio/silk"); - REGISTER_MIME("silo", "model/mesh"); - REGISTER_MIME("sis", "application/vnd.symbian.install"); - REGISTER_MIME("sisx", "application/vnd.symbian.install"); - REGISTER_MIME("sit", "application/x-stuffit"); - REGISTER_MIME("sitx", "application/x-stuffitx"); - REGISTER_MIME("skd", "application/vnd.koan"); - REGISTER_MIME("skm", "application/vnd.koan"); - REGISTER_MIME("skp", "application/vnd.koan"); - REGISTER_MIME("skt", "application/vnd.koan"); - REGISTER_MIME("sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"); - REGISTER_MIME( - "sldx", - "application/vnd.openxmlformats-officedocument.presentationml.slide"); - REGISTER_MIME("slt", "application/vnd.epson.salt"); - REGISTER_MIME("sm", "application/vnd.stepmania.stepchart"); - REGISTER_MIME("smf", "application/vnd.stardivision.math"); - REGISTER_MIME("smi", "application/smil+xml"); - REGISTER_MIME("smil", "application/smil+xml"); - REGISTER_MIME("smv", "video/x-smv"); - REGISTER_MIME("smzip", "application/vnd.stepmania.package"); - REGISTER_MIME("snd", "audio/basic"); - REGISTER_MIME("snf", "application/x-font-snf"); - REGISTER_MIME("so", "application/octet-stream"); - REGISTER_MIME("spc", "application/x-pkcs7-certificates"); - REGISTER_MIME("spf", "application/vnd.yamaha.smaf-phrase"); - REGISTER_MIME("spl", "application/x-futuresplash"); - REGISTER_MIME("spot", "text/vnd.in3d.spot"); - REGISTER_MIME("spp", "application/scvp-vp-response"); - REGISTER_MIME("spq", "application/scvp-vp-request"); - REGISTER_MIME("spx", "audio/ogg"); - REGISTER_MIME("sql", "application/x-sql"); - REGISTER_MIME("src", "application/x-wais-source"); - REGISTER_MIME("srt", "application/x-subrip"); - REGISTER_MIME("sru", "application/sru+xml"); - REGISTER_MIME("srx", "application/sparql-results+xml"); - REGISTER_MIME("ssdl", "application/ssdl+xml"); - REGISTER_MIME("sse", "application/vnd.kodak-descriptor"); - REGISTER_MIME("ssf", "application/vnd.epson.ssf"); - REGISTER_MIME("ssml", "application/ssml+xml"); - REGISTER_MIME("st", "application/vnd.sailingtracker.track"); - REGISTER_MIME("stc", "application/vnd.sun.xml.calc.template"); - REGISTER_MIME("std", "application/vnd.sun.xml.draw.template"); - REGISTER_MIME("stf", "application/vnd.wt.stf"); - REGISTER_MIME("sti", "application/vnd.sun.xml.impress.template"); - REGISTER_MIME("stk", "application/hyperstudio"); - REGISTER_MIME("stl", "application/vnd.ms-pki.stl"); - REGISTER_MIME("str", "application/vnd.pg.format"); - REGISTER_MIME("stw", "application/vnd.sun.xml.writer.template"); - REGISTER_MIME("sub", "text/vnd.dvb.subtitle"); - REGISTER_MIME("sus", "application/vnd.sus-calendar"); - REGISTER_MIME("susp", "application/vnd.sus-calendar"); - REGISTER_MIME("sv4cpio", "application/x-sv4cpio"); - REGISTER_MIME("sv4crc", "application/x-sv4crc"); - REGISTER_MIME("svc", "application/vnd.dvb.service"); - REGISTER_MIME("svd", "application/vnd.svd"); - REGISTER_MIME("svg", "image/svg+xml"); - REGISTER_MIME("svgz", "image/svg+xml"); - REGISTER_MIME("swa", "application/x-director"); - REGISTER_MIME("swf", "application/x-shockwave-flash"); - REGISTER_MIME("swi", "application/vnd.aristanetworks.swi"); - REGISTER_MIME("sxc", "application/vnd.sun.xml.calc"); - REGISTER_MIME("sxd", "application/vnd.sun.xml.draw"); - REGISTER_MIME("sxg", "application/vnd.sun.xml.writer.global"); - REGISTER_MIME("sxi", "application/vnd.sun.xml.impress"); - REGISTER_MIME("sxm", "application/vnd.sun.xml.math"); - REGISTER_MIME("sxw", "application/vnd.sun.xml.writer"); - REGISTER_MIME("t", "text/troff"); - REGISTER_MIME("t3", "application/x-t3vm-image"); - REGISTER_MIME("taglet", "application/vnd.mynfc"); - REGISTER_MIME("tao", "application/vnd.tao.intent-module-archive"); - REGISTER_MIME("tar", "application/x-tar"); - REGISTER_MIME("tcap", "application/vnd.3gpp2.tcap"); - REGISTER_MIME("tcl", "application/x-tcl"); - REGISTER_MIME("teacher", "application/vnd.smart.teacher"); - REGISTER_MIME("tei", "application/tei+xml"); - REGISTER_MIME("teicorpus", "application/tei+xml"); - REGISTER_MIME("tex", "application/x-tex"); - REGISTER_MIME("texi", "application/x-texinfo"); - REGISTER_MIME("texinfo", "application/x-texinfo"); - REGISTER_MIME("text", "text/plain"); - REGISTER_MIME("tfi", "application/thraud+xml"); - REGISTER_MIME("tfm", "application/x-tex-tfm"); - REGISTER_MIME("tga", "image/x-tga"); - REGISTER_MIME("thmx", "application/vnd.ms-officetheme"); - REGISTER_MIME("tif", "image/tiff"); - REGISTER_MIME("tiff", "image/tiff"); - REGISTER_MIME("tmo", "application/vnd.tmobile-livetv"); - REGISTER_MIME("torrent", "application/x-bittorrent"); - REGISTER_MIME("tpl", "application/vnd.groove-tool-template"); - REGISTER_MIME("tpt", "application/vnd.trid.tpt"); - REGISTER_MIME("tr", "text/troff"); - REGISTER_MIME("tra", "application/vnd.trueapp"); - REGISTER_MIME("trm", "application/x-msterminal"); - REGISTER_MIME("tsd", "application/timestamped-data"); - REGISTER_MIME("tsv", "text/tab-separated-values"); - REGISTER_MIME("ttc", "application/x-font-ttf"); - REGISTER_MIME("ttf", "application/x-font-ttf"); - REGISTER_MIME("ttl", "text/turtle"); - REGISTER_MIME("twd", "application/vnd.simtech-mindmapper"); - REGISTER_MIME("twds", "application/vnd.simtech-mindmapper"); - REGISTER_MIME("txd", "application/vnd.genomatix.tuxedo"); - REGISTER_MIME("txf", "application/vnd.mobius.txf"); - REGISTER_MIME("txt", "text/plain"); - REGISTER_MIME("u32", "application/x-authorware-bin"); - REGISTER_MIME("udeb", "application/x-debian-package"); - REGISTER_MIME("ufd", "application/vnd.ufdl"); - REGISTER_MIME("ufdl", "application/vnd.ufdl"); - REGISTER_MIME("ulx", "application/x-glulx"); - REGISTER_MIME("umj", "application/vnd.umajin"); - REGISTER_MIME("unityweb", "application/vnd.unity"); - REGISTER_MIME("uoml", "application/vnd.uoml+xml"); - REGISTER_MIME("uri", "text/uri-list"); - REGISTER_MIME("uris", "text/uri-list"); - REGISTER_MIME("urls", "text/uri-list"); - REGISTER_MIME("ustar", "application/x-ustar"); - REGISTER_MIME("utz", "application/vnd.uiq.theme"); - REGISTER_MIME("uu", "text/x-uuencode"); - REGISTER_MIME("uva", "audio/vnd.dece.audio"); - REGISTER_MIME("uvd", "application/vnd.dece.data"); - REGISTER_MIME("uvf", "application/vnd.dece.data"); - REGISTER_MIME("uvg", "image/vnd.dece.graphic"); - REGISTER_MIME("uvh", "video/vnd.dece.hd"); - REGISTER_MIME("uvi", "image/vnd.dece.graphic"); - REGISTER_MIME("uvm", "video/vnd.dece.mobile"); - REGISTER_MIME("uvp", "video/vnd.dece.pd"); - REGISTER_MIME("uvs", "video/vnd.dece.sd"); - REGISTER_MIME("uvt", "application/vnd.dece.ttml+xml"); - REGISTER_MIME("uvu", "video/vnd.uvvu.mp4"); - REGISTER_MIME("uvv", "video/vnd.dece.video"); - REGISTER_MIME("uvva", "audio/vnd.dece.audio"); - REGISTER_MIME("uvvd", "application/vnd.dece.data"); - REGISTER_MIME("uvvf", "application/vnd.dece.data"); - REGISTER_MIME("uvvg", "image/vnd.dece.graphic"); - REGISTER_MIME("uvvh", "video/vnd.dece.hd"); - REGISTER_MIME("uvvi", "image/vnd.dece.graphic"); - REGISTER_MIME("uvvm", "video/vnd.dece.mobile"); - REGISTER_MIME("uvvp", "video/vnd.dece.pd"); - REGISTER_MIME("uvvs", "video/vnd.dece.sd"); - REGISTER_MIME("uvvt", "application/vnd.dece.ttml+xml"); - REGISTER_MIME("uvvu", "video/vnd.uvvu.mp4"); - REGISTER_MIME("uvvv", "video/vnd.dece.video"); - REGISTER_MIME("uvvx", "application/vnd.dece.unspecified"); - REGISTER_MIME("uvvz", "application/vnd.dece.zip"); - REGISTER_MIME("uvx", "application/vnd.dece.unspecified"); - REGISTER_MIME("uvz", "application/vnd.dece.zip"); - REGISTER_MIME("vcard", "text/vcard"); - REGISTER_MIME("vcd", "application/x-cdlink"); - REGISTER_MIME("vcf", "text/x-vcard"); - REGISTER_MIME("vcg", "application/vnd.groove-vcard"); - REGISTER_MIME("vcs", "text/x-vcalendar"); - REGISTER_MIME("vcx", "application/vnd.vcx"); - REGISTER_MIME("vis", "application/vnd.visionary"); - REGISTER_MIME("viv", "video/vnd.vivo"); - REGISTER_MIME("vob", "video/x-ms-vob"); - REGISTER_MIME("vor", "application/vnd.stardivision.writer"); - REGISTER_MIME("vox", "application/x-authorware-bin"); - REGISTER_MIME("vrml", "model/vrml"); - REGISTER_MIME("vsd", "application/vnd.visio"); - REGISTER_MIME("vsf", "application/vnd.vsf"); - REGISTER_MIME("vss", "application/vnd.visio"); - REGISTER_MIME("vst", "application/vnd.visio"); - REGISTER_MIME("vsw", "application/vnd.visio"); - REGISTER_MIME("vtu", "model/vnd.vtu"); - REGISTER_MIME("vxml", "application/voicexml+xml"); - REGISTER_MIME("w3d", "application/x-director"); - REGISTER_MIME("wad", "application/x-doom"); - REGISTER_MIME("wav", "audio/x-wav"); - REGISTER_MIME("wax", "audio/x-ms-wax"); - REGISTER_MIME("wbmp", "image/vnd.wap.wbmp"); - REGISTER_MIME("wbs", "application/vnd.criticaltools.wbs+xml"); - REGISTER_MIME("wbxml", "application/vnd.wap.wbxml"); - REGISTER_MIME("wcm", "application/vnd.ms-works"); - REGISTER_MIME("wdb", "application/vnd.ms-works"); - REGISTER_MIME("wdp", "image/vnd.ms-photo"); - REGISTER_MIME("weba", "audio/webm"); - REGISTER_MIME("webm", "video/webm"); - REGISTER_MIME("webp", "image/webp"); - REGISTER_MIME("wg", "application/vnd.pmi.widget"); - REGISTER_MIME("wgt", "application/widget"); - REGISTER_MIME("wks", "application/vnd.ms-works"); - REGISTER_MIME("wm", "video/x-ms-wm"); - REGISTER_MIME("wma", "audio/x-ms-wma"); - REGISTER_MIME("wmd", "application/x-ms-wmd"); - REGISTER_MIME("wmf", "application/x-msmetafile"); - REGISTER_MIME("wml", "text/vnd.wap.wml"); - REGISTER_MIME("wmlc", "application/vnd.wap.wmlc"); - REGISTER_MIME("wmls", "text/vnd.wap.wmlscript"); - REGISTER_MIME("wmlsc", "application/vnd.wap.wmlscriptc"); - REGISTER_MIME("wmv", "video/x-ms-wmv"); - REGISTER_MIME("wmx", "video/x-ms-wmx"); - REGISTER_MIME("wmz", "application/x-ms-wmz"); - // REGISTER_MIME("wmz", "application/x-msmetafile"); - REGISTER_MIME("woff", "application/font-woff"); - REGISTER_MIME("wpd", "application/vnd.wordperfect"); - REGISTER_MIME("wpl", "application/vnd.ms-wpl"); - REGISTER_MIME("wps", "application/vnd.ms-works"); - REGISTER_MIME("wqd", "application/vnd.wqd"); - REGISTER_MIME("wri", "application/x-mswrite"); - REGISTER_MIME("wrl", "model/vrml"); - REGISTER_MIME("wsdl", "application/wsdl+xml"); - REGISTER_MIME("wspolicy", "application/wspolicy+xml"); - REGISTER_MIME("wtb", "application/vnd.webturbo"); - REGISTER_MIME("wvx", "video/x-ms-wvx"); - REGISTER_MIME("x32", "application/x-authorware-bin"); - REGISTER_MIME("x3d", "model/x3d+xml"); - REGISTER_MIME("x3db", "model/x3d+binary"); - REGISTER_MIME("x3dbz", "model/x3d+binary"); - REGISTER_MIME("x3dv", "model/x3d+vrml"); - REGISTER_MIME("x3dvz", "model/x3d+vrml"); - REGISTER_MIME("x3dz", "model/x3d+xml"); - REGISTER_MIME("xaml", "application/xaml+xml"); - REGISTER_MIME("xap", "application/x-silverlight-app"); - REGISTER_MIME("xar", "application/vnd.xara"); - REGISTER_MIME("xbap", "application/x-ms-xbap"); - REGISTER_MIME("xbd", "application/vnd.fujixerox.docuworks.binder"); - REGISTER_MIME("xbm", "image/x-xbitmap"); - REGISTER_MIME("xdf", "application/xcap-diff+xml"); - REGISTER_MIME("xdm", "application/vnd.syncml.dm+xml"); - REGISTER_MIME("xdp", "application/vnd.adobe.xdp+xml"); - REGISTER_MIME("xdssc", "application/dssc+xml"); - REGISTER_MIME("xdw", "application/vnd.fujixerox.docuworks"); - REGISTER_MIME("xenc", "application/xenc+xml"); - REGISTER_MIME("xer", "application/patch-ops-error+xml"); - REGISTER_MIME("xfdf", "application/vnd.adobe.xfdf"); - REGISTER_MIME("xfdl", "application/vnd.xfdl"); - REGISTER_MIME("xht", "application/xhtml+xml"); - REGISTER_MIME("xhtml", "application/xhtml+xml"); - REGISTER_MIME("xhvml", "application/xv+xml"); - REGISTER_MIME("xif", "image/vnd.xiff"); - REGISTER_MIME("xla", "application/vnd.ms-excel"); - REGISTER_MIME("xlam", "application/vnd.ms-excel.addin.macroenabled.12"); - REGISTER_MIME("xlc", "application/vnd.ms-excel"); - REGISTER_MIME("xlf", "application/x-xliff+xml"); - REGISTER_MIME("xlm", "application/vnd.ms-excel"); - REGISTER_MIME("xls", "application/vnd.ms-excel"); - REGISTER_MIME("xlsb", - "application/vnd.ms-excel.sheet.binary.macroenabled.12"); - REGISTER_MIME("xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"); - REGISTER_MIME( - "xlsx", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - REGISTER_MIME("xlt", "application/vnd.ms-excel"); - REGISTER_MIME("xltm", "application/vnd.ms-excel.template.macroenabled.12"); - REGISTER_MIME( - "xltx", - "application/vnd.openxmlformats-officedocument.spreadsheetml.template"); - REGISTER_MIME("xlw", "application/vnd.ms-excel"); - REGISTER_MIME("xm", "audio/xm"); - REGISTER_MIME("xml", "application/xml"); - REGISTER_MIME("xo", "application/vnd.olpc-sugar"); - REGISTER_MIME("xop", "application/xop+xml"); - REGISTER_MIME("xpi", "application/x-xpinstall"); - REGISTER_MIME("xpl", "application/xproc+xml"); - REGISTER_MIME("xpm", "image/x-xpixmap"); - REGISTER_MIME("xpr", "application/vnd.is-xpr"); - REGISTER_MIME("xps", "application/vnd.ms-xpsdocument"); - REGISTER_MIME("xpw", "application/vnd.intercon.formnet"); - REGISTER_MIME("xpx", "application/vnd.intercon.formnet"); - REGISTER_MIME("xsl", "application/xml"); - REGISTER_MIME("xslt", "application/xslt+xml"); - REGISTER_MIME("xsm", "application/vnd.syncml+xml"); - REGISTER_MIME("xspf", "application/xspf+xml"); - REGISTER_MIME("xul", "application/vnd.mozilla.xul+xml"); - REGISTER_MIME("xvm", "application/xv+xml"); - REGISTER_MIME("xvml", "application/xv+xml"); - REGISTER_MIME("xwd", "image/x-xwindowdump"); - REGISTER_MIME("xyz", "chemical/x-xyz"); - REGISTER_MIME("xz", "application/x-xz"); - REGISTER_MIME("yang", "application/yang"); - REGISTER_MIME("yin", "application/yin+xml"); - REGISTER_MIME("z1", "application/x-zmachine"); - REGISTER_MIME("z2", "application/x-zmachine"); - REGISTER_MIME("z3", "application/x-zmachine"); - REGISTER_MIME("z4", "application/x-zmachine"); - REGISTER_MIME("z5", "application/x-zmachine"); - REGISTER_MIME("z6", "application/x-zmachine"); - REGISTER_MIME("z7", "application/x-zmachine"); - REGISTER_MIME("z8", "application/x-zmachine"); - REGISTER_MIME("zaz", "application/vnd.zzazz.deck+xml"); - REGISTER_MIME("zip", "application/zip"); - REGISTER_MIME("zir", "application/vnd.zul"); - REGISTER_MIME("zirz", "application/vnd.zul"); - REGISTER_MIME("zmm", "application/vnd.handheld-entertainment+xml"); -#undef REGISTER_MIME - http_mimetype_stats(); -} diff --git a/ext/iodine/http_internal.h b/ext/iodine/http_internal.h deleted file mode 100644 index 1f3cd911..00000000 --- a/ext/iodine/http_internal.h +++ /dev/null @@ -1,237 +0,0 @@ -/* -Copyright: Boaz Segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_HTTP_INTERNAL_H -#define H_HTTP_INTERNAL_H - -#include -/* subscription lists have a long lifetime */ -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_INCLUDE_LINKED_LIST -#include - -#include - -#ifndef __MINGW32__ -#include -#endif -#include - -/* ***************************************************************************** -Types -***************************************************************************** */ - -typedef struct http_fio_protocol_s http_fio_protocol_s; -typedef struct http_vtable_s http_vtable_s; - -struct http_vtable_s { - /** Should send existing headers and data */ - int (*const http_send_body)(http_s *h, void *data, uintptr_t length); - /** Should send existing headers and file */ - int (*const http_sendfile)(http_s *h, int fd, uintptr_t length, - uintptr_t offset); - /** Should send existing headers and data and prepare for streaming */ - int (*const http_stream)(http_s *h, void *data, uintptr_t length); - /** Should send existing headers or complete streaming */ - void (*const http_finish)(http_s *h); - /** Push for data. */ - int (*const http_push_data)(http_s *h, void *data, uintptr_t length, - FIOBJ mime_type); - /** Upgrades a connection to Websockets. */ - int (*const http2websocket)(http_s *h, websocket_settings_s *arg); - /** Push for files. */ - int (*const http_push_file)(http_s *h, FIOBJ filename, FIOBJ mime_type); - /** Pauses the request / response handling. */ - void (*http_on_pause)(http_s *, http_fio_protocol_s *); - - /** Resumes a request / response handling. */ - void (*http_on_resume)(http_s *, http_fio_protocol_s *); - /** hijacks the socket aaway from the protocol. */ - intptr_t (*http_hijack)(http_s *h, fio_str_info_s *leftover); - - /** Upgrades an HTTP connection to an EventSource (SSE) connection. */ - int (*http_upgrade2sse)(http_s *h, http_sse_s *sse); - /** Writes data to an EventSource (SSE) connection. MUST free the FIOBJ. */ - int (*http_sse_write)(http_sse_s *sse, FIOBJ str); - /** Closes an EventSource (SSE) connection. */ - int (*http_sse_close)(http_sse_s *sse); -}; - -struct http_fio_protocol_s { - fio_protocol_s protocol; /* facil.io protocol */ - intptr_t uuid; /* socket uuid */ - http_settings_s *settings; /* pointer to HTTP settings */ -}; - -#define http2protocol(h) ((http_fio_protocol_s *)h->private_data.flag) - -/* ***************************************************************************** -Constants that shouldn't be accessed by the users (`fiobj_dup` required). -***************************************************************************** */ - -extern FIOBJ HTTP_HEADER_ACCEPT_RANGES; -extern FIOBJ HTTP_HEADER_WS_SEC_CLIENT_KEY; -extern FIOBJ HTTP_HEADER_WS_SEC_KEY; -extern FIOBJ HTTP_HVALUE_BYTES; -extern FIOBJ HTTP_HVALUE_CLOSE; -extern FIOBJ HTTP_HVALUE_CONTENT_TYPE_DEFAULT; -extern FIOBJ HTTP_HVALUE_GZIP; -extern FIOBJ HTTP_HVALUE_KEEP_ALIVE; -extern FIOBJ HTTP_HVALUE_MAX_AGE; -extern FIOBJ HTTP_HVALUE_NO_CACHE; -extern FIOBJ HTTP_HVALUE_SSE_MIME; -extern FIOBJ HTTP_HVALUE_WEBSOCKET; -extern FIOBJ HTTP_HVALUE_WS_SEC_VERSION; -extern FIOBJ HTTP_HVALUE_WS_UPGRADE; -extern FIOBJ HTTP_HVALUE_WS_VERSION; - -/* ***************************************************************************** -HTTP request/response object management -***************************************************************************** */ - -static inline void http_s_new(http_s *h, http_fio_protocol_s *owner, - http_vtable_s *vtbl) { - *h = (http_s){ - .private_data = - { - .vtbl = vtbl, - .flag = (uintptr_t)owner, - .out_headers = fiobj_hash_new(), - }, - .headers = fiobj_hash_new(), - .received_at = fio_last_tick(), - .status = 200, - }; -} - -static inline void http_s_destroy(http_s *h, uint8_t log) { - if (log && h->status && !h->status_str) { - http_write_log(h); - } - fiobj_free(h->method); - fiobj_free(h->status_str); - fiobj_free(h->private_data.out_headers); - fiobj_free(h->headers); - fiobj_free(h->version); - fiobj_free(h->query); - fiobj_free(h->path); - fiobj_free(h->cookies); - fiobj_free(h->body); - fiobj_free(h->params); - - *h = (http_s){ - .private_data.vtbl = h->private_data.vtbl, - .private_data.flag = h->private_data.flag, - }; -} - -static inline void http_s_clear(http_s *h, uint8_t log) { - http_s_destroy(h, log); - http_s_new(h, (http_fio_protocol_s *)h->private_data.flag, - h->private_data.vtbl); -} - -/** tests handle validity */ -#define HTTP_INVALID_HANDLE(h) \ - (!(h) || (!(h)->method && !(h)->status_str && (h)->status)) - -/* ***************************************************************************** -Request / Response Handlers -***************************************************************************** */ - -/** Use this function to handle HTTP requests.*/ -void http_on_request_handler______internal(http_s *h, - http_settings_s *settings); - -void http_on_response_handler______internal(http_s *h, - http_settings_s *settings); -int http_send_error2(size_t error, intptr_t uuid, http_settings_s *settings); - -/* ***************************************************************************** -EventSource Support (SSE) -***************************************************************************** */ - -typedef struct http_sse_internal_s { - http_sse_s sse; /* the user SSE settings */ - intptr_t uuid; /* the socket's uuid */ - http_vtable_s *vtable; /* the protocol's vtable */ - uintptr_t id; /* the SSE identifier */ - fio_ls_s subscriptions; /* Subscription List */ - fio_lock_i lock; /* Subscription List lock */ - size_t ref; /* reference count */ -} http_sse_internal_s; - -static inline void http_sse_init(http_sse_internal_s *sse, intptr_t uuid, - http_vtable_s *vtbl, http_sse_s *args) { - *sse = (http_sse_internal_s){ - .sse = *args, - .uuid = uuid, - .subscriptions = FIO_LS_INIT(sse->subscriptions), - .vtable = vtbl, - .ref = 1, - }; -} - -static inline void http_sse_try_free(http_sse_internal_s *sse) { - if (fio_atomic_sub(&sse->ref, 1)) - return; - fio_free(sse); -} - -static inline void http_sse_destroy(http_sse_internal_s *sse) { - while (fio_ls_any(&sse->subscriptions)) { - void *sub = fio_ls_pop(&sse->subscriptions); - fio_unsubscribe(sub); - } - if (sse->sse.on_close) - sse->sse.on_close(&sse->sse); - sse->uuid = -1; - http_sse_try_free(sse); -} - -/* ***************************************************************************** -Helpers -***************************************************************************** */ - -/** sets an outgoing header only if it doesn't exist */ -static inline void set_header_if_missing(FIOBJ hash, FIOBJ name, FIOBJ value) { - FIOBJ old = fiobj_hash_replace(hash, name, value); - if (!old) - return; - fiobj_hash_replace(hash, name, old); - fiobj_free(value); -} - -/** sets an outgoing header, collecting duplicates in an Array (i.e. cookies) - */ -static inline void set_header_add(FIOBJ hash, FIOBJ name, FIOBJ value) { - FIOBJ old = fiobj_hash_replace(hash, name, value); - if (!old) - return; - if (!value) { - fiobj_free(old); - return; - } - if (!FIOBJ_TYPE_IS(old, FIOBJ_T_ARRAY)) { - FIOBJ tmp = fiobj_ary_new(); - fiobj_ary_push(tmp, old); - old = tmp; - } - if (FIOBJ_TYPE_IS(value, FIOBJ_T_ARRAY)) { - for (size_t i = 0; i < fiobj_ary_count(value); ++i) { - fiobj_ary_push(old, fiobj_dup(fiobj_ary_index(value, i))); - } - /* frees `value` */ - fiobj_hash_set(hash, name, old); - return; - } - /* value will be owned by both hash and array */ - fiobj_ary_push(old, value); - /* don't free `value` (leave in array) */ - fiobj_hash_replace(hash, name, old); -} - -#endif /* H_HTTP_INTERNAL_H */ diff --git a/ext/iodine/http_mime_parser.h b/ext/iodine/http_mime_parser.h deleted file mode 100644 index 4d0a6994..00000000 --- a/ext/iodine/http_mime_parser.h +++ /dev/null @@ -1,350 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_HTTP_MIME_PARSER_H -#define H_HTTP_MIME_PARSER_H -#include -#include -#include - -/* ***************************************************************************** -Known Limitations: - -- Doesn't support nested multipart form structures (i.e., multi-file selection). - See: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - -To circumvent limitation, initialize a new parser to parse nested multiparts. -***************************************************************************** */ - -/* ***************************************************************************** -The HTTP MIME Multipart Form Parser Type -***************************************************************************** */ - -/** all data id read-only / for internal use */ -typedef struct { - char *boundary; - size_t boundary_len; - uint8_t in_obj; - uint8_t done; - uint8_t error; -} http_mime_parser_s; - -/* ***************************************************************************** -Callbacks to be implemented. -***************************************************************************** */ - -/** Called when all the data is available at once. */ -static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name, - size_t name_len, void *filename, - size_t filename_len, void *mimetype, - size_t mimetype_len, void *value, - size_t value_len); - -/** Called when the data didn't fit in the buffer. Data will be streamed. */ -static void http_mime_parser_on_partial_start( - http_mime_parser_s *parser, void *name, size_t name_len, void *filename, - size_t filename_len, void *mimetype, size_t mimetype_len); - -/** Called when partial data is available. */ -static void http_mime_parser_on_partial_data(http_mime_parser_s *parser, - void *value, size_t value_len); - -/** Called when the partial data is complete. */ -static void http_mime_parser_on_partial_end(http_mime_parser_s *parser); - -/** - * Called when URL decoding is required. - * - * Should support inplace decoding (`dest == encoded`). - * - * Should return the length of the decoded string. - */ -static size_t http_mime_decode_url(char *dest, const char *encoded, - size_t length); - -/* ***************************************************************************** -API -***************************************************************************** */ - -/** - * Takes the HTTP Content-Type header and initializes the parser data. - * - * Note: the Content-Type header should persist in memory while the parser is in - * use. - */ -static int http_mime_parser_init(http_mime_parser_s *parser, char *content_type, - size_t len); - -/** - * Consumes data from a streaming buffer. - * - * The data might be partially consumed, in which case the unconsumed data - * should be resent to the parser as more data becomes available. - * - * Note: test the `parser->done` and `parser->error` flags between iterations. - */ -static size_t http_mime_parse(http_mime_parser_s *parser, void *buffer, - size_t length); - -/* ***************************************************************************** -Implementations -***************************************************************************** */ - -/** takes the HTTP Content-Type header and initializes the parser data. */ -static int http_mime_parser_init(http_mime_parser_s *parser, char *content_type, - size_t len) { - *parser = (http_mime_parser_s){.done = 0}; - if (len < 14 || strncasecmp("multipart/form", content_type, 14)) - return -1; - char *cut = memchr(content_type, ';', len); - while (cut) { - ++cut; - len -= (size_t)(cut - content_type); - while (len && cut[0] == ' ') { - --len; - ++cut; - } - if (len <= 9) - return -1; - if (strncasecmp("boundary=", cut, 9)) { - content_type = cut; - cut = memchr(cut, ';', len); - continue; - } - cut += 9; - len -= 9; - content_type = cut; - parser->boundary = content_type; - if ((cut = memchr(content_type, ';', len))) - parser->boundary_len = (size_t)(cut - content_type); - else - parser->boundary_len = len; - return 0; - } - return -1; -} - -/** - * Consumes data from a streaming buffer. - * - * The data might be partially consumed, in which case the unconsumed data - * should be resent to the parser as more data becomes available. - * - * Note: test the `parser->done` and `parser->error` flags between iterations. - */ -static size_t http_mime_parse(http_mime_parser_s *parser, void *buffer, - size_t length) { - int first_run = 1; - char *pos = buffer; - const char *stop = pos + length; - if (!length) - goto end_of_data; -consume_partial: - if (parser->in_obj) { - /* we're in an object longer than the buffer */ - char *start = pos; - char *end = start; - do { - end = memchr(end, '\n', (size_t)(stop - end)); - } while (end && ++end && - (size_t)(stop - end) >= (4 + parser->boundary_len) && - (end[0] != '-' || end[1] != '-' || - memcmp(end + 2, parser->boundary, parser->boundary_len))); - if (!end) { - end = (char *)stop; - pos = end; - if (end - start) - http_mime_parser_on_partial_data(parser, start, (size_t)(end - start)); - goto end_of_data; - } else if (end + 4 + parser->boundary_len >= stop) { - end -= 2; - if (end[0] == '\r') - --end; - pos = end; - if (end - start) - http_mime_parser_on_partial_data(parser, start, (size_t)(end - start)); - goto end_of_data; - } - size_t len = (end - start) - 1; - if (start[len - 1] == '\r') - --len; - if (len) - http_mime_parser_on_partial_data(parser, start, len); - http_mime_parser_on_partial_end(parser); - pos = end; - parser->in_obj = 0; - first_run = 0; - } else if (length < (4 + parser->boundary_len) || pos[0] != '-' || - pos[1] != '-' || - memcmp(pos + 2, parser->boundary, parser->boundary_len)) - goto error; - /* We're at a boundary */ - while (pos < stop) { - char *start; - char *end; - char *name = NULL; - uint32_t name_len = 0; - char *value = NULL; - uint32_t value_len = 0; - char *filename = NULL; - uint32_t filename_len = 0; - char *mime = NULL; - uint32_t mime_len = 0; - uint8_t header_count = 0; - /* test for ending */ - if (pos[2 + parser->boundary_len] == '-' && - pos[3 + parser->boundary_len] == '-') { - pos += 5 + parser->boundary_len; - if (pos > stop) - pos = (char *)stop; - else if (pos < stop && pos[0] == '\n') - ++pos; - goto done; - } - start = pos + 3 + parser->boundary_len; - if (start[0] == '\n') { - /* should be true, unless new line marker was just '\n' */ - ++start; - } - /* consume headers */ - while (start + 4 < stop && start[0] != '\n' && start[1] != '\n') { - end = memchr(start, '\n', (size_t)(stop - start)); - if (!end) { - if (first_run) - goto error; - goto end_of_data; - } - if (end - start > 29 && !strncasecmp(start, "content-disposition:", 20)) { - /* content-disposition header */ - start = memchr(start + 20, ';', end - (start + 20)); - // if (!start) - // start = end + 1; - while (start) { - ++start; - if (start[0] == ' ') - ++start; - if (start + 6 < end && !strncasecmp(start, "name=", 5)) { - name = start + 5; - if (name[0] == '"') - ++name; - start = memchr(name, ';', (size_t)(end - start)); - if (!start) { - name_len = (size_t)(end - name); - if (name[name_len - 1] == '\r') - --name_len; - } else { - name_len = (size_t)(start - name); - } - if (name[name_len - 1] == '"') - --name_len; - } else if (start + 9 < end && !strncasecmp(start, "filename", 8)) { - uint8_t encoded = 0; - start += 8; - if (start[0] == '*') { - encoded = 1; - ++start; - } - if (start[0] != '=') - goto error; - ++start; - if (start[0] == ' ') - ++start; - if (start[0] == '"') - ++start; - if (filename && !encoded) { - /* prefer URL encoded version */ - start = memchr(filename, ';', (size_t)(end - start)); - continue; - } - filename = start; - start = memchr(filename, ';', (size_t)(end - start)); - if (!start) { - filename_len = (size_t)((end - filename)); - if (filename[filename_len - 1] == '\r') { - --filename_len; - } - } else { - filename_len = (size_t)(start - filename); - } - if (filename[filename_len - 1] == '"') - --filename_len; - if (encoded) { - ssize_t new_len = - http_mime_decode_url(filename, filename, filename_len); - if (new_len > 0) - filename_len = new_len; - } - } else { - start = memchr(start, ';', (size_t)(end - start)); - } - } - } else if (end - start > 14 && !strncasecmp(start, "content-type:", 13)) { - /* content-type header */ - start += 13; - if (start[0] == ' ') - ++start; - mime = start; - start = memchr(start, ';', (size_t)(end - start)); - if (!start) { - mime_len = (size_t)(end - mime); - if (mime[mime_len - 1] == '\r') - --mime_len; - } else { - mime_len = (size_t)(start - mime); - } - } - start = end + 1; - if (header_count++ > 4) - goto error; - } - if (!name) { - if (start + 4 >= stop) - goto end_of_data; - goto error; - } - - /* advance to end of boundry */ - ++start; - if (start[0] == '\n') - ++start; - value = start; - end = start; - do { - end = memchr(end, '\n', (size_t)(stop - end)); - } while (end && ++end && - (size_t)(stop - end) >= (4 + parser->boundary_len) && - (end[0] != '-' || end[1] != '-' || - memcmp(end + 2, parser->boundary, parser->boundary_len))); - if (!end || end + 4 + parser->boundary_len >= stop) { - if (first_run) { - http_mime_parser_on_partial_start(parser, name, name_len, filename, - filename_len, mime, mime_len); - parser->in_obj = 1; - pos = value; - goto consume_partial; - } - goto end_of_data; - } - value_len = (size_t)((end - value) - 1); - if (value[value_len - 1] == '\r') - --value_len; - pos = end; - http_mime_parser_on_data(parser, name, name_len, filename, filename_len, - mime, mime_len, value, value_len); - first_run = 0; - } -end_of_data: - return (size_t)((uintptr_t)pos - (uintptr_t)buffer); -done: - parser->done = 1; - parser->error = 0; - return (size_t)((uintptr_t)pos - (uintptr_t)buffer); -error: - parser->done = 0; - parser->error = 1; - return (size_t)((uintptr_t)pos - (uintptr_t)buffer); -} -#endif diff --git a/ext/iodine/iodine.c b/ext/iodine/iodine.c index a0c1272c..510c3662 100644 --- a/ext/iodine/iodine.c +++ b/ext/iodine/iodine.c @@ -1,1430 +1,152 @@ +/* core include */ #include "iodine.h" -#include - -#define FIO_INCLUDE_LINKED_LIST -#include "fio.h" -#include "fio_cli.h" -/* ***************************************************************************** -OS specific patches -***************************************************************************** */ - -#ifdef __APPLE__ -#include -#endif - -/** Any patches required by the running environment for consistent behavior */ -static void patch_env(void) { -#ifdef __APPLE__ - /* patch for dealing with the High Sierra `fork` limitations */ - void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY); - (void)obj_c_runtime; -#endif -} - -/* ***************************************************************************** -Constants and State -***************************************************************************** */ - -VALUE IodineModule; -VALUE IodineBaseModule; - -/** Default connection settings for {Iodine.listen} and {Iodine.connect}. */ -VALUE iodine_default_args; - -ID iodine_call_id; -ID iodine_to_s_id; - -static VALUE address_sym; -static VALUE app_sym; -static VALUE body_sym; -static VALUE cookies_sym; -static VALUE handler_sym; -static VALUE headers_sym; -static VALUE log_sym; -static VALUE max_body_sym; -static VALUE max_clients_sym; -static VALUE max_headers_sym; -static VALUE max_msg_sym; -static VALUE method_sym; -static VALUE path_sym; -static VALUE ping_sym; -static VALUE port_sym; -static VALUE public_sym; -static VALUE service_sym; -static VALUE timeout_sym; -static VALUE tls_sym; -static VALUE url_sym; - /* ***************************************************************************** -Idling +Deprecation Warnings ***************************************************************************** */ -/* performs a Ruby state callback and clears the Ruby object's memory */ -static void iodine_perform_on_idle_callback(void *blk_) { - VALUE blk = (VALUE)blk_; - IodineCaller.call(blk, iodine_call_id); - IodineStore.remove(blk); - fio_state_callback_remove(FIO_CALL_ON_IDLE, iodine_perform_on_idle_callback, - blk_); -} - -/** -Schedules a single occuring event for the next idle cycle. - -To schedule a reoccuring event, reschedule the event at the end of it's -run. - -i.e. - - IDLE_PROC = Proc.new { puts "idle"; Iodine.on_idle &IDLE_PROC } - Iodine.on_idle &IDLE_PROC -*/ -static VALUE iodine_sched_on_idle(VALUE self) { - // clang-format on - rb_need_block(); - VALUE block = rb_block_proc(); - IodineStore.add(block); - fio_state_callback_add(FIO_CALL_ON_IDLE, iodine_perform_on_idle_callback, - (void *)block); - return block; - (void)self; -} - -/* ***************************************************************************** -Running Iodine -***************************************************************************** */ - -typedef struct { - int16_t threads; - int16_t workers; -} iodine_start_params_s; - -static void *iodine_run_outside_GVL(void *params_) { - iodine_start_params_s *params = params_; - fio_start(.threads = params->threads, .workers = params->workers); - return NULL; -} +/** @deprecated use {Iodine::TLS.add_cert}. */ +static VALUE iodine_tls_cert_add_old_name(int argc, VALUE *argv, VALUE self); /* ***************************************************************************** -Core API +Initialize module ***************************************************************************** */ -/** - * Returns the number of worker threads that will be used when {Iodine.start} - * is called. - * - * Negative numbers are translated as fractions of the number of CPU cores. - * i.e., -2 == half the number of detected CPU cores. - * - * Zero values promise nothing (iodine will decide what to do with them). - * - * @return [FixNum] Thread Count - */ -static VALUE iodine_threads_get(VALUE self) { - VALUE i = rb_ivar_get(self, rb_intern2("@threads", 8)); - if (i == Qnil) - i = INT2NUM(0); - return i; -} - -/** - * Sets the number of worker threads that will be used when {Iodine.start} - * is called. - * - * Negative numbers are translated as fractions of the number of CPU cores. - * i.e., -2 == half the number of detected CPU cores. - * - * Zero values promise nothing (iodine will decide what to do with them). - * - * @param thread_count [FixNum] The number of worker threads to use - */ -static VALUE iodine_threads_set(VALUE self, VALUE val) { - Check_Type(val, T_FIXNUM); - if (NUM2SSIZET(val) >= (1 << 12)) { - rb_raise(rb_eRangeError, "requsted thread count is out of range."); - } - rb_ivar_set(self, rb_intern2("@threads", 8), val); - return val; -} - -/** - * Gets the logging level used for Iodine messages. - * - * Levels range from 0-5, where: - * - * 0 == Quite (no messages) - * 1 == Fatal Errors only. - * 2 == Errors only (including fatal errors). - * 3 == Warnings and errors only. - * 4 == Informational messages, warnings and errors (default). - * 5 == Everything, including debug information. - * - * Logging is always performed to the process's STDERR and can be piped away. - * - * @return [FixNum] Logging Level - * - * NOTE: this does NOT effect HTTP logging. - */ -static VALUE iodine_logging_get(VALUE self) { - return INT2FIX(FIO_LOG_LEVEL); - (void)self; -} - -/** - * Gets the logging level used for Iodine messages. - * - * Levels range from 0-5, where: - * - * 0 == Quite (no messages) - * 1 == Fatal Errors only. - * 2 == Errors only (including fatal errors). - * 3 == Warnings and errors only. - * 4 == Informational messages, warnings and errors (default). - * 5 == Everything, including debug information. - * - * Logging is always performed to the process's STDERR and can be piped away. - * - * @param log_level [FixNum] Sets the logging level - * - * NOTE: this does NOT effect HTTP logging. - */ -static VALUE iodine_logging_set(VALUE self, VALUE val) { - Check_Type(val, T_FIXNUM); - FIO_LOG_LEVEL = FIX2INT(val); - return self; -} - -/** - * Returns the number of worker processes that will be used when {Iodine.start} - * is called. - * - * Negative numbers are translated as fractions of the number of CPU cores. - * i.e., -2 == half the number of detected CPU cores. - * - * Zero values promise nothing (iodine will decide what to do with them). - * - * 1 == single process mode, the msater process acts as a worker process. - * - * @return [FixNum] Worker Count - */ -static VALUE iodine_workers_get(VALUE self) { - VALUE i = rb_ivar_get(self, rb_intern2("@workers", 8)); - if (i == Qnil) - i = INT2NUM(0); - return i; -} - -/** - * Sets the number of worker processes that will be used when {Iodine.start} - * is called. - * - * Negative numbers are translated as fractions of the number of CPU cores. - * i.e., -2 == half the number of detected CPU cores. - * - * Zero values promise nothing (iodine will decide what to do with them). - * - * 1 == single process mode, the msater process acts as a worker process. - * - * @param worker_count [FixNum] Number of worker processes - */ -static VALUE iodine_workers_set(VALUE self, VALUE val) { - Check_Type(val, T_FIXNUM); - if (NUM2SSIZET(val) >= (1 << 9)) { - rb_raise(rb_eRangeError, "requsted worker process count is out of range."); - } - rb_ivar_set(self, rb_intern2("@workers", 8), val); - return val; -} - -/** Logs the Iodine startup message */ -static void iodine_print_startup_message(iodine_start_params_s params) { - VALUE iodine_version = rb_const_get(IodineModule, rb_intern("VERSION")); - VALUE ruby_version = rb_const_get(IodineModule, rb_intern("RUBY_VERSION")); - fio_expected_concurrency(¶ms.threads, ¶ms.workers); - FIO_LOG_INFO("Starting up Iodine:\n" - " * Iodine %s\n * Ruby %s\n" - " * facil.io " FIO_VERSION_STRING " (%s)\n" - " * %d Workers X %d Threads per worker.\n" - " * Maximum %zu open files / sockets per worker.\n" - " * Master (root) process: %d.\n", - StringValueCStr(iodine_version), StringValueCStr(ruby_version), - fio_engine(), params.workers, params.threads, fio_capa(), - fio_parent_pid()); - (void)params; -} - -/** - * This will block the calling (main) thread and start the Iodine reactor. - * - * When using cluster mode (2 or more worker processes), it is important that no - * other threads are active. - * - * For many reasons, `fork` should NOT be called while multi-threading, so - * cluster mode must always be initiated from the main thread in a single thread - * environment. - * - * For information about why forking in multi-threaded environments should be - * avoided, see (for example): - * http://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them - * - */ -static VALUE iodine_start(VALUE self) { - if (fio_is_running()) { - rb_raise(rb_eRuntimeError, "Iodine already running!"); - } - IodineCaller.set_GVL(1); - VALUE threads_rb = iodine_threads_get(self); - VALUE workers_rb = iodine_workers_get(self); - iodine_start_params_s params = { - .threads = NUM2SHORT(threads_rb), - .workers = NUM2SHORT(workers_rb), - }; - iodine_print_startup_message(params); - IodineCaller.leaveGVL(iodine_run_outside_GVL, ¶ms); - return self; -} - -/** - * This will stop the iodine server, shutting it down. - * - * If called within a worker process (rather than the root/master process), this - * will cause a hot-restart for the worker. - */ -static VALUE iodine_stop(VALUE self) { - fio_stop(); - return self; -} - -/** - * Returns `true` if this process is the master / root process, `false` - * otherwise. - * - * Note that the master process might be a worker process as well, when running - * in single process mode (see {Iodine.workers}). - */ -static VALUE iodine_master_is(VALUE self) { - return fio_is_master() ? Qtrue : Qfalse; -} - -/** - * Returns `true` if this process is a worker process or if iodine is running in - * a single process mode (the master is also a worker), `false` otherwise. - */ -static VALUE iodine_worker_is(VALUE self) { - return fio_is_worker() ? Qtrue : Qfalse; -} - -/** - * Returns `true` if Iodine is currently running a server - */ -static VALUE iodine_running(VALUE self) { - if (fio_is_running()) { - return Qtrue; - } else { - return Qfalse; - } +static void Init_Iodine(void) { + rb_define_singleton_method(iodine_rb_IODINE, "start", iodine_start, 0); + rb_define_singleton_method(iodine_rb_IODINE, "stop", iodine_stop, 0); + + rb_define_singleton_method(iodine_rb_IODINE, + "running?", + iodine_is_running, + 0); + rb_define_singleton_method(iodine_rb_IODINE, "master?", iodine_is_master, 0); + rb_define_singleton_method(iodine_rb_IODINE, "worker?", iodine_is_worker, 0); + + rb_define_singleton_method(iodine_rb_IODINE, "workers", iodine_workers, 0); + rb_define_singleton_method(iodine_rb_IODINE, + "workers=", + iodine_workers_set, + 1); + + rb_define_singleton_method(iodine_rb_IODINE, "threads", iodine_threads, 0); + rb_define_singleton_method(iodine_rb_IODINE, + "threads=", + iodine_threads_set, + 1); + + rb_define_singleton_method(iodine_rb_IODINE, + "verbosity", + iodine_verbosity, + 0); + rb_define_singleton_method(iodine_rb_IODINE, + "verbosity=", + iodine_verbosity_set, + 1); + + rb_define_module_function(iodine_rb_IODINE, "run", iodine_defer_run_async, 0); + rb_define_module_function(iodine_rb_IODINE, "defer", iodine_defer_run, 0); + + rb_define_module_function(iodine_rb_IODINE, + "run_after", + iodine_defer_run_after, + -1); + rb_define_module_function(iodine_rb_IODINE, "on_state", iodine_on_state, 1); + + rb_define_singleton_method(iodine_rb_IODINE, "listen", iodine_listen_rb, -1); } /* ***************************************************************************** -CLI parser (Ruby's OptParser is more limiting than I knew...) +Initialize Extension ***************************************************************************** */ -/** - * Parses the CLI argnumnents, returning the Rack filename (if provided). - * - * Unknown arguments are ignored. - * - * @params [String] desc a String containg the iodine server's description. - */ -static VALUE iodine_cli_parse(VALUE self) { - (void)self; - VALUE ARGV = rb_get_argv(); - VALUE ret = Qtrue; - VALUE defaults = iodine_default_args; - VALUE iodine_version = rb_const_get(IodineModule, rb_intern("VERSION")); - char desc[1024]; - if (!defaults || !ARGV || TYPE(ARGV) != T_ARRAY || TYPE(defaults) != T_HASH || - TYPE(iodine_version) != T_STRING || RSTRING_LEN(iodine_version) > 512) { - FIO_LOG_ERROR("CLI parsing initialization error " - "ARGV=%p, Array?(%d), defaults == %p (%d)", - (void *)ARGV, (int)(TYPE(ARGV) == T_ARRAY), (void *)defaults, - (int)(TYPE(defaults) == T_HASH)); - return Qnil; - } - /* Copy the Ruby ARGV to a C valid ARGV */ - int argc = (int)RARRAY_LEN(ARGV) + 1; - if (argc <= 1) { - FIO_LOG_DEBUG("CLI: No arguments to parse...\n"); - return Qnil; - } else { - FIO_LOG_DEBUG("Iodine CLI parsing %d arguments", argc); - } - char **argv = calloc(argc, sizeof(*argv)); - FIO_ASSERT_ALLOC(argv); - argv[0] = (char *)"iodine"; - for (int i = 1; i < argc; ++i) { - VALUE tmp = rb_ary_entry(ARGV, (long)(i - 1)); - if (TYPE(tmp) != T_STRING) { - FIO_LOG_ERROR("ARGV Array contains a non-String object."); - ret = Qnil; - goto finish; - } - fio_str_info_s s = IODINE_RSTRINFO(tmp); - argv[i] = malloc(s.len + 1); - FIO_ASSERT_ALLOC(argv[i]); - memcpy(argv[i], s.data, s.len); - argv[i][s.len] = 0; - } - /* Levarage the facil.io CLI library */ - memcpy(desc, "Iodine's HTTP/WebSocket server version ", 39); - memcpy(desc + 39, StringValueCStr(iodine_version), - RSTRING_LEN(iodine_version)); - memcpy(desc + 39 + RSTRING_LEN(iodine_version), - "\r\n\r\nUse:\r\n iodine \r\n\r\n" - "Both and are optional. i.e.,:\r\n" - " iodine -p 0 -b /tmp/my_unix_sock\r\n" - " iodine -p 8080 path/to/app/conf.ru\r\n" - " iodine -p 8080 -w 4 -t 16\r\n" - " iodine -w -1 -t 4 -r redis://usr:pass@localhost:6379/", - 263); - desc[39 + 263 + RSTRING_LEN(iodine_version)] = 0; - fio_cli_start( - argc, (const char **)argv, 0, -1, desc, - FIO_CLI_PRINT_HEADER("Address Binding:"), - "-bind -b -address address to listen to. defaults to any available.", - FIO_CLI_INT("-port -p port number to listen to. defaults port 3000"), - FIO_CLI_PRINT("\t\t\x1B[4mNote\x1B[0m: to bind to a Unix socket, set " - "\x1B[1mport\x1B[0m to 0."), - FIO_CLI_PRINT_HEADER("Concurrency:"), - FIO_CLI_INT("-threads -t number of threads per process."), - FIO_CLI_INT("-workers -w number of processes to use."), - FIO_CLI_PRINT("Negative concurrency values " - "map to fractions of available CPU cores."), - FIO_CLI_PRINT_HEADER("HTTP Settings:"), - FIO_CLI_STRING("-public -www public folder, for static file service."), - FIO_CLI_INT("-keep-alive -k -tout HTTP keep-alive timeout in seconds " - "(0..255). Default: 40s"), - FIO_CLI_BOOL("-log -v HTTP request logging."), - FIO_CLI_INT( - "-max-body -maxbd HTTP upload limit in Mega-Bytes. Default: 50Mb"), - FIO_CLI_INT("-max-header -maxhd header limit per HTTP request in Kb. " - "Default: 32Kb."), - FIO_CLI_PRINT_HEADER("WebSocket Settings:"), - FIO_CLI_INT("-max-msg -maxms incoming WebSocket message limit in Kb. " - "Default: 250Kb"), - FIO_CLI_INT("-ping websocket ping interval (1..255). Default: 40s"), - FIO_CLI_PRINT_HEADER("SSL/TLS:"), - FIO_CLI_BOOL("-tls enable SSL/TLS using a self-signed certificate."), - FIO_CLI_STRING( - "-tls-cert -cert the SSL/TLS public certificate file name."), - FIO_CLI_STRING("-tls-key -key the SSL/TLS private key file name."), - FIO_CLI_STRING( - "-tls-pass -tls-password the password (if any) protecting the " - "private key file."), - FIO_CLI_PRINT("\t\t\x1B[1m-tls-password\x1B[0m is deprecated, use " - "\x1B[1m-tls-pass\x1B[0m"), - FIO_CLI_PRINT_HEADER("Connecting Iodine to Redis:"), - FIO_CLI_STRING( - "-redis -r an optional Redis URL server address. Default: none."), - FIO_CLI_INT( - "-redis-ping -rp websocket ping interval (0..255). Default: 300s"), - FIO_CLI_PRINT_HEADER("Misc:"), - FIO_CLI_STRING("-config -C configuration file to be loaded."), - FIO_CLI_STRING("-pid -pidfile name for the pid file to be created."), - FIO_CLI_INT("-verbosity -V 0..5 server verbosity level. Default: 4"), - FIO_CLI_BOOL( - "-warmup --preload warm up the application. CAREFUL! with workers.")); - - /* copy values from CLI library to iodine */ - if (fio_cli_get("-V")) { - int level = fio_cli_get_i("-V"); - if (level >= 0 && level < 100) - FIO_LOG_LEVEL = level; - } - if (!fio_cli_get("-w") && getenv("WEB_CONCURRENCY")) { - fio_cli_set("-w", getenv("WEB_CONCURRENCY")); - } - if (!fio_cli_get("-w") && getenv("WORKERS")) { - fio_cli_set("-w", getenv("WORKERS")); - } - if (fio_cli_get("-w")) { - iodine_workers_set(IodineModule, INT2NUM(fio_cli_get_i("-w"))); - } - if (!fio_cli_get("-t") && getenv("THREADS")) { - fio_cli_set("-t", getenv("THREADS")); - } - if (fio_cli_get("-t")) { - iodine_threads_set(IodineModule, INT2NUM(fio_cli_get_i("-t"))); - } - if (fio_cli_get_bool("-v")) { - rb_hash_aset(defaults, log_sym, Qtrue); - } - if (fio_cli_get_bool("-warmup")) { - rb_hash_aset(defaults, ID2SYM(rb_intern("warmup_")), Qtrue); - } - // if (!fio_cli_get("-b") && getenv("ADDRESS")) { - // fio_cli_set("-b", getenv("ADDRESS")); - // } - if (fio_cli_get("-b")) { - if (fio_cli_get("-b")[0] == '/' || - (fio_cli_get("-b")[0] == '.' && fio_cli_get("-b")[1] == '/')) { - if (fio_cli_get("-p") && - (fio_cli_get("-p")[0] != '0' || fio_cli_get("-p")[1])) { - FIO_LOG_WARNING( - "Detected a Unix socket binding (-b) conflicting with port.\n" - " Port settings (-p %s) are ignored", - fio_cli_get("-p")); - } - fio_cli_set("-p", "0"); - } else { - // if (!fio_cli_get("-p") && getenv("PORT")) { - // fio_cli_set("-p", getenv("PORT")); - // } - } - rb_hash_aset(defaults, address_sym, rb_str_new_cstr(fio_cli_get("-b"))); - } - if (fio_cli_get("-p")) { - rb_hash_aset(defaults, port_sym, rb_str_new_cstr(fio_cli_get("-p"))); - } - if (fio_cli_get("-www")) { - rb_hash_aset(defaults, public_sym, rb_str_new_cstr(fio_cli_get("-www"))); - } - if (!fio_cli_get("-redis") && getenv("IODINE_REDIS_URL")) { - fio_cli_set("-redis", getenv("IODINE_REDIS_URL")); - } - if (fio_cli_get("-redis")) { - rb_hash_aset(defaults, ID2SYM(rb_intern("redis_")), - rb_str_new_cstr(fio_cli_get("-redis"))); - } - if (fio_cli_get("-k")) { - rb_hash_aset(defaults, timeout_sym, INT2NUM(fio_cli_get_i("-k"))); - } - if (fio_cli_get("-ping")) { - rb_hash_aset(defaults, ping_sym, INT2NUM(fio_cli_get_i("-ping"))); - } - if (fio_cli_get("-redis-ping")) { - rb_hash_aset(defaults, ID2SYM(rb_intern("redis_ping_")), - INT2NUM(fio_cli_get_i("-redis-ping"))); - } - if (fio_cli_get("-max-body")) { - rb_hash_aset(defaults, max_body_sym, - INT2NUM((fio_cli_get_i("-max-body") /* * 1024 * 1024 */))); - } - if (fio_cli_get("-maxms")) { - rb_hash_aset(defaults, max_msg_sym, - INT2NUM((fio_cli_get_i("-maxms") /* * 1024 */))); - } - if (fio_cli_get("-maxhd")) { - rb_hash_aset(defaults, max_headers_sym, - INT2NUM((fio_cli_get_i("-maxhd") /* * 1024 */))); - } -#ifndef __MINGW32__ - if (fio_cli_get_bool("-tls") || fio_cli_get("-key") || fio_cli_get("-cert")) { - VALUE rbtls = IodineCaller.call(IodineTLSClass, rb_intern2("new", 3)); - if (rbtls == Qnil) { - FIO_LOG_FATAL("Iodine internal error, Ruby TLS object is nil."); - exit(-1); - } - fio_tls_s *tls = iodine_tls2c(rbtls); - if (!tls) { - FIO_LOG_FATAL("Iodine internal error, TLS object NULL."); - exit(-1); - } - if (fio_cli_get("-tls-key") && fio_cli_get("-tls-cert")) { - fio_tls_cert_add(tls, NULL, fio_cli_get("-tls-cert"), - fio_cli_get("-tls-key"), fio_cli_get("-tls-pass")); - } else { - if (!fio_cli_get_bool("-tls")) - FIO_LOG_ERROR("TLS support requires both key and certificate." - "\r\n\t\tfalling back on a self signed certificate."); - char name[1024]; - fio_local_addr(name, 1024); - fio_tls_cert_add(tls, name, NULL, NULL, NULL); - } - rb_hash_aset(defaults, tls_sym, rbtls); - } -#endif - if (fio_cli_unnamed_count()) { - rb_hash_aset(defaults, ID2SYM(rb_intern("filename_")), - rb_str_new_cstr(fio_cli_unnamed(0))); - } - if (fio_cli_get("-pid")) { - VALUE pid_filename = rb_str_new_cstr(fio_cli_get("-pid")); - rb_hash_aset(defaults, ID2SYM(rb_intern("pid_")), pid_filename); - rb_hash_aset(defaults, ID2SYM(rb_intern("pid")), pid_filename); - } - if (fio_cli_get("-config")) { - VALUE conf_filename = rb_str_new_cstr(fio_cli_get("-config")); - rb_hash_aset(defaults, ID2SYM(rb_intern("conf_")), conf_filename); - } - - /* create `filename` String, cleanup and return */ - fio_cli_end(); -finish: - for (int i = 1; i < argc; ++i) { - free(argv[i]); - } - free(argv); - return ret; -} - -/* ***************************************************************************** -Argument support for `connect` / `listen` -***************************************************************************** */ - -static int for_each_header_value(VALUE key, VALUE val, VALUE h_) { - FIOBJ h = h_; - if (RB_TYPE_P(key, T_SYMBOL)) - key = rb_sym2str(key); - if (!RB_TYPE_P(key, T_STRING)) { - FIO_LOG_WARNING("invalid key type in header hash, ignored."); - return ST_CONTINUE; - } - if (RB_TYPE_P(val, T_SYMBOL)) - val = rb_sym2str(val); - if (RB_TYPE_P(val, T_STRING)) { - FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key)); - fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val))); - fiobj_free(k); - } else if (RB_TYPE_P(val, T_ARRAY)) { - FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key)); - size_t len = rb_array_len(val); - FIOBJ v = fiobj_ary_new2(len); - fiobj_hash_set(h, k, v); - fiobj_free(k); - for (size_t i = 0; i < len; ++i) { - VALUE tmp = rb_ary_entry(val, i); - if (RB_TYPE_P(tmp, T_SYMBOL)) - tmp = rb_sym2str(tmp); - if (RB_TYPE_P(tmp, T_STRING)) - fiobj_ary_push(v, fiobj_str_new(RSTRING_PTR(tmp), RSTRING_LEN(tmp))); - } - } else { - FIO_LOG_WARNING("invalid header value type, ignored."); - } - return ST_CONTINUE; -} - -static int for_each_cookie(VALUE key, VALUE val, VALUE h_) { - FIOBJ h = h_; - if (RB_TYPE_P(key, T_SYMBOL)) - key = rb_sym2str(key); - if (!RB_TYPE_P(key, T_STRING)) { - FIO_LOG_WARNING("invalid key type in cookie hash, ignored."); - return ST_CONTINUE; - } - if (RB_TYPE_P(val, T_SYMBOL)) - val = rb_sym2str(val); - if (RB_TYPE_P(val, T_STRING)) { - FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key)); - fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val))); - fiobj_free(k); - } else { - FIO_LOG_WARNING("invalid cookie value type, ignored."); - } - return ST_CONTINUE; -} - -/* cleans up any resources used by the argument list processing */ -FIO_FUNC void iodine_connect_args_cleanup(iodine_connection_args_s *s) { - if (!s) - return; - fiobj_free(s->cookies); - fiobj_free(s->headers); - if (s->port.capa) - fio_free(s->port.data); - if (s->address.capa) - fio_free(s->address.data); -#ifndef __MINGW32__ - if (s->tls) - fio_tls_destroy(s->tls); -#endif -} - -/* -Accepts: - - func(settings) - -Supported Settigs: -- `:url` -- `:handler` (deprecated: `app`) -- `:service` (raw / ws / wss / http / https ) -- `:address` -- `:port` -- `:path` (HTTP/WebSocket client) -- `:method` (HTTP client) -- `:headers` (HTTP/WebSocket client) -- `:cookies` (HTTP/WebSocket client) -- `:body` (HTTP client) -- `:tls` -- `:log` (HTTP only) -- `:public` (public folder, HTTP server only) -- `:timeout` (HTTP only) -- `:ping` (`:raw` clients and WebSockets only) -- `:max_headers` (HTTP only) -- `:max_body` (HTTP only) -- `:max_msg` (WebSockets only) - -*/ -FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) { - Check_Type(s, T_HASH); - iodine_connection_args_s r = {.ping = 0}; /* set all to 0 */ - /* Collect argument values */ - VALUE address = rb_hash_aref(s, address_sym); - VALUE app = rb_hash_aref(s, app_sym); - VALUE body = rb_hash_aref(s, body_sym); - VALUE cookies = rb_hash_aref(s, cookies_sym); - VALUE handler = rb_hash_aref(s, handler_sym); - VALUE headers = rb_hash_aref(s, headers_sym); - VALUE log = rb_hash_aref(s, log_sym); - VALUE max_body = rb_hash_aref(s, max_body_sym); - VALUE max_clients = rb_hash_aref(s, max_clients_sym); - VALUE max_headers = rb_hash_aref(s, max_headers_sym); - VALUE max_msg = rb_hash_aref(s, max_msg_sym); - VALUE method = rb_hash_aref(s, method_sym); - VALUE path = rb_hash_aref(s, path_sym); - VALUE ping = rb_hash_aref(s, ping_sym); - VALUE port = rb_hash_aref(s, port_sym); - VALUE r_public = rb_hash_aref(s, public_sym); - VALUE service = rb_hash_aref(s, service_sym); - VALUE timeout = rb_hash_aref(s, timeout_sym); -#ifndef __MINGW32__ - VALUE tls = rb_hash_aref(s, tls_sym); -#endif - VALUE r_url = rb_hash_aref(s, url_sym); - fio_str_info_s service_str = {.data = NULL}; - - /* Complete using default values */ - if (address == Qnil) - address = rb_hash_aref(iodine_default_args, address_sym); - if (app == Qnil) - app = rb_hash_aref(iodine_default_args, app_sym); - if (cookies == Qnil) - cookies = rb_hash_aref(iodine_default_args, cookies_sym); - if (handler == Qnil) - handler = rb_hash_aref(iodine_default_args, handler_sym); - if (headers == Qnil) - headers = rb_hash_aref(iodine_default_args, headers_sym); - if (log == Qnil) - log = rb_hash_aref(iodine_default_args, log_sym); - if (max_body == Qnil) - max_body = rb_hash_aref(iodine_default_args, max_body_sym); - if (max_clients == Qnil) - max_clients = rb_hash_aref(iodine_default_args, max_clients_sym); - if (max_headers == Qnil) - max_headers = rb_hash_aref(iodine_default_args, max_headers_sym); - if (max_msg == Qnil) - max_msg = rb_hash_aref(iodine_default_args, max_msg_sym); - if (method == Qnil) - method = rb_hash_aref(iodine_default_args, method_sym); - if (path == Qnil) - path = rb_hash_aref(iodine_default_args, path_sym); - if (ping == Qnil) - ping = rb_hash_aref(iodine_default_args, ping_sym); - if (port == Qnil) - port = rb_hash_aref(iodine_default_args, port_sym); - if (r_public == Qnil) { - r_public = rb_hash_aref(iodine_default_args, public_sym); - } - // if (service == Qnil) // not supported by default settings... - // service = rb_hash_aref(iodine_default_args, service_sym); - if (timeout == Qnil) - timeout = rb_hash_aref(iodine_default_args, timeout_sym); -#ifndef __MINGW32__ - if (tls == Qnil) - tls = rb_hash_aref(iodine_default_args, tls_sym); -#endif - - /* TODO: deprecation */ - if (handler == Qnil) { - handler = rb_hash_aref(s, app_sym); - if (handler != Qnil) - FIO_LOG_WARNING(":app is deprecated in Iodine.listen and Iodine.connect. " - "Use :handler"); - } - - /* specific for HTTP */ - if (is_srv && handler == Qnil && rb_block_given_p()) { - handler = rb_block_proc(); - } - - /* Raise exceptions on errors (last chance) */ - if (handler == Qnil) { - rb_raise(rb_eArgError, "a :handler is required."); - } - - /* Set existing values */ - if (handler != Qnil) { - r.handler = handler; - } - if (address != Qnil && RB_TYPE_P(address, T_STRING)) { - r.address = IODINE_RSTRINFO(address); - } - if (body != Qnil && RB_TYPE_P(body, T_STRING)) { - r.body = IODINE_RSTRINFO(body); - } - if (cookies != Qnil && RB_TYPE_P(cookies, T_HASH)) { - r.cookies = fiobj_hash_new2(rb_hash_size(cookies)); - rb_hash_foreach(cookies, for_each_cookie, r.cookies); - } - if (headers != Qnil && RB_TYPE_P(headers, T_HASH)) { - r.headers = fiobj_hash_new2(rb_hash_size(headers)); - rb_hash_foreach(headers, for_each_header_value, r.headers); - } - if (log != Qnil && log != Qfalse) { - r.log = 1; - } - if (max_body != Qnil && RB_TYPE_P(max_body, T_FIXNUM)) { - r.max_body = FIX2ULONG(max_body) * 1024 * 1024; - } - if (max_clients != Qnil && RB_TYPE_P(max_clients, T_FIXNUM)) { - r.max_clients = FIX2ULONG(max_clients); - } - if (max_headers != Qnil && RB_TYPE_P(max_headers, T_FIXNUM)) { - r.max_headers = FIX2ULONG(max_headers) * 1024; - } - if (max_msg != Qnil && RB_TYPE_P(max_msg, T_FIXNUM)) { - r.max_msg = FIX2ULONG(max_msg) * 1024; - } - if (method != Qnil && RB_TYPE_P(method, T_STRING)) { - r.method = IODINE_RSTRINFO(method); - } - if (path != Qnil && RB_TYPE_P(path, T_STRING)) { - r.path = IODINE_RSTRINFO(path); - } - if (ping != Qnil && RB_TYPE_P(ping, T_FIXNUM)) { - if (FIX2ULONG(ping) > 255) - FIO_LOG_WARNING(":ping value over 255 will be silently ignored."); - else - r.ping = FIX2ULONG(ping); - } - if (port != Qnil) { - if (RB_TYPE_P(port, T_STRING)) { - char *tmp = RSTRING_PTR(port); - if (fio_atol(&tmp)) - r.port = IODINE_RSTRINFO(port); - } else if (RB_TYPE_P(port, T_FIXNUM) && FIX2UINT(port)) { - if (FIX2UINT(port) >= 65536) { - FIO_LOG_WARNING("Port number %u is too high, quietly ignored.", - FIX2UINT(port)); - } else { - r.port = (fio_str_info_s){.data = fio_malloc(16), .len = 0, .capa = 1}; - r.port.len = fio_ltoa(r.port.data, FIX2INT(port), 10); - r.port.data[r.port.len] = 0; - } - } - } - - if (r_public != Qnil && RB_TYPE_P(r_public, T_STRING)) { - r.public = IODINE_RSTRINFO(r_public); - } - if (service != Qnil && RB_TYPE_P(service, T_STRING)) { - service_str = IODINE_RSTRINFO(service); - } else if (service != Qnil && RB_TYPE_P(service, T_SYMBOL)) { - service = rb_sym2str(service); - service_str = IODINE_RSTRINFO(service); - } - if (timeout != Qnil && RB_TYPE_P(ping, T_FIXNUM)) { - if (FIX2ULONG(timeout) > 255) - FIO_LOG_WARNING(":timeout value over 255 will be silently ignored."); - else - r.timeout = FIX2ULONG(timeout); - } -#ifndef __MINGW32__ - if (tls != Qnil) { - r.tls = iodine_tls2c(tls); - if (r.tls) - fio_tls_dup(r.tls); - } -#endif - /* URL parsing */ - if (r_url != Qnil && RB_TYPE_P(r_url, T_STRING)) { - r.url = IODINE_RSTRINFO(r_url); - fio_url_s u = fio_url_parse(r.url.data, r.url.len); - /* set service string */ - if (u.scheme.data) { - service_str = u.scheme; - } - /* copy port number */ - if (u.port.data) { - char *tmp = u.port.data; - if (fio_atol(&tmp) == 0) { - if (r.port.capa) - fio_free(r.port.data); - r.port = (fio_str_info_s){.data = NULL}; - } else { - if (u.port.len > 5) - FIO_LOG_WARNING("Port number error (%.*s too long to be valid).", - (int)u.port.len, u.port.data); - if (r.port.capa && u.port.len >= 16) { - fio_free(r.port.data); - r.port = (fio_str_info_s){.data = NULL}; - } - if (!r.port.capa) - r.port = (fio_str_info_s){ - .data = fio_malloc(u.port.len + 1), .len = u.port.len, .capa = 1}; - memcpy(r.port.data, u.port.data, u.port.len); - r.port.len = u.port.len; - r.port.data[r.port.len] = 0; - } - } else { - if (r.port.capa) - fio_free(r.port.data); - r.port = (fio_str_info_s){.data = NULL}; - } - /* copy host / address */ - if (u.host.data) { - r.address = (fio_str_info_s){ - .data = fio_malloc(u.host.len + 1), .len = u.host.len, .capa = 1}; - memcpy(r.address.data, u.host.data, u.host.len); - r.address.len = u.host.len; - r.address.data[r.address.len] = 0; - } else { - if (r.address.capa) - fio_free(r.address.data); - r.address = (fio_str_info_s){.data = NULL}; - } - /* set path */ - if (u.path.data) { - /* support possible Unix address as "raw://:0/my/sock.sock" */ - if (r.address.data || r.port.data) - r.path = u.path; - else - r.address = u.path; - } - } - /* test/set service type */ - r.service = IODINE_SERVICE_RAW; - if (service_str.data) { -#ifdef __MINGW32__ - switch (service_str.data[0]) { - case 't': /* overflow */ - /* tcp or tls */ - if (service_str.data[1] == 'l') { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - } - /* overflow */ - case 'u': /* overflow */ - /* unix */ - case 'r': - /* raw */ - r.service = IODINE_SERVICE_RAW; - break; - case 'h': - /* http(s) */ - r.service = IODINE_SERVICE_HTTP; - if (service_str.len == 5) { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - } - case 'w': - /* ws(s) */ - r.service = IODINE_SERVICE_WS; - if (service_str.len == 3) { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - } - break; - } -#else - switch (service_str.data[0]) { - case 't': /* overflow */ - /* tcp or tls */ - if (service_str.data[1] == 'l' && !r.tls) { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - r.tls = fio_tls_new(local, NULL, NULL, NULL); - } - /* overflow */ - case 'u': /* overflow */ - /* unix */ - case 'r': - /* raw */ - r.service = IODINE_SERVICE_RAW; - break; - case 'h': - /* http(s) */ - r.service = IODINE_SERVICE_HTTP; - if (service_str.len == 5 && !r.tls) { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - r.tls = fio_tls_new(local, NULL, NULL, NULL); - } - case 'w': - /* ws(s) */ - r.service = IODINE_SERVICE_WS; - if (service_str.len == 3 && !r.tls) { - char *local = NULL; - char buf[1024]; - buf[1023] = 0; - if (is_srv) { - local = buf; - if (fio_local_addr(buf, 1023) >= 1022) - local = NULL; - } - r.tls = fio_tls_new(local, NULL, NULL, NULL); - } - break; - } -#endif - } - return r; -} - -/* ***************************************************************************** -Listen function routing -***************************************************************************** */ - -// clang-format off -/* -{Iodine.listen} can be used to listen to any incoming connections, including HTTP and raw (tcp/ip and unix sockets) connections. - - Iodine.listen(settings) - -Supported Settigs: - -| | | -|---|---| -| `:url` | URL indicating service type, host name and port. Path will be parsed as a Unix socket. | -| `:handler` | (deprecated: `:app`) see details below. | -| `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. | -| `:log` | (HTTP only) request logging. For global verbosity see {Iodine.verbosity} | -| `:max_body` | (HTTP only) maximum upload size allowed per request before disconnection (in Mb). | -| `:max_headers` | (HTTP only) maximum total header length allowed per request (in Kb). | -| `:max_msg` | (WebSockets only) maximum message size pre message (in Kb). | -| `:ping` | (`:raw` clients and WebSockets only) ping interval (in seconds). Up to 255 seconds. | -| `:port` | port number to listen to either a String or Number) | -| `:public` | (HTTP server only) public folder for static file service. | -| `:service` | (`:raw` / `:tls` / `:ws` / `:wss` / `:http` / `:https` ) a supported service this socket will listen to. | -| `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. | -| `:tls` | an {Iodine::TLS} context object for encrypted connections. | - -Some connection settings are only valid when listening to HTTP / WebSocket connections. - -If `:url` is provided, it will overwrite the `:address` and `:port` settings (if provided). - -For HTTP connections, the `:handler` **must** be a valid Rack application object (answers `.call(env)`). - -Here's an example for an HTTP hello world application: - - require 'iodine' - # a handler can be a block - Iodine.listen(service: :http, port: "3000") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] } - # start the service - Iodine.threads = 1 - Iodine.start - - -Here's another example, using a Unix Socket instead of a TCP/IP socket for an HTTP hello world application. - -This example shows how the `:url` option can be used, but the `:address` settings could have been used for the same effect (with `port: 0`). - - require 'iodine' - # note that unix sockets in URL form use an absolute path. - Iodine.listen(url: "http://:0/tmp/sock.sock") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] } - # start the service - Iodine.threads = 1 - Iodine.start - - -For raw connections, the `:handler` object should be an object that answer `.call` and returns a valid callback object that supports the following callbacks (see also {Iodine::Connection}): - -| | | -|---|---| -| `on_open(client)` | called after a connection was established | -| `on_message(client,data)` | called when incoming data is available. Data may be fragmented. | -| `on_drained(client)` | called after pending `client.write` events have been processed (see {Iodine::Connection#pending}). | -| `ping(client)` | called whenever a timeout has occured (see {Iodine::Connection#timeout=}). | -| `on_shutdown(client)` | called if the server is shutting down. This is called before the connection is closed. | -| `on_close(client)` | called when the connection with the client was closed. | - -The `client` argument passed to the `:handler` callbacks is an {Iodine::Connection} instance that represents the connection / the client. - -Here's an example for a telnet based chat-room example: - - require 'iodine' - # define the protocol for our service - module ChatHandler - def self.on_open(client) - # Set a connection timeout - client.timeout = 10 - # subscribe to the chat channel. - client.subscribe :chat - # Write a welcome message - client.publish :chat, "new member entered the chat\r\n" - end - # this is called for incoming data - note data might be fragmented. - def self.on_message(client, data) - # publish the data we received - client.publish :chat, data - # close the connection when the time comes - client.close if data =~ /^bye[\n\r]/ - end - # called whenever timeout occurs. - def self.ping(client) - client.write "System: quite, isn't it...?\r\n" - end - # called if the connection is still open and the server is shutting down. - def self.on_shutdown(client) - # write the data we received - client.write "Chat server going away. Try again later.\r\n" - end - # returns the callback object (self). - def self.call - self - end - end - # we use can both the `handler` keyword or a block, anything that answers #call. - Iodine.listen(service: :raw, port: "3000", handler: ChatHandler) - # we can listen to more than a single socket at a time. - Iodine.listen(url: "raw://:3030", handler: ChatHandler) - # start the service - Iodine.threads = 1 - Iodine.start - - - -Returns the handler object used. -*/ -static VALUE iodine_listen(VALUE self, VALUE args) { - // clang-format on - iodine_connection_args_s s = iodine_connect_args(args, 1); - intptr_t uuid = -1; - switch (s.service) { - case IODINE_SERVICE_RAW: - uuid = iodine_tcp_listen(s); - break; - case IODINE_SERVICE_HTTP: /* overflow */ - case IODINE_SERVICE_WS: - uuid = iodine_http_listen(s); - break; - } - iodine_connect_args_cleanup(&s); - if (uuid == -1) - rb_raise(rb_eRuntimeError, "Couldn't open listening socket."); - return s.handler; - (void)self; -} - -/* ***************************************************************************** -Connect function routing -***************************************************************************** */ - -// clang-format off -/* - -The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets. - - Iodine.connect(settings) - -Supported Settigs: - - -| | | -|---|---| -| `:url` | URL indicating service type, host name, port and optional path. | -| `:handler` | see details below. | -| `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. | -| `:body` | (HTTP client) the body to be sent. | -| `:cookies` | (HTTP/WebSocket client) cookie data. | -| `:headers` | (HTTP/WebSocket client) custom headers. | -| `:log` | (HTTP only) - logging the requests. | -| `:max_body` | (HTTP only) - limits HTTP body in the response, see {listen}. | -| `:max_headers` | (HTTP only) - limits the header length in the response, see {listen}. | -| `:max_msg` | (WebSockets only) maximum incoming message size pre message (in Kb). | -| `:method` | (HTTP client) a String such as "GET" or "POST". | -| `:path` |HTTP/WebSocket client) the HTTP path to be used. | -| `:ping` | ping interval (in seconds). Up to 255 seconds. | -| `:port` | port number to listen to either a String or Number) | -| `:public` | (public folder, HTTP server only) | -| `:service` | (`:raw` / `:tls` / `:ws` / `:wss` ) | -| `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. | -| `:tls` | an {Iodine::TLS} context object for encrypted connections. | - -Some connection settings are only valid for HTTP / WebSocket connections. - -If `:url` is provided, it will overwrite the `:address`, `:port` and `:path` settings (if provided). - -Unlike {Iodine.listen}, a block can't be used and a `:handler` object **must** be provided. - -If the connection fails, only the `on_close` callback will be called (with a `nil` client). - -Here's an example TCP/IP client that sends a simple HTTP GET request: - - # use a secure connection? - USE_TLS = false - - # remote server details - $port = USE_TLS ? 443 : 80 - $address = "google.com" - - - # require iodine - require 'iodine' - - # Iodine runtime settings - Iodine.threads = 1 - Iodine.workers = 1 - Iodine.verbosity = 3 # warnings only - - - # a client callback handler - module Client - - def self.on_open(connection) - # Set a connection timeout - connection.timeout = 10 - # subscribe to the chat channel. - puts "* Sending request..." - connection.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n" - end - - def self.on_message(connection, data) - # publish the data we received - STDOUT.write data - # close the connection after a second... we're not really parsing anything, so it's a guess. - Iodine.run_after(1000) { connection.close } - end - - def self.on_close(connection) - # stop iodine - Iodine.stop - puts "Done." - end - - # returns the callback object (self). - def self.call - self - end - end - - - - if(USE_TLS) - tls = Iodine::TLS.new - # ALPN blocks should return a valid calback object - tls.on_protocol("http/1.1") { Client } - end - - Iodine.connect(address: $address, port: $port, handler: Client, tls: tls) - - # start the iodine reactor - Iodine.start - -Iodine also supports WebSocket client connections, using either the `url` property or the `ws` and `wss` service names. - -The following example establishes a secure (TLS) connects to the WebSocket echo testing server at `wss://echo.websocket.org`: - - # require iodine - require 'iodine' - - # The client class - class EchoClient - - def on_open(connection) - @messages = [ "Hello World!", - "I'm alive and sending messages", - "I also receive messages", - "now that we all know this...", - "I can stop.", - "Goodbye." ] - send_one_message(connection) - end - - def on_message(connection, message) - puts "Received: #{message}" - send_one_message(connection) - end - - def on_close(connection) - # in this example, we stop iodine once the client is closed - puts "* Client closed." - Iodine.stop - end - - # We use this method to pop messages from the queue and send them - # - # When the queue is empty, we disconnect the client. - def send_one_message(connection) - msg = @messages.shift - if(msg) - connection.write msg - else - connection.close - end - end - end - - Iodine.threads = 1 - Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40 - Iodine.start - -**Note**: the `on_close` callback is always called, even if a connection couldn't be established. - -Returns the handler object used. -*/ -static VALUE iodine_connect(VALUE self, VALUE args) { - // clang-format on - iodine_connection_args_s s = iodine_connect_args(args, 0); - intptr_t uuid = -1; - switch (s.service) { - case IODINE_SERVICE_RAW: - uuid = iodine_tcp_connect(s); - break; - case IODINE_SERVICE_HTTP: - iodine_connect_args_cleanup(&s); - rb_raise(rb_eRuntimeError, "HTTP client connections aren't supported yet."); - return Qnil; - break; - case IODINE_SERVICE_WS: - uuid = iodine_ws_connect(s); - break; - } - iodine_connect_args_cleanup(&s); - if (uuid == -1) - rb_raise(rb_eRuntimeError, "Couldn't open client socket."); - return self; -} - -/* ***************************************************************************** -Ruby loads the library and invokes the Init_ function... - -Here we connect all the C code to the Ruby interface, completing the bridge -between Lib-Server and Ruby. -***************************************************************************** */ -void Init_iodine_ext(void) { - /* common Symbol objects in use by Iodine */ -#define IODINE_MAKE_SYM(name) \ - do { \ - name##_sym = rb_id2sym(rb_intern(#name)); \ - rb_global_variable(&name##_sym); \ - } while (0) - IODINE_MAKE_SYM(address); - IODINE_MAKE_SYM(app); - IODINE_MAKE_SYM(body); - IODINE_MAKE_SYM(cookies); - IODINE_MAKE_SYM(handler); - IODINE_MAKE_SYM(headers); - IODINE_MAKE_SYM(log); - IODINE_MAKE_SYM(max_body); - IODINE_MAKE_SYM(max_clients); - IODINE_MAKE_SYM(max_headers); - IODINE_MAKE_SYM(max_msg); - IODINE_MAKE_SYM(method); - IODINE_MAKE_SYM(path); - IODINE_MAKE_SYM(ping); - IODINE_MAKE_SYM(port); - IODINE_MAKE_SYM(public); - IODINE_MAKE_SYM(service); - IODINE_MAKE_SYM(timeout); - IODINE_MAKE_SYM(tls); - IODINE_MAKE_SYM(url); - - // load any environment specific patches - patch_env(); - - // force the GVL state for the main thread - IodineCaller.set_GVL(1); - - // Create the Iodine module (namespace) - IodineModule = rb_define_module("Iodine"); - IodineBaseModule = rb_define_module_under(IodineModule, "Base"); - VALUE IodineCLIModule = rb_define_module_under(IodineBaseModule, "CLI"); - iodine_call_id = rb_intern2("call", 4); - iodine_to_s_id = rb_intern("to_s"); - - // register core methods - rb_define_module_function(IodineModule, "threads", iodine_threads_get, 0); - rb_define_module_function(IodineModule, "threads=", iodine_threads_set, 1); - rb_define_module_function(IodineModule, "verbosity", iodine_logging_get, 0); - rb_define_module_function(IodineModule, "verbosity=", iodine_logging_set, 1); - rb_define_module_function(IodineModule, "workers", iodine_workers_get, 0); - rb_define_module_function(IodineModule, "workers=", iodine_workers_set, 1); - rb_define_module_function(IodineModule, "start", iodine_start, 0); - rb_define_module_function(IodineModule, "stop", iodine_stop, 0); - rb_define_module_function(IodineModule, "on_idle", iodine_sched_on_idle, 0); - rb_define_module_function(IodineModule, "master?", iodine_master_is, 0); - rb_define_module_function(IodineModule, "worker?", iodine_worker_is, 0); - rb_define_module_function(IodineModule, "running?", iodine_running, 0); - rb_define_module_function(IodineModule, "listen", iodine_listen, 1); - rb_define_module_function(IodineModule, "connect", iodine_connect, 1); - - // register CLI methods - rb_define_module_function(IodineCLIModule, "parse", iodine_cli_parse, 0); - - /** Default connection settings for {listen} and {connect}. */ - iodine_default_args = rb_hash_new(); - /** Default connection settings for {listen} and {connect}. */ - rb_const_set(IodineModule, rb_intern("DEFAULT_SETTINGS"), - iodine_default_args); - - /** Depracated, use {Iodine::DEFAULT_SETTINGS}. */ - rb_const_set(IodineModule, rb_intern("DEFAULT_HTTP_ARGS"), - iodine_default_args); - - // initialize Object storage for GC protection - iodine_storage_init(); - - // initialize concurrency related methods - iodine_defer_initialize(); - - // initialize the connection class - iodine_connection_init(); - - // intialize the TCP/IP related module - iodine_init_tcp_connections(); - - // initialize the HTTP module - iodine_init_http(); - -#ifndef __MINGW32__ - // initialize SSL/TLS support module - iodine_init_tls(); -#endif - - // initialize JSON helpers - iodine_init_json(); - - // initialize Mustache engine - iodine_init_mustache(); - - // initialize Rack helpers and IO - iodine_init_helpers(); - IodineRackIO.init(); - - // initialize Pub/Sub extension (for Engines) - iodine_pubsub_init(); +void Init_iodine(void) { + fio_state_callback_force(FIO_CALL_ON_INITIALIZE); + + IodineUTF8Encoding = rb_enc_find("UTF-8"); + IodineBinaryEncoding = rb_enc_find("binary"); + + /** The Iodine module is where it all happens. */ + iodine_rb_IODINE = rb_define_module("Iodine"); + STORE.hold(iodine_rb_IODINE); + /** The PubSub module contains Pub/Sub related classes / data. */ + iodine_rb_IODINE_PUBSUB = rb_define_module_under(iodine_rb_IODINE, "PubSub"); + STORE.hold(iodine_rb_IODINE_PUBSUB); + /** The Iodine::Base module is for internal concerns. */ + iodine_rb_IODINE_BASE = + rb_define_class_under(iodine_rb_IODINE, "Base", rb_cObject); + STORE.hold(iodine_rb_IODINE_BASE); + /** The Iodine::Base::App404 module is for static file only. */ + iodine_rb_IODINE_BASE_APP404 = + rb_define_module_under(iodine_rb_IODINE_BASE, "App404"); + STORE.hold(iodine_rb_IODINE_BASE_APP404); + { /** Initialize `STORE` and object reference counting. */ + iodine_setup_value_reference_counter(iodine_rb_IODINE_BASE); + rb_define_singleton_method(iodine_rb_IODINE_BASE, + "print_debug", + iodine_store___print_debug, + 0); + } + + IODINE_CONST_ID_STORE(IODINE_CALL_ID, "call"); + IODINE_CONST_ID_STORE(IODINE_CLOSE_ID, "close"); + IODINE_CONST_ID_STORE(IODINE_EACH_ID, "each"); + IODINE_CONST_ID_STORE(IODINE_FILENO_ID, "fileno"); + IODINE_CONST_ID_STORE(IODINE_NEW_ID, "new"); + IODINE_CONST_ID_STORE(IODINE_TO_PATH_ID, "to_path"); + IODINE_CONST_ID_STORE(IODINE_TO_S_ID, "to_s"); + + IODINE_CONST_ID_STORE(IODINE_RACK_HIJACK_ID, "rack_hijack"); + IODINE_CONST_ID_STORE(IODINE_ON_AUTHENTICATE_ID, "on_authenticate"); + IODINE_CONST_ID_STORE(IODINE_ON_AUTHENTICATE_SSE_ID, "on_authenticate_sse"); + IODINE_CONST_ID_STORE(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID, + "on_authenticate_websocket"); + IODINE_CONST_ID_STORE(IODINE_ON_CLOSE_ID, "on_close"); + IODINE_CONST_ID_STORE(IODINE_ON_DATA_ID, "on_data"); + IODINE_CONST_ID_STORE(IODINE_ON_DRAINED_ID, "on_drained"); + IODINE_CONST_ID_STORE(IODINE_ON_EVENTSOURCE_ID, "on_eventsource"); + IODINE_CONST_ID_STORE(IODINE_ON_EVENTSOURCE_RECONNECT_ID, + "on_eventsource_reconnect"); + IODINE_CONST_ID_STORE(IODINE_ON_FINISH_ID, "on_finish"); + IODINE_CONST_ID_STORE(IODINE_ON_HTTP_ID, "on_http"); + IODINE_CONST_ID_STORE(IODINE_ON_MESSAGE_ID, "on_message"); + IODINE_CONST_ID_STORE(IODINE_ON_OPEN_ID, "on_open"); + IODINE_CONST_ID_STORE(IODINE_ON_SHUTDOWN_ID, "on_shutdown"); + IODINE_CONST_ID_STORE(IODINE_ON_TIMEOUT_ID, "on_timeout"); + + IODINE_CONST_ID_STORE(IODINE_STATE_PRE_START, "pre_start"); + IODINE_CONST_ID_STORE(IODINE_STATE_BEFORE_FORK, "before_fork"); + IODINE_CONST_ID_STORE(IODINE_STATE_AFTER_FORK, "after_fork"); + IODINE_CONST_ID_STORE(IODINE_STATE_ENTER_CHILD, "enter_child"); + IODINE_CONST_ID_STORE(IODINE_STATE_ENTER_MASTER, "enter_master"); + IODINE_CONST_ID_STORE(IODINE_STATE_ON_START, "on_start"); + IODINE_CONST_ID_STORE(IODINE_STATE_ON_PARENT_CRUSH, "on_parent_crush"); + IODINE_CONST_ID_STORE(IODINE_STATE_ON_CHILD_CRUSH, "on_child_crush"); + IODINE_CONST_ID_STORE(IODINE_STATE_ON_SHUTDOWN, "on_shutdown"); + IODINE_CONST_ID_STORE(IODINE_STATE_ON_STOP, "on_stop"); + + STORE.hold(IODINE_RACK_HIJACK_SYM = rb_id2sym(IODINE_RACK_HIJACK_ID)); + STORE.hold(IODINE_RACK_HIJACK_STR = rb_str_new_static("rack.hijack", 11)); + + IODINE_RACK_UPGRADE_STR = + STORE.frozen_str(FIO_STR_INFO1((char *)"rack.upgrade")); + IODINE_RACK_UPGRADE_Q_STR = + STORE.frozen_str(FIO_STR_INFO1((char *)"rack.upgrade?")); + STORE.hold(IODINE_RACK_UPGRADE_WS_SYM = rb_id2sym(rb_intern("websocket"))); + STORE.hold(IODINE_RACK_UPGRADE_SSE_SYM = rb_id2sym(rb_intern("sse"))); + STORE.hold(IODINE_RACK_AFTER_RPLY_STR = + rb_str_new_static("rack.after_reply", 16)); + + Init_Iodine(); + Init_Iodine_Base_CLI(); + Init_Iodine_Mustache(); + Init_Iodine_Utils(); + Init_Iodine_JSON(); + Init_Iodine_MiniMap(); + Init_Iodine_PubSub_Engine(); + Init_Iodine_PubSub_Message(); + Init_Iodine_TLS(); + Init_Iodine_Connection(); + + fio_io_async_attach(&IODINE_THREAD_POOL, (uint32_t)fio_cli_get_i("-t")); } diff --git a/ext/iodine/iodine.h b/ext/iodine/iodine.h index d5ed3c6a..d53f8154 100644 --- a/ext/iodine/iodine.h +++ b/ext/iodine/iodine.h @@ -1,63 +1,137 @@ -#ifndef H_IODINE_H -#define H_IODINE_H - -#include "ruby.h" - -#include "fio.h" -#include "fio_tls.h" -#include "fiobj.h" -/* used for iodine_connect and iodine_listen routing */ -typedef struct { - fio_str_info_s address; - fio_str_info_s port; - fio_str_info_s method; - fio_str_info_s path; - fio_str_info_s body; - fio_str_info_s public; - fio_str_info_s url; -#ifndef __MINGW32__ - fio_tls_s *tls; -#endif - VALUE handler; - FIOBJ headers; - FIOBJ cookies; - size_t max_headers; - size_t max_body; - intptr_t max_clients; - size_t max_msg; - uint8_t timeout; - uint8_t ping; - uint8_t log; - enum { - IODINE_SERVICE_RAW, - IODINE_SERVICE_HTTP, - IODINE_SERVICE_WS, - } service; -} iodine_connection_args_s; +#ifndef H___IODINE___H +#define H___IODINE___H +#include +#include +#include +#include +#include -#include "iodine_caller.h" -#include "iodine_connection.h" -#include "iodine_defer.h" -#include "iodine_helpers.h" -#include "iodine_http.h" -#include "iodine_json.h" -#include "iodine_mustache.h" -#include "iodine_pubsub.h" -#include "iodine_rack_io.h" -#include "iodine_store.h" -#include "iodine_tcp.h" -#include "iodine_tls.h" +typedef int fio_thread_pid_t; +typedef VALUE fio_thread_t; /* ***************************************************************************** Constants ***************************************************************************** */ -extern VALUE IodineModule; -extern VALUE IodineBaseModule; -extern VALUE iodine_default_args; -extern ID iodine_call_id; -extern ID iodine_to_s_id; -#define IODINE_RSTRINFO(rstr) \ - ((fio_str_info_s){.len = RSTRING_LEN(rstr), .data = RSTRING_PTR(rstr)}) +static ID IODINE_CALL_ID; +static ID IODINE_CLOSE_ID; +static ID IODINE_EACH_ID; +static ID IODINE_FILENO_ID; +static ID IODINE_NEW_ID; +static ID IODINE_TO_PATH_ID; +static ID IODINE_TO_S_ID; +static ID IODINE_RACK_HIJACK_ID; +static ID IODINE_ON_AUTHENTICATE_ID; +static ID IODINE_ON_AUTHENTICATE_SSE_ID; +static ID IODINE_ON_AUTHENTICATE_WEBSOCKET_ID; +static ID IODINE_ON_CLOSE_ID; +static ID IODINE_ON_DATA_ID; +static ID IODINE_ON_DRAINED_ID; +static ID IODINE_ON_EVENTSOURCE_ID; +static ID IODINE_ON_EVENTSOURCE_RECONNECT_ID; +static ID IODINE_ON_FINISH_ID; +static ID IODINE_ON_HTTP_ID; +static ID IODINE_ON_MESSAGE_ID; +static ID IODINE_ON_OPEN_ID; +static ID IODINE_ON_SHUTDOWN_ID; +static ID IODINE_ON_TIMEOUT_ID; +#define IODINE_CONST_ID_STORE(name, value) \ + name = rb_intern(value); \ + STORE.hold(RB_ID2SYM(name)); + +static VALUE iodine_rb_IODINE; +static VALUE iodine_rb_IODINE_BASE; +static VALUE iodine_rb_IODINE_BASE_APP404; +static VALUE iodine_rb_IODINE_CONNECTION; +static VALUE iodine_rb_IODINE_PUBSUB; +static VALUE iodine_rb_IODINE_PUBSUB_ENG; +static VALUE iodine_rb_IODINE_PUBSUB_MSG; +static rb_encoding *IodineUTF8Encoding; +static rb_encoding *IodineBinaryEncoding; + +static VALUE IODINE_CONNECTION_ENV_TEMPLATE = Qnil; +static VALUE IODINE_RACK_HIJACK_SYM; +static VALUE IODINE_RACK_HIJACK_STR; +static VALUE IODINE_RACK_UPGRADE_STR; +static VALUE IODINE_RACK_UPGRADE_Q_STR; +static VALUE IODINE_RACK_UPGRADE_WS_SYM; +static VALUE IODINE_RACK_UPGRADE_SSE_SYM; +static VALUE IODINE_RACK_AFTER_RPLY_STR; +#ifndef IODINE_RAW_ON_DATA_READ_BUFFER +#define IODINE_RAW_ON_DATA_READ_BUFFER (1ULL << 14) #endif + +#define IODINE_STORE_IS_SKIP(o) \ + (!o || o == Qnil || o == Qtrue || o == Qfalse || TYPE(o) == RUBY_T_FIXNUM) + +#define IODINE_RSTR_INFO(o) \ + { .buf = RSTRING_PTR(o), .len = (size_t)RSTRING_LEN(o) } + +/* shadow exit function and route it to Ruby */ +#define exit(status) rb_exit(status) + +/* ***************************************************************************** +facil.io +***************************************************************************** */ +#define FIO_LEAK_COUNTER 1 +#define FIO_MUSTACHE_LAMBDA_SUPPORT 1 +#define FIO_THREADS_BYO 1 +#define FIO_THREADS_FORK_BYO 1 +#define FIO_MEMORY_ARENA_COUNT_MAX 4 +#define FIO_EVERYTHING +#include "fio-stl.h" + +/* ***************************************************************************** +Deferring Ruby Blocks +***************************************************************************** */ +static void iodine_defer_performe_once(void *block, void *ignr); + +#define IODINE_DEFER_BLOCK(blk) \ + do { \ + STORE.hold((blk)); \ + fio_io_async(&IODINE_THREAD_POOL, \ + iodine_defer_performe_once, \ + (void *)(blk)); \ + } while (0) + +/* ***************************************************************************** +Leak Counters +***************************************************************************** */ +FIO_LEAK_COUNTER_DEF(iodine_connection) +FIO_LEAK_COUNTER_DEF(iodine_minimap) +FIO_LEAK_COUNTER_DEF(iodine_mustache) +FIO_LEAK_COUNTER_DEF(iodine_pubsub_msg) +FIO_LEAK_COUNTER_DEF(iodine_pubsub_eng) +FIO_LEAK_COUNTER_DEF(iodine_hmap) + +/* ***************************************************************************** +Common Iodine Helpers +***************************************************************************** */ + +/* IO reactor thread-pool */ +static fio_io_async_s IODINE_THREAD_POOL; + +/* layer 1 helpers */ +#include "iodine_arg_helper.h" +#include "iodine_store.h" +/* layer 2 helpers */ +#include "iodine_caller.h" +#include "iodine_json.h" +#include "iodine_pubsub_msg.h" +/* layer 1 modules */ +#include "iodine_cli.h" +#include "iodine_defer.h" +#include "iodine_minimap.h" +#include "iodine_mustache.h" +#include "iodine_pubsub_eng.h" +#include "iodine_threads.h" +#include "iodine_tls.h" +#include "iodine_utils.h" +/* layer 2 modules */ +#include "iodine_connection.h" + +/* layer 3 modules */ +#include "iodine_core.h" + +#endif /* H___IODINE___H */ diff --git a/ext/iodine/iodine_arg_helper.h b/ext/iodine/iodine_arg_helper.h new file mode 100644 index 00000000..4753604a --- /dev/null +++ b/ext/iodine/iodine_arg_helper.h @@ -0,0 +1,287 @@ +#ifndef H___IODINE_ARG_HELPER___H +#define H___IODINE_ARG_HELPER___H +#include "iodine.h" + +/* ***************************************************************************** +Ruby Multi-Argument Helper + +Reads and validates method arguments (either "splat" or Hash Map). + + iodine_rb2c_arg(argc, argv, + IODINE_ARG_RB(var_name, id_or_0, "name1", 1), + IODINE_ARG_STR(var_name, id_or_0, "name2", 0), + IODINE_ARG_NUM(var_name, id_or_0, "name3", 0), + IODINE_ARG_PROC(var_name, id_or_0, "name4", 0)); + +The `IODINE_ARG_XXX` macro arguments are as follows: + + IODINE_ARG_XXX(target_varriable, id_or_0, named_argument, required) + +Example: + + fio_buf_info_s file_name = {0}; + fio_buf_info_s file_content = {0}; + VALUE on_yaml_block = Qnil; + iodine_rb2c_arg(argc, argv, + IODINE_ARG_STR(file_name, 0, "file", 0), + IODINE_ARG_STR(file_content, 0, "data", 0), + IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 1)); + +For Ruby methods declared as: + + static VALUE my_method(int argc, VALUE *argv, VALUE klass); + rb_define_singleton_method(klass, "method", my_method, -1); + +***************************************************************************** */ + +/* ***************************************************************************** +Ruby arguments to C arguments +***************************************************************************** */ + +typedef struct { + int expected_type; + union { + VALUE *rb; + fio_buf_info_s *buf; + fio_str_info_s *str; + int64_t *num; + size_t *zu; + int32_t *i32; + int16_t *i16; + int8_t *i8; + uint32_t *u32; + uint16_t *u16; + uint8_t *u8; + }; + ID id; + fio_buf_info_s name; + int required; +} iodine_rb2c_arg_s; + +#define IODINE_ARG_RB(t_var, id_, name_, required_) \ + { \ + .expected_type = 0, .rb = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_BUF(t_var, id_, name_, required_) \ + { \ + .expected_type = 1, .buf = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_STR(t_var, id_, name_, required_) \ + { \ + .expected_type = 2, .str = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_NUM(t_var, id_, name_, required_) \ + { \ + .expected_type = 3, .num = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_PROC(t_var, id_, name_, required_) \ + { \ + .expected_type = 4, .rb = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_SIZE_T(t_var, id_, name_, required_) \ + { \ + .expected_type = 5, .zu = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_I32(t_var, id_, name_, required_) \ + { \ + .expected_type = 6, .i32 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_I16(t_var, id_, name_, required_) \ + { \ + .expected_type = 7, .i16 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_I8(t_var, id_, name_, required_) \ + { \ + .expected_type = 8, .i8 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_U32(t_var, id_, name_, required_) \ + { \ + .expected_type = 6, .u32 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_U16(t_var, id_, name_, required_) \ + { \ + .expected_type = 7, .u16 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_U8(t_var, id_, name_, required_) \ + { \ + .expected_type = 8, .u8 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } +#define IODINE_ARG_BOOL(t_var, id_, name_, required_) \ + { \ + .expected_type = 9, .u8 = &(t_var), .id = (id_), \ + .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \ + } + +/** Reads and validates method arguments (either "splat" or Hash Map). */ +static int iodine_rb2c_arg(int argc, const VALUE *argv, iodine_rb2c_arg_s *a) { + int i = 0; + VALUE tmp = Qnil; + +#define IODINE_RB2C_STORE_ARG() \ + if (!a[i].rb) \ + goto too_many_arguments; \ + if (tmp == Qnil && a[i].expected_type != 4) { \ + if (a[i].required) \ + goto missing_required; \ + continue; \ + } \ + switch (a[i].expected_type) { \ + case 0: a[i].rb[0] = tmp; continue; \ + case 1: \ + if (RB_TYPE_P(tmp, RUBY_T_SYMBOL)) \ + tmp = rb_sym2str(tmp); \ + if (!RB_TYPE_P(tmp, RUBY_T_STRING)) \ + rb_raise(rb_eTypeError, \ + "%s should be a String (or Symbol)", \ + a[i].name.buf); \ + a[i].buf[0] = FIO_BUF_INFO2(RSTRING_PTR(tmp), (size_t)RSTRING_LEN(tmp)); \ + continue; \ + case 2: \ + if (RB_TYPE_P(tmp, RUBY_T_SYMBOL)) \ + tmp = rb_sym2str(tmp); \ + if (!RB_TYPE_P(tmp, RUBY_T_STRING)) \ + rb_raise(rb_eTypeError, \ + "%s should be a String (or Symbol)", \ + a[i].name.buf); \ + a[i].str[0] = FIO_STR_INFO2(RSTRING_PTR(tmp), (size_t)RSTRING_LEN(tmp)); \ + continue; \ + case 3: \ + if (!RB_TYPE_P(tmp, RUBY_T_FIXNUM)) \ + rb_raise(rb_eTypeError, "%s should be a Number", a[i].name.buf); \ + a[i].num[0] = NUM2LL(tmp); \ + continue; \ + case 4: \ + if (tmp == Qnil) { \ + if (rb_block_given_p()) \ + tmp = rb_block_proc(); \ + else if (a[i].required) \ + goto missing_required; \ + else \ + continue; \ + } else if (tmp != Qnil && !rb_respond_to(tmp, rb_intern2("call", 4))) \ + rb_raise(rb_eArgError, "a callback object MUST respond to `call`"); \ + a[i].rb[0] = tmp; \ + continue; \ + case 5: \ + if (tmp != Qnil) { \ + if (!RB_TYPE_P(tmp, RUBY_T_FIXNUM)) \ + rb_raise(rb_eTypeError, "%s should be a Number", a[i].name.buf); \ + a[i].zu[0] = NUM2SIZET(tmp); \ + } \ + continue; \ + case 6: \ + if (tmp != Qnil) { \ + if (!RB_TYPE_P(tmp, RUBY_T_FIXNUM)) \ + rb_raise(rb_eTypeError, "%s should be a Number", a[i].name.buf); \ + if (NUM2ULL(tmp) > 0xFFFFFFFFULL) \ + rb_raise(rb_eRangeError, "%s out of range", a[i].name.buf); \ + a[i].num[0] = NUM2INT(tmp); \ + } \ + continue; \ + case 7: \ + if (tmp != Qnil) { \ + if (!RB_TYPE_P(tmp, RUBY_T_FIXNUM)) \ + rb_raise(rb_eTypeError, "%s should be a Number", a[i].name.buf); \ + if (NUM2ULL(tmp) > 0xFFFFULL) \ + rb_raise(rb_eRangeError, "%s out of range", a[i].name.buf); \ + a[i].num[0] = NUM2SHORT(tmp); \ + } \ + continue; \ + case 8: \ + if (tmp != Qnil) { \ + if (!RB_TYPE_P(tmp, RUBY_T_FIXNUM)) \ + rb_raise(rb_eTypeError, "%s should be a Number", a[i].name.buf); \ + if (NUM2ULL(tmp) > 0xFFULL) \ + rb_raise(rb_eRangeError, "%s out of range", a[i].name.buf); \ + a[i].num[0] = NUM2CHR(tmp); \ + } \ + continue; \ + case 9: \ + if (tmp != Qnil) { \ + if (tmp != Qtrue && tmp != Qfalse) \ + if (!RB_TYPE_P(tmp, RUBY_T_TRUE)) \ + rb_raise(rb_eTypeError, "%s should be a Boolean", a[i].name.buf); \ + a[i].u8[0] = (tmp == Qtrue); \ + } \ + continue; \ + default: \ + rb_raise(rb_eException, \ + "C code failure - missing valid expected_type @ %s", \ + a[i].name.buf); \ + } + /* FIXME: allow partially named arguments. */ + if (argc) { + --argc; + for (; i < argc; ++i) { + /* unnamed parameters (list of parameters), excluding last parameter */ + tmp = argv[i]; + IODINE_RB2C_STORE_ARG(); + } + /* last parameter is special, as it may be a named parameter Hash */ + tmp = argv[argc]; + i = argc; /* does `for` add even after condition breaks loop? no, so why? */ + if (RB_TYPE_P(tmp, RUBY_T_HASH)) { /* named parameters (hash table) */ + VALUE tbl = tmp; + if (a[i].expected_type == 0) + a[i].rb[0] = tmp; + for (; a[i].rb; ++i) { + tmp = rb_hash_aref( + tbl, + rb_id2sym(a[i].id ? a[i].id + : rb_intern2(a[i].name.buf, a[i].name.len))); + IODINE_RB2C_STORE_ARG(); + } + return 0; + } + do { + IODINE_RB2C_STORE_ARG(); + } while (0); + tmp = Qnil; + } + for (; a[i].rb; ++i) { + /* possible leftover last parameter */ + if (a[i].expected_type == 4) { + IODINE_RB2C_STORE_ARG(); + continue; + } + if (a[i].required) + goto missing_required; + } + return 0; + +#undef IODINE_RB2C_STORE_ARG +too_many_arguments: + rb_raise(rb_eArgError, "too many arguments in method call."); + return -1; +missing_required: + rb_raise(rb_eArgError, "missing required argument %s.", a[i].name.buf); + return -1; +} + +/** + * Reads and validates method arguments (either "splat" or Hash Map). + * + * Use: + * + * iodine_rb2c_arg(argc, argv, + * IODINE_ARG_RB(var_name, id_or_0, "name1", 1), + * IODINE_ARG_STR(var_name, id_or_0, "name2", 0), + * IODINE_ARG_NUM(var_name, id_or_0, "name3", 0), + * IODINE_ARG_PROC(var_name, id_or_0, "name4", 0)); + */ +#define iodine_rb2c_arg(argc, argv, ...) \ + iodine_rb2c_arg(argc, argv, (iodine_rb2c_arg_s[]){__VA_ARGS__, {0}}) + +#endif /* H___IODINE_ARG_HELPER___H */ diff --git a/ext/iodine/iodine_caller.c b/ext/iodine/iodine_caller.c deleted file mode 100644 index 6ef363f6..00000000 --- a/ext/iodine/iodine_caller.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "iodine_caller.h" - -#include -#include - -#include - -#include - -static pthread_key_t iodine_GVL_state_key; -static pthread_once_t iodine_GVL_state_once = PTHREAD_ONCE_INIT; -static void init_iodine_GVL_state_key(void) { - pthread_key_create(&iodine_GVL_state_key, NULL); -} -static void init_iodine_GVL_state_init(void) { - uint8_t *gvl = malloc(sizeof(uint8_t)); - FIO_ASSERT_ALLOC(gvl); - *gvl = 1; - pthread_setspecific(iodine_GVL_state_key, gvl); -} - -/* ***************************************************************************** -Calling protected Ruby methods -***************************************************************************** */ - -/* task container */ -typedef struct { - VALUE obj; - int argc; - VALUE *argv; - ID method; - int exception; - VALUE (*protected_task)(VALUE tsk_); - VALUE (*each_func)(VALUE block_arg, VALUE data, int argc, VALUE *argv); - VALUE each_udata; -} iodine_rb_task_s; - -/* printout backtrace in case of exceptions */ -static void *iodine_handle_exception(void *ignr) { - (void)ignr; - VALUE exc = rb_errinfo(); - if (exc != Qnil && rb_respond_to(exc, rb_intern("message")) && - rb_respond_to(exc, rb_intern("backtrace"))) { - VALUE msg = rb_funcall2(exc, rb_intern("message"), 0, NULL); - VALUE exc_class = rb_class_name(CLASS_OF(exc)); - VALUE bt = rb_funcall2(exc, rb_intern("backtrace"), 0, NULL); - if (TYPE(bt) == T_ARRAY) { - bt = rb_ary_join(bt, rb_str_new_literal("\n")); - FIO_LOG_ERROR("Iodine caught an unprotected exception - %.*s: %.*s\n%s", - (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class), - (int)RSTRING_LEN(msg), RSTRING_PTR(msg), - StringValueCStr(bt)); - } else { - FIO_LOG_ERROR("Iodine caught an unprotected exception - %.*s: %.*s\n" - "No backtrace available.\n", - (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class), - (int)RSTRING_LEN(msg), RSTRING_PTR(msg)); - } - rb_backtrace(); - FIO_LOG_ERROR("\n"); - rb_set_errinfo(Qnil); - } else if (exc != Qnil) { - FIO_LOG_ERROR( - "Iodine caught an unprotected exception - NO MESSAGE / DATA AVAILABLE"); - } - return (void *)Qnil; -} - -/* calls the Ruby each method within the protection block */ -static VALUE iodine_ruby_caller_perform_block(VALUE tsk_) { - iodine_rb_task_s *task = (void *)tsk_; - return rb_block_call(task->obj, task->method, task->argc, task->argv, - task->each_func, task->each_udata); -} - -/* calls the Ruby method within the protection block */ -static VALUE iodine_ruby_caller_perform(VALUE tsk_) { - iodine_rb_task_s *task = (void *)tsk_; - return rb_funcall2(task->obj, task->method, task->argc, task->argv); -} - -/* wrap the function call in exception handling block (uses longjmp) */ -static void *iodine_protect_ruby_call(void *task_) { - int state = 0; - VALUE ret = rb_protect(((iodine_rb_task_s *)task_)->protected_task, - (VALUE)(task_), &state); - if (state) { - iodine_handle_exception(NULL); - } - return (void *)ret; -} - -/* ***************************************************************************** -API -***************************************************************************** */ - -/** Calls a C function within the GVL. */ -static void *iodine_enterGVL(void *(*func)(void *), void *arg) { - pthread_once(&iodine_GVL_state_once, init_iodine_GVL_state_key); - uint8_t *iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - if (!iodine_GVL_state) { - init_iodine_GVL_state_init(); - iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - } - if (*iodine_GVL_state) { - return func(arg); - } - void *rv = NULL; - *iodine_GVL_state = 1; - rv = rb_thread_call_with_gvl(func, arg); - *iodine_GVL_state = 0; - return rv; -} - -/** Calls a C function outside the GVL. */ -static void *iodine_leaveGVL(void *(*func)(void *), void *arg) { - pthread_once(&iodine_GVL_state_once, init_iodine_GVL_state_key); - uint8_t *iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - if (!iodine_GVL_state) { - init_iodine_GVL_state_init(); - iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - } - if (!*iodine_GVL_state) { - return func(arg); - } - void *rv = NULL; - *iodine_GVL_state = 0; - rv = rb_thread_call_without_gvl(func, arg, NULL, NULL); - *iodine_GVL_state = 1; - return rv; -} - -/** Calls a Ruby method on a given object, protecting against exceptions. */ -static VALUE iodine_call(VALUE obj, ID method) { - iodine_rb_task_s task = { - .obj = obj, - .argc = 0, - .argv = NULL, - .method = method, - .protected_task = iodine_ruby_caller_perform, - }; - void *rv = iodine_enterGVL(iodine_protect_ruby_call, &task); - return (VALUE)rv; -} - -/** Calls a Ruby method on a given object, protecting against exceptions. */ -static VALUE iodine_call2(VALUE obj, ID method, int argc, VALUE *argv) { - iodine_rb_task_s task = { - .obj = obj, - .argc = argc, - .argv = argv, - .method = method, - .protected_task = iodine_ruby_caller_perform, - }; - void *rv = iodine_enterGVL(iodine_protect_ruby_call, &task); - return (VALUE)rv; -} - -/** Calls a Ruby method on a given object, protecting against exceptions. */ -static VALUE iodine_call_block(VALUE obj, ID method, int argc, VALUE *argv, - VALUE udata, - VALUE(each_func)(VALUE block_arg, VALUE udata, - int argc, VALUE *argv)) { - iodine_rb_task_s task = { - .obj = obj, - .argc = argc, - .argv = argv, - .method = method, - .protected_task = iodine_ruby_caller_perform_block, - .each_func = each_func, - .each_udata = udata, - }; - void *rv = iodine_enterGVL(iodine_protect_ruby_call, &task); - return (VALUE)rv; -} - -/** Returns the GVL state flag. */ -static uint8_t iodine_in_GVL(void) { - pthread_once(&iodine_GVL_state_once, init_iodine_GVL_state_key); - uint8_t *iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - if (!iodine_GVL_state) { - init_iodine_GVL_state_init(); - iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - } - return *iodine_GVL_state; -} - -/** Forces the GVL state flag. */ -static void iodine_set_GVL(uint8_t state) { - pthread_once(&iodine_GVL_state_once, init_iodine_GVL_state_key); - uint8_t *iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - if (!iodine_GVL_state) { - init_iodine_GVL_state_init(); - iodine_GVL_state = pthread_getspecific(iodine_GVL_state_key); - } - *iodine_GVL_state = state; -} - -/* ***************************************************************************** -Caller Initialization -***************************************************************************** */ - -struct IodineCaller_s IodineCaller = { - /** Calls a C function within the GVL. */ - .enterGVL = iodine_enterGVL, - /** Calls a C function outside the GVL. */ - .leaveGVL = iodine_leaveGVL, - /** Calls a Ruby method on a given object, protecting against exceptions. */ - .call_with_block = iodine_call_block, - /** Calls a Ruby method on a given object, protecting against exceptions. */ - .call = iodine_call, - /** Calls a Ruby method on a given object, protecting against exceptions. */ - .call2 = iodine_call2, - /** Returns the GVL state flag. */ - .in_GVL = iodine_in_GVL, - /** Forces the GVL state flag. */ - .set_GVL = iodine_set_GVL, -}; diff --git a/ext/iodine/iodine_caller.h b/ext/iodine/iodine_caller.h index c1be97c8..231ee09e 100644 --- a/ext/iodine/iodine_caller.h +++ b/ext/iodine/iodine_caller.h @@ -1,27 +1,173 @@ -#ifndef H_IODINE_CALLER_H -#define H_IODINE_CALLER_H - -#include "ruby.h" - -#include - -extern struct IodineCaller_s { - /** Calls a C function within the GVL (unprotected). */ - void *(*enterGVL)(void *(*func)(void *), void *arg); - /** Calls a C function outside the GVL (no Ruby API calls allowed). */ - void *(*leaveGVL)(void *(*func)(void *), void *arg); - /** Calls a Ruby method on a given object, protecting against exceptions. */ - VALUE (*call)(VALUE obj, ID method); - /** Calls a Ruby method on a given object, protecting against exceptions. */ - VALUE (*call2)(VALUE obj, ID method, int argc, VALUE *argv); - /** Calls a Ruby method on a given object, protecting against exceptions. */ - VALUE(*call_with_block) - (VALUE obj, ID method, int argc, VALUE *argv, VALUE udata, - VALUE (*block_func)(VALUE block_argv1, VALUE udata, int argc, VALUE *argv)); - /** Returns the GVL state flag. */ - uint8_t (*in_GVL)(void); - /** Forces the GVL state flag. */ - void (*set_GVL)(uint8_t state); -} IodineCaller; - -#endif +#ifndef H___IODINE_CALLER___H +#define H___IODINE_CALLER___H +#include "iodine.h" + +/* ***************************************************************************** +Ruby Caller and Thread (GVL) Helper + +From within the GVL call Ruby functions so: + + iodine_caller_result_s r = iodine_ruby_call_inside(recv, mid, + argc, argv, proc); + +From outside the GVL call Ruby functions so: + + iodine_caller_result_s r = iodine_ruby_call_outside(recv, mid, argc, argv); + +***************************************************************************** */ + +/* printout backtrace in case of exceptions */ +static void *iodine_handle_exception(void *ignr) { + (void)ignr; + FIO_LOG_ERROR("iodine catching an exposed exception"); + VALUE exc = rb_errinfo(); + if (exc != Qnil && rb_respond_to(exc, rb_intern("message")) && + rb_respond_to(exc, rb_intern("backtrace"))) { + VALUE msg = rb_funcallv(exc, rb_intern("message"), 0, NULL); + VALUE exc_class = rb_class_name(CLASS_OF(exc)); + VALUE bt = rb_funcallv(exc, rb_intern("backtrace"), 0, NULL); + if (msg == Qnil) + msg = rb_str_new("Error message unavailable", 25); + if (exc_class == Qnil) + exc_class = rb_str_new("unknown exception class", 23); + if (TYPE(bt) == RUBY_T_ARRAY) { + bt = rb_ary_join(bt, rb_str_new_literal("\n")); + FIO_LOG_ERROR("Iodine caught an unprotected exception - %.*s: %.*s\n %s ", + (int)RSTRING_LEN(exc_class), + RSTRING_PTR(exc_class), + (int)RSTRING_LEN(msg), + RSTRING_PTR(msg), + StringValueCStr(bt)); + } else if (TYPE(bt) == RUBY_T_STRING) { + FIO_LOG_ERROR("Iodine caught an unprotected exception - %.*s: %.*s\n" + "No backtrace available.\n", + (int)RSTRING_LEN(exc_class), + RSTRING_PTR(exc_class), + (int)RSTRING_LEN(msg), + RSTRING_PTR(msg)); + } else { + FIO_LOG_ERROR("Iodine caught an unprotected exception - %.*s: %.*s\n %s\n" + "BACKTRACE UNAVAILABLE!\n", + (int)RSTRING_LEN(exc_class), + RSTRING_PTR(exc_class), + (int)RSTRING_LEN(msg), + RSTRING_PTR(msg)); + FIO_LOG_ERROR("Backtrace missing."); + } + rb_backtrace(); + FIO_LOG_ERROR("\n"); + rb_set_errinfo(Qnil); + } else if (exc != Qnil) { + FIO_LOG_ERROR( + "Iodine caught an unprotected exception - NO MESSAGE / DATA AVAILABLE"); + } + return (void *)Qnil; +} + +typedef struct { + VALUE recv; + ID mid; + int argc; + VALUE *argv; + VALUE proc; +} iodine_caller_args_s; + +typedef struct { + VALUE result; + int exception; +} iodine_caller_result_s; + +typedef struct { + iodine_caller_args_s in; + iodine_caller_result_s out; +} iodine___caller_s; + +static VALUE iodine___func_caller_task(VALUE args_) { + iodine_caller_args_s *args = (iodine_caller_args_s *)args_; + return rb_funcallv(args->recv, args->mid, args->argc, args->argv); +} + +static VALUE iodine___func_caller_task_proc(VALUE args_) { + iodine_caller_args_s *args = (iodine_caller_args_s *)args_; + return rb_funcall_with_block(args->recv, + args->mid, + args->argc, + args->argv, + args->proc); +} + +static void *iodine_ruby____outside_task_proc(void *c_) { + iodine___caller_s *c = (iodine___caller_s *)c_; + c->out.result = rb_protect(iodine___func_caller_task_proc, + (VALUE)&c->in, + &c->out.exception); + if (c->out.exception) + iodine_handle_exception(NULL); + return NULL; +} + +static void *iodine_ruby____outside_task(void *c_) { + iodine___caller_s *c = (iodine___caller_s *)c_; + c->out.result = + rb_protect(iodine___func_caller_task, (VALUE)&c->in, &c->out.exception); + if (c->out.exception) + iodine_handle_exception(NULL); + return NULL; +} + +/* ***************************************************************************** +Ruby Caller and Thread (GVL) - Helper Implementation +***************************************************************************** */ + +inline static iodine_caller_result_s iodine_ruby_call_inside( + iodine_caller_args_s args) { + iodine_caller_result_s r = {0}; + FIO_ASSERT_DEBUG(args.recv && args.mid, + "iodine_ruby_call requires an object and method name"); + VALUE stub[1] = {Qnil}; + if (!args.argv) + args.argv = stub; + r.result = rb_protect( + (args.proc ? iodine___func_caller_task_proc : iodine___func_caller_task), + (VALUE)&args, + &r.exception); + if (r.exception) + iodine_handle_exception(NULL); + return r; +} + +inline static iodine_caller_result_s iodine_ruby_call_outside( + iodine_caller_args_s args) { + VALUE stub[1] = {Qnil}; + if (!args.argv) + args.argv = stub; + iodine___caller_s r = {args, {0}}; + FIO_ASSERT_DEBUG(args.recv && args.mid, + "iodine_ruby_call requires an object and method name"); + rb_thread_call_with_gvl(args.proc ? iodine_ruby____outside_task_proc + : iodine_ruby____outside_task, + &r); + return r.out; +} + +/** + +Accept the following, possibly named, arguments: + + (VALUE recv, ID mid, int argc, VALUE *argv, VALUE proc) + +*/ +#define iodine_ruby_call_inside(...) \ + iodine_ruby_call_inside((iodine_caller_args_s){__VA_ARGS__}) + +/** + +Accept the following, possibly named, arguments: + + (VALUE recv, ID mid, int argc, VALUE *argv, VALUE proc) + +*/ +#define iodine_ruby_call_outside(...) \ + iodine_ruby_call_outside((iodine_caller_args_s){__VA_ARGS__}) + +#endif /* H___IODINE_CALLER___H */ diff --git a/ext/iodine/iodine_cli.h b/ext/iodine/iodine_cli.h new file mode 100644 index 00000000..4555bbee --- /dev/null +++ b/ext/iodine/iodine_cli.h @@ -0,0 +1,327 @@ +#ifndef H___IODINE_CLI___H +#define H___IODINE_CLI___H +#include "iodine.h" + +#define IODINE_CLI_LIMIT 256 + +static int iodine_cli_task(fio_buf_info_s name, + fio_buf_info_s val, + fio_cli_arg_e t, + void *h_) { + static long at = 0; + VALUE h = (VALUE)h_; + VALUE tmp = Qnil; + VALUE n = Qnil; + VALUE v = Qnil; + switch (t) { + case FIO_CLI_ARG_BOOL: v = Qtrue; break; + case FIO_CLI_ARG_INT: v = RB_LL2NUM(fio_atol(&val.buf)); break; + default: v = rb_str_new(val.buf, val.len); break; + } + STORE.hold(v); + if (name.buf) { + at = 0; + while (name.buf[0] == '-') { + ++name.buf; + --name.len; + } + n = rb_str_new(name.buf, name.len); + STORE.hold(n); + } else { + n = RB_INT2FIX(at); + ++at; + } + rb_hash_aset(h, n, v); + if (RB_TYPE_P(n, RUBY_T_STRING)) { + tmp = rb_str_intern(n); + STORE.release(n); + STORE.hold((n = tmp)); + rb_hash_aset(h, n, v); + STORE.release(n); + } + STORE.release(v); + return 0; +} + +/* ***************************************************************************** +Ruby Public API. +***************************************************************************** */ + +/** Read CLI as required data. */ +static VALUE iodine_cli_parse(VALUE self, VALUE required) { + if (!RB_TYPE_P(rb_argv, RUBY_T_ARRAY)) + rb_raise(rb_eException, "ARGV should be an Array!"); + FIO_STR_INFO_TMP_VAR(desc, 2048); + FIO_STR_INFO_TMP_VAR(threads, 128); + FIO_STR_INFO_TMP_VAR(workers, 128); + const char *argv[IODINE_CLI_LIMIT]; + long len = 0; + VALUE iodine_version = rb_const_get(iodine_rb_IODINE, rb_intern("VERSION")); + + /* I don't know if Ruby promises a NUL separator, but fio_bstr does. */ + if (rb_argv0 && RB_TYPE_P(rb_argv0, RUBY_T_STRING)) { + argv[len++] = + fio_bstr_write(NULL, RSTRING_PTR(rb_argv0), RSTRING_LEN(rb_argv0)); + } else + argv[len++] = (const char *)"iodine"; + for (long i = 0; len < IODINE_CLI_LIMIT && i < rb_array_len(rb_argv); ++i) { + VALUE o = rb_ary_entry(rb_argv, i); + if (!RB_TYPE_P(o, RUBY_T_STRING)) { + FIO_LOG_WARNING("ARGV member skipped - not a String!"); + continue; + } + argv[len++] = fio_bstr_write(NULL, RSTRING_PTR(o), RSTRING_LEN(o)); + } + + /* in case of `-h` or error, make sure we clean up */ + for (long i = 0; i < len; ++i) + fio_state_callback_add(FIO_CALL_AT_EXIT, + (void (*)(void *))fio_bstr_free, + (void *)argv[i]); + + fio_string_write2( + &threads, + NULL, + FIO_STRING_WRITE_STR1("--threads -t ("), + (getenv("THREADS") ? FIO_STRING_WRITE_STR1(getenv("THREADS")) + : FIO_STRING_WRITE_STR1("-4")), + FIO_STRING_WRITE_STR1(") number of worker threads to use.")); + fio_string_write2( + &workers, + NULL, + FIO_STRING_WRITE_STR1("--workers -w ("), + (getenv("WORKERS") ? FIO_STRING_WRITE_STR1(getenv("WORKERS")) + : FIO_STRING_WRITE_STR1("-2")), + FIO_STRING_WRITE_STR1(") number of worker processes to use.")); + + fio_string_write2( + &desc, + NULL, + FIO_STRING_WRITE_STR1("Iodine's (" FIO_POLL_ENGINE_STR + ") HTTP/WebSocket server version "), + FIO_STRING_WRITE_STR2(RSTRING_PTR(iodine_version), + (size_t)RSTRING_LEN(iodine_version)), + FIO_STRING_WRITE_STR1( + "\r\n\r\nUse:\r\n iodine \r\n\r\n" + "Both and are optional. i.e.,:\r\n" + " iodine -p 0 -b /tmp/my_unix_sock\r\n" + " iodine -p 8080 path/to/app/conf.ru\r\n" + " iodine -p 8080 -w 4 -t 16\r\n" + " iodine -w -1 -t 4 -r redis://usr:pass@localhost:6379/")); + + fio_cli_end(); + fio_cli_start( + (int)len, + (const char **)argv, + 0, + ((required == Qnil || required == Qfalse) ? -1 : 1), + desc.buf, + FIO_CLI_PRINT_HEADER("Address Binding"), + FIO_CLI_PRINT_LINE( + "NOTE: also controlled by the ADDRESS or PORT environment vars."), + FIO_CLI_STRING( + "-bind -b address to listen to in URL format (MAY include PORT)."), + FIO_CLI_PRINT( + "It's possible to add TLS/SSL data to the binding URL. i.e.:"), + FIO_CLI_PRINT("\t iodine -b https://0.0.0.0/tls=./cert_path/"), + FIO_CLI_PRINT( + "\t iodine -b https://0.0.0.0/key=./key.pem&cert=./cert.pem"), + FIO_CLI_INT("-port -p default port number to listen to."), + FIO_CLI_PRINT( + "Note: these are optional and supersede previous instructions."), + + FIO_CLI_PRINT_HEADER("Concurrency"), + FIO_CLI_INT(threads.buf), + FIO_CLI_INT(workers.buf), + + FIO_CLI_PRINT_HEADER("HTTP"), + FIO_CLI_STRING("--public -www public folder for static file service."), + FIO_CLI_INT("--max-line -maxln per-header line limit, in Kb."), + FIO_CLI_INT("--max-header -maxhd total header limit per request, in Kb."), + FIO_CLI_INT( + "--max-body -maxbd total message payload limit per request, in Mb."), + FIO_CLI_INT("--keep-alive -k (" FIO_MACRO2STR( + FIO_HTTP_DEFAULT_TIMEOUT) ") HTTP keep-alive timeout in seconds " + "(0..255)"), + FIO_CLI_INT("--max-age -maxage (3600) default Max-Age header value for " + "static files."), + FIO_CLI_BOOL("--log -v log HTTP messages."), + + FIO_CLI_PRINT_HEADER("WebSocket / SSE"), + FIO_CLI_INT( + "--ws-max-msg -maxms incoming WebSocket message limit, in Kb."), + FIO_CLI_INT("--timeout -ping WebSocket / SSE timeout, in seconds."), + + FIO_CLI_PRINT_HEADER("TLS / SSL"), + FIO_CLI_PRINT( + "NOTE: crashes if no crypto library implementation is found."), + FIO_CLI_BOOL( + "--tls-self -tls uses SSL/TLS with a self signed certificate."), + FIO_CLI_STRING("--tls-name -name The host name for the SSL/TLS " + "certificate (if any)."), + FIO_CLI_STRING("--tls-cert -cert The SSL/TLS certificate .pem file."), + FIO_CLI_STRING("--tls-key -key The SSL/TLS private key .pem file."), + FIO_CLI_STRING( + "--tls-password -tls-pass The SSL/TLS password for the private key."), + + FIO_CLI_PRINT_HEADER("Clustering Pub/Sub"), + FIO_CLI_INT("--broadcast -bp Cluster Broadcast Port."), + FIO_CLI_STRING("--secret -scrt Cluster Secret."), + FIO_CLI_PRINT("NOTE: also controlled by the SECRET and SECRET_LENGTH " + "environment vars."), + + FIO_CLI_PRINT_HEADER("Connecting Iodine to Redis:"), + FIO_CLI_STRING( + "--redis -r an optional Redis URL server address. Default: none."), + FIO_CLI_INT("--redis-ping -rp Redis ping interval in seconds."), + + FIO_CLI_PRINT_HEADER("Misc"), + FIO_CLI_BOOL("--verbose -V -d print out debugging messages."), + FIO_CLI_BOOL("--rack -R -rack prefer Rack::Builder over NeoRack."), + FIO_CLI_STRING("--config -C configuration file to be loaded."), + FIO_CLI_STRING( + "--pid -pidfile -pid name for the pid file to be created."), + FIO_CLI_BOOL( + "--preload -warmup warm up the application. CAREFUL! with workers."), + FIO_CLI_BOOL( + "--contained attempts to handle possible container restrictions."), + FIO_CLI_PRINT( + "Containers sometimes impose file-system restrictions, i.e.,"), + FIO_CLI_PRINT("the IPC Unix Socket might need to be placed in `/tmp`.")); + + /* review CLI for logging */ + if (fio_cli_get_bool("-V")) { + FIO_LOG_LEVEL = FIO_LOG_LEVEL_DEBUG; + } + + if (fio_cli_get_bool("--contained")) { /* container - IPC url in tmp */ + char *u = (char *)fio_pubsub_ipc_url(); + memcpy((void *)(u + 7), "/tmp/", 5); + } + + /* Clustering */ + if (fio_cli_get_i("-bp") > 0) { + fio_buf_info_s scrt = FIO_BUF_INFO1((char *)fio_cli_get("-scrt")); + fio_pubsub_secret_set(scrt.buf, scrt.len); + fio_pubsub_broadcast_on_port(fio_cli_get_i("-bp")); + } + + /* support -b and -p for when a URL isn't provided */ + if (fio_cli_get("-p")) { + fio_buf_info_s tmp = fio_cli_get_str("-b"); + if (tmp.buf) { + FIO_STR_INFO_TMP_VAR(url, 2048); + FIO_ASSERT(tmp.len < 2000, "binding address / url too long."); + fio_url_s u = fio_url_parse(tmp.buf, tmp.len); + tmp.buf = (char *)fio_cli_get("-p"); + tmp.len = strlen(tmp.buf); + FIO_ASSERT(tmp.len < 6, "port number too long."); + fio_string_write2(&url, + NULL, + FIO_STRING_WRITE_STR2(u.scheme.buf, u.scheme.len), + (u.scheme.len ? FIO_STRING_WRITE_STR2("://", 3) + : FIO_STRING_WRITE_STR2(NULL, 0)), + FIO_STRING_WRITE_STR2(u.host.buf, u.host.len), + FIO_STRING_WRITE_STR2(":", 1), + FIO_STRING_WRITE_STR2(tmp.buf, tmp.len), + (u.query.len ? FIO_STRING_WRITE_STR2("?", 1) + : FIO_STRING_WRITE_STR2(NULL, 0)), + FIO_STRING_WRITE_STR2(u.query.buf, u.query.len)); + fio_cli_set("-b", url.buf); + } else { +#if FIO_OS_WIN + SetEnvironmentVariable("PORT", fio_cli_get("-p")); +#else + setenv("PORT", fio_cli_get("-p"), 1); +#endif + } + } + + /* Save data to Hash and return it... why? I don't know. */ + VALUE h = rb_hash_new(); + STORE.hold(h); + fio_cli_each(iodine_cli_task, (void *)h); + /* cleanup */ + for (long i = 0; i < len; ++i) { + fio_state_callback_remove(FIO_CALL_AT_EXIT, + (void (*)(void *))fio_bstr_free, + (void *)argv[i]); + fio_bstr_free((char *)argv[i]); + } + STORE.release(h); + return h; +} + +static VALUE iodine_cli_get(VALUE self, VALUE key) { + VALUE r = Qnil; + fio_buf_info_s val; + int64_t ival = 0; + char *tmp; + if (RB_TYPE_P(key, RUBY_T_FIXNUM)) { + val = fio_cli_unnamed_str(NUM2UINT(key)); + r = rb_str_new(val.buf, val.len); + return r; + } + if (RB_TYPE_P(key, RUBY_T_SYMBOL)) + key = rb_sym2str(key); + if (!RB_TYPE_P(key, RUBY_T_STRING)) + rb_raise(rb_eArgError, + "key should be either an Integer, a String or a Symbol"); + val = fio_cli_get_str(RSTRING_PTR(key)); + if (!val.len) + return r; + tmp = val.buf; + ival = fio_atol(&tmp); + if (tmp == val.buf + val.len) + r = LL2NUM(ival); + else + r = rb_str_new(val.buf, val.len); + return r; +} + +static VALUE iodine_cli_set(VALUE self, VALUE key, VALUE value) { + if (fio_io_is_running() || !fio_io_is_master()) + rb_raise(rb_eException, + "Setting CLI arguments can only be performed before Iodine.start " + "and in the master process."); + if (RB_TYPE_P(key, RUBY_T_FIXNUM)) { + if (!RB_TYPE_P(value, RUBY_T_STRING)) + rb_raise(rb_eArgError, + "value for an indexed CLI argument should be a String"); + fio_cli_set_unnamed(NUM2UINT(key), RSTRING_PTR(value)); + return value; + } + if (RB_TYPE_P(key, RUBY_T_SYMBOL)) + key = rb_sym2str(key); + if (!RB_TYPE_P(key, RUBY_T_STRING)) + rb_raise(rb_eArgError, + "key should be either an Integer, a String or a Symbol"); + fio_cli_arg_e t = fio_cli_type(RSTRING_PTR(key)); + if (t == FIO_CLI_ARG_INT || t == FIO_CLI_ARG_STRING) { + if (!RB_TYPE_P(value, RUBY_T_FIXNUM)) + rb_raise(rb_eArgError, + "value for %s should be an Integer", + RSTRING_PTR(key)); + fio_cli_set_i(RSTRING_PTR(key), NUM2LL(value)); + } else { + if (!RB_TYPE_P(value, RUBY_T_STRING)) + rb_raise(rb_eArgError, + "value for %s should be a String", + RSTRING_PTR(key)); + fio_cli_set(RSTRING_PTR(key), RSTRING_PTR(value)); + } + return value; +} + +/** Initialize Iodine::Base::CLI */ +/** + * The Iodine::Base::CLI module is used internally to manage CLI options. + */ +static void Init_Iodine_Base_CLI(void) { + VALUE cli = rb_define_module_under(iodine_rb_IODINE_BASE, "CLI"); + rb_define_singleton_method(cli, "parse", iodine_cli_parse, 1); + rb_define_singleton_method(cli, "[]", iodine_cli_get, 1); + rb_define_singleton_method(cli, "[]=", iodine_cli_set, 2); + iodine_cli_parse(cli, Qfalse); +} +#endif /* H___IODINE_CLI___H */ diff --git a/ext/iodine/iodine_connection.c b/ext/iodine/iodine_connection.c deleted file mode 100644 index ced441c7..00000000 --- a/ext/iodine/iodine_connection.c +++ /dev/null @@ -1,941 +0,0 @@ -#include "iodine_connection.h" - -#define FIO_INCLUDE_LINKED_LIST -#define FIO_INCLUDE_STR -#include "fio.h" - -#include "fiobj4fio.h" -#include "websockets.h" - -#include - -/* ***************************************************************************** -Constants in use -***************************************************************************** */ - -static ID new_id; -static ID call_id; -static ID to_id; -static ID channel_id; -static ID as_id; -static ID binary_id; -static ID match_id; -static ID redis_id; -static ID handler_id; -static ID engine_id; -static ID message_id; -static ID on_open_id; -static ID on_message_id; -static ID on_drained_id; -static ID ping_id; -static ID on_shutdown_id; -static ID on_close_id; -static VALUE ConnectionKlass; -static rb_encoding *IodineUTF8Encoding; -static VALUE WebSocketSymbol; -static VALUE SSESymbol; -static VALUE RAWSymbol; - -/* ***************************************************************************** -Pub/Sub storage -***************************************************************************** */ - -#define FIO_SET_NAME fio_subhash -#define FIO_SET_OBJ_TYPE subscription_s * -#define FIO_SET_KEY_TYPE fio_str_info_s -#define FIO_SET_KEY_COMPARE(s1, s2) \ - ((s1).len == (s2).len && \ - ((s1).data == (s2).data || !memcmp((s1).data, (s2).data, (s1).len))) -#define FIO_SET_OBJ_DESTROY(obj) fio_unsubscribe((obj)) -#include // creates the fio_str_set_s Set and functions - -static inline VALUE iodine_sub_unsubscribe(fio_subhash_s *store, - fio_str_info_s channel) { - if (fio_subhash_remove(store, fiobj_hash_string(channel.data, channel.len), - channel, NULL)) - return Qfalse; - return Qtrue; -} -static inline void iodine_sub_add(fio_subhash_s *store, subscription_s *sub) { - fio_str_info_s ch = fio_subscription_channel(sub); - fio_subhash_insert(store, fiobj_hash_string(ch.data, ch.len), ch, sub, NULL); -} -static inline void iodine_sub_clear_all(fio_subhash_s *store) { - fio_subhash_free(store); -} - -static fio_lock_i sub_lock = FIO_LOCK_INIT; -static fio_subhash_s sub_global = FIO_SET_INIT; - -/* ***************************************************************************** -C <=> Ruby Data allocation -***************************************************************************** */ - -typedef struct { - iodine_connection_s info; - size_t ref; - fio_subhash_s subscriptions; - fio_lock_i lock; - uint8_t answers_on_message; - uint8_t answers_on_drained; - uint8_t answers_ping; - /* these are one-shot, but the CPU cache might have the data, so set it */ - uint8_t answers_on_open; - uint8_t answers_on_shutdown; - uint8_t answers_on_close; -} iodine_connection_data_s; - -/** frees an iodine_connection_data_s object*/ - -/* a callback for the GC (marking active objects) */ -static void iodine_connection_data_mark(void *c_) { - iodine_connection_data_s *c = c_; - if (!c) { - return; - } - if (c->info.handler && c->info.handler != Qnil) { - rb_gc_mark(c->info.handler); - } - if (c->info.env && c->info.env != Qnil) { - rb_gc_mark(c->info.env); - } -} -/* a callback for the GC (freeing inactive objects) */ -static void iodine_connection_data_free(void *c_) { - iodine_connection_data_s *data = c_; - if (fio_atomic_sub(&data->ref, 1)) - return; - free(data); -} - -static size_t iodine_connection_data_size(const void *c_) { - return sizeof(iodine_connection_data_s); - (void)c_; -} - -const rb_data_type_t iodine_connection_data_type = { - .wrap_struct_name = "IodineConnectionData", - .function = - { - .dmark = iodine_connection_data_mark, - .dfree = iodine_connection_data_free, - .dsize = iodine_connection_data_size, - }, - .data = NULL, - // .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -/* Iodine::PubSub::Engine.allocate */ -static VALUE iodine_connection_data_alloc_c(VALUE self) { - iodine_connection_data_s *c = malloc(sizeof(*c)); - *c = (iodine_connection_data_s){ - .info.handler = (VALUE)0, - .info.uuid = -1, - .ref = 1, - .subscriptions = FIO_SET_INIT, - .lock = FIO_LOCK_INIT, - }; - return TypedData_Wrap_Struct(self, &iodine_connection_data_type, c); -} - -static inline iodine_connection_data_s *iodine_connection_ruby2C(VALUE self) { - iodine_connection_data_s *c = NULL; - TypedData_Get_Struct(self, iodine_connection_data_s, - &iodine_connection_data_type, c); - return c; -} - -static inline iodine_connection_data_s * -iodine_connection_validate_data(VALUE self) { - iodine_connection_data_s *c = iodine_connection_ruby2C(self); - if (c == NULL || c->info.handler == Qnil || c->info.uuid == -1) { - return NULL; - } - return c; -} - -/* ***************************************************************************** -Ruby Connection Methods - write, close open? pending -***************************************************************************** */ - -/** - * Writes data to the connection asynchronously. `data` MUST be a String. - * - * In effect, the `write` call does nothing, it only schedules the data to be - * sent and marks the data as pending. - * - * Use {pending} to test how many `write` operations are pending completion - * (`on_drained(client)` will be called when they complete). - */ -static VALUE iodine_connection_write(VALUE self, VALUE data) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (!c || fio_is_closed(c->info.uuid)) { - // don't throw exceptions - closed connections are unavoidable. - return Qnil; - // rb_raise(rb_eIOError, "Connection closed or invalid."); - } - if (!RB_TYPE_P(data, T_STRING)) { - VALUE tmp = data; - data = IodineCaller.call(data, iodine_to_s_id); - if (!RB_TYPE_P(data, T_STRING)) - Check_Type(tmp, T_STRING); - rb_backtrace(); - FIO_LOG_WARNING( - "`Iodine::Connection#write` was called with a non-String object."); - } - - switch (c->info.type) { - case IODINE_CONNECTION_WEBSOCKET: - /* WebSockets*/ - websocket_write(c->info.arg, IODINE_RSTRINFO(data), - rb_enc_get(data) == IodineUTF8Encoding); - return Qtrue; - break; - case IODINE_CONNECTION_SSE: -/* SSE */ -#if 1 - http_sse_write(c->info.arg, .data = IODINE_RSTRINFO(data)); - return Qtrue; -#else - if (rb_enc_get(data) == IodineUTF8Encoding) { - http_sse_write(c->info.arg, .data = {.data = RSTRING_PTR(data), - .len = RSTRING_LEN(data)}); - return Qtrue; - } - fprintf(stderr, "WARNING: ignoring an attept to write binary data to " - "non-binary protocol (SSE).\n"); - return Qfalse; -// rb_raise(rb_eEncodingError, -// "This Connection type requires data to be UTF-8 encoded."); -#endif - break; - case IODINE_CONNECTION_RAW: /* fallthrough */ - default: { - fio_write(c->info.uuid, RSTRING_PTR(data), RSTRING_LEN(data)); - return Qtrue; - } break; - } - return Qnil; -} - -/** - * Schedules the connection to be closed. - * - * The connection will be closed once all the scheduled `write` operations have - * been completed (or failed). - */ -static VALUE iodine_connection_close(VALUE self) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c && !fio_is_closed(c->info.uuid)) { - if (c->info.type == IODINE_CONNECTION_WEBSOCKET) { - websocket_close(c->info.arg); /* sends WebSocket close packet */ - } else { - fio_close(c->info.uuid); - } - } - - return Qnil; -} -/** Returns true if the connection appears to be open (no known issues). */ -static VALUE iodine_connection_is_open(VALUE self) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c && !fio_is_closed(c->info.uuid)) { - return Qtrue; - } - return Qfalse; -} - -/** - * Always returns true, since Iodine connections support the pub/sub extension. - */ -static VALUE iodine_connection_is_pubsub(VALUE self) { - return INT2NUM(0); - (void)self; -} -/** - * Returns the number of pending `write` operations that need to complete - * before the next `on_drained` callback is called. - * - * Returns -1 if the connection is closed and 0 if `on_drained` won't be - * scheduled (no pending `write`). - */ -static VALUE iodine_connection_pending(VALUE self) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (!c || fio_is_closed(c->info.uuid)) { - return INT2NUM(-1); - } - return SIZET2NUM((fio_pending(c->info.uuid))); -} - -// clang-format off -/** - * Returns the connection's protocol Symbol (`:sse`, `:websocket` or `:raw`). - * - * @Note For compatibility reasons (with ther `rack.upgrade` servers), it might be more prudent to use the data in the {#env} (`env['rack.upgrade?']`). However, this method is provided both as a faster alternative and for those cases where Raw / Custom (TCP/IP) data stream is a valid option. -*/ -static VALUE iodine_connection_protocol_name(VALUE self) { - // clang-format on - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c) { - switch (c->info.type) { - case IODINE_CONNECTION_WEBSOCKET: - return WebSocketSymbol; - break; - case IODINE_CONNECTION_SSE: - return SSESymbol; - break; - case IODINE_CONNECTION_RAW: /* fallthrough */ - return RAWSymbol; - break; - } - } - return Qnil; -} - -/** - * Returns the timeout / `ping` interval for the connection. - * - * Returns nil on error. - */ -static VALUE iodine_connection_timeout_get(VALUE self) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c && !fio_is_closed(c->info.uuid)) { - size_t tout = (size_t)fio_timeout_get(c->info.uuid); - return SIZET2NUM(tout); - } - return Qnil; -} - -/** - * Sets the timeout / `ping` interval for the connection (up to 255 seconds). - * - * Returns nil on error. - */ -static VALUE iodine_connection_timeout_set(VALUE self, VALUE timeout) { - Check_Type(timeout, T_FIXNUM); - int tout = NUM2INT(timeout); - if (tout < 0 || tout > 255) { - rb_raise(rb_eRangeError, "timeout out of range."); - return Qnil; - } - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c && !fio_is_closed(c->info.uuid)) { - fio_timeout_set(c->info.uuid, (uint8_t)tout); - return timeout; - } - return Qnil; -} - -/** - * Returns the connection's `env` (if it originated from an HTTP request). - */ -static VALUE iodine_connection_env(VALUE self) { - iodine_connection_data_s *c = iodine_connection_validate_data(self); - if (c && c->info.env) { - return c->info.env; - } - return Qnil; -} - -/** - * Returns the client's current callback object. - */ -static VALUE iodine_connection_handler_get(VALUE self) { - iodine_connection_data_s *data = iodine_connection_validate_data(self); - if (!data) { - FIO_LOG_DEBUG("(iodine) requested connection handler for " - "an invalid connection: %p", - (void *)self); - return Qnil; - } - return data->info.handler; -} - -// clang-format off -/** - * Sets the client's callback object, so future events will use the new object's callbacks. - * - * @Note this will fire the `on_close` callback in the old handler and the `on_open` callback on the new handler. However, existing subscriptions will remain intact. - */ -static VALUE iodine_connection_handler_set(VALUE self, VALUE handler) { - // clang-format on - iodine_connection_data_s *data = iodine_connection_validate_data(self); - if (!data) { - FIO_LOG_DEBUG("(iodine) attempted to set a connection handler for " - "an invalid connection: %p", - (void *)self); - return Qnil; - } - if (handler == Qnil || handler == Qfalse) { - FIO_LOG_DEBUG( - "(iodine) called client.handler = nil, closing connection: %p", - (void *)self); - iodine_connection_close(self); - return Qnil; - } - if (data->info.handler != handler) { - uint8_t answers_on_open = (rb_respond_to(handler, on_open_id) != 0); - if (data->answers_on_close) - IodineCaller.call2(data->info.handler, on_close_id, 1, &self); - fio_lock(&data->lock); - data->info.handler = handler; - data->answers_on_open = answers_on_open, - data->answers_on_message = (rb_respond_to(handler, on_message_id) != 0), - data->answers_ping = (rb_respond_to(handler, ping_id) != 0), - data->answers_on_drained = (rb_respond_to(handler, on_drained_id) != 0), - data->answers_on_shutdown = (rb_respond_to(handler, on_shutdown_id) != 0), - data->answers_on_close = (rb_respond_to(handler, on_close_id) != 0), - fio_unlock(&data->lock); - if (answers_on_open) { - iodine_connection_fire_event(self, IODINE_CONNECTION_ON_OPEN, Qnil); - } - FIO_LOG_DEBUG("(iodine) switched handlers for connection: %p", - (void *)self); - } - return handler; -} -/* ***************************************************************************** -Pub/Sub Callbacks (internal implementation) -***************************************************************************** */ - -/* calls the Ruby block assigned to a pubsub event (within the GVL). */ -static void *iodine_on_pubsub_call_block(void *msg_) { - fio_msg_s *msg = msg_; - VALUE args[2]; - args[0] = rb_str_new(msg->channel.data, msg->channel.len); - IodineStore.add(args[0]); - args[1] = rb_str_new(msg->msg.data, msg->msg.len); - IodineStore.add(args[1]); - IodineCaller.call2((VALUE)msg->udata2, call_id, 2, args); - IodineStore.remove(args[1]); - IodineStore.remove(args[0]); - return NULL; -} - -/* callback for incoming subscription messages */ -static void iodine_on_pubsub(fio_msg_s *msg) { - iodine_connection_data_s *data = msg->udata1; - VALUE block = (VALUE)msg->udata2; - switch (block) { - case 0: /* fallthrough */ - case Qnil: /* fallthrough */ - case Qtrue: { /* Qtrue == binary WebSocket */ - if (!data) { - FIO_LOG_ERROR("Pub/Sub direct called with no connection data!"); - return; - } - if (data->info.handler == Qnil || data->info.uuid == -1 || - fio_is_closed(data->info.uuid)) - return; - switch (data->info.type) { - case IODINE_CONNECTION_WEBSOCKET: { - FIOBJ s = (FIOBJ)fio_message_metadata( - msg, (block == Qnil ? WEBSOCKET_OPTIMIZE_PUBSUB - : WEBSOCKET_OPTIMIZE_PUBSUB_BINARY)); - if (s) { - // fwrite(".", 1, 1, stderr); - fiobj_send_free(data->info.uuid, fiobj_dup(s)); - } else { - fwrite("-", 1, 1, stderr); - websocket_write(data->info.arg, msg->msg, (block == Qnil)); - } - return; - } - case IODINE_CONNECTION_SSE: - http_sse_write(data->info.arg, .data = msg->msg); - return; - default: - fio_write(data->info.uuid, msg->msg.data, msg->msg.len); - return; - } - } - default: - if (data && data->info.uuid != -1) { - fio_protocol_s *pr = - fio_protocol_try_lock(data->info.uuid, FIO_PR_LOCK_TASK); - if (!pr) { - // perror("Connection lock failed"); - if (errno != EBADF) - fio_message_defer(msg); - break; - } - IodineCaller.enterGVL(iodine_on_pubsub_call_block, msg); - fio_protocol_unlock(pr, FIO_PR_LOCK_TASK); - } else { - IodineCaller.enterGVL(iodine_on_pubsub_call_block, msg); - } - break; - } -} - -/* callback for subscription closure */ -static void iodine_on_unsubscribe(void *udata1, void *udata2) { - iodine_connection_data_s *data = udata1; - VALUE block = (VALUE)udata2; - switch (block) { - case Qnil: - if (data && data->info.type == IODINE_CONNECTION_WEBSOCKET) { - websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB, 0); - } - break; - case Qtrue: - if (data && data->info.type == IODINE_CONNECTION_WEBSOCKET) { - websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_BINARY, 0); - } - break; - default: - IodineStore.remove(block); - break; - } - if (data) { - iodine_connection_data_free(data); - } -} - -/* ***************************************************************************** -Ruby Connection Methods - Pub/Sub -***************************************************************************** */ - -typedef struct { - VALUE channel_org; - VALUE channel; - VALUE block; - fio_match_fn pattern; - uint8_t binary; -} iodine_sub_args_s; - -/** Tests the `subscribe` Ruby arguments */ -static iodine_sub_args_s iodine_subscribe_args(int argc, VALUE *argv) { - - iodine_sub_args_s ret = {.channel_org = Qnil, .channel = Qnil, .block = Qnil}; - VALUE rb_opt = 0; - - switch (argc) { - case 2: - ret.channel = argv[0]; - rb_opt = argv[1]; - break; - case 1: - /* single argument must be a Hash / channel name */ - if (TYPE(argv[0]) == T_HASH) { - rb_opt = argv[0]; - ret.channel = rb_hash_aref(argv[0], ID2SYM(to_id)); - if (ret.channel == Qnil || ret.channel == Qfalse) { - /* temporary backport support */ - ret.channel = rb_hash_aref(argv[0], ID2SYM(channel_id)); - if (ret.channel != Qnil) { - FIO_LOG_WARNING("use of :channel in subscribe is deprecated."); - } - } - } else { - ret.channel = argv[0]; - } - break; - default: - rb_raise(rb_eArgError, "method accepts 1 or 2 arguments."); - return ret; - } - ret.channel_org = ret.channel; - if (ret.channel == Qnil || ret.channel == Qfalse) { - rb_raise(rb_eArgError, - "a target (:to) subject / stream / channel is required."); - return ret; - } - - if (TYPE(ret.channel) == T_SYMBOL) - ret.channel = rb_sym2str(ret.channel); - Check_Type(ret.channel, T_STRING); - - if (rb_opt) { - VALUE tmp = Qnil; - if ((tmp = rb_hash_aref(rb_opt, ID2SYM(as_id))) != Qnil && - TYPE(tmp) == T_SYMBOL && rb_sym2id(tmp) == binary_id) { - ret.binary = 1; - } - if ((tmp = rb_hash_aref(rb_opt, ID2SYM(match_id))) != Qnil && - TYPE(tmp) == T_SYMBOL && rb_sym2id(tmp) == redis_id) { - ret.pattern = FIO_MATCH_GLOB; - } - ret.block = rb_hash_aref(rb_opt, ID2SYM(handler_id)); - if (ret.block != Qnil) { - IodineStore.add(ret.block); - } - } - - if (ret.block == Qnil) { - if (rb_block_given_p()) { - ret.block = rb_block_proc(); - IodineStore.add(ret.block); - } - } - return ret; -} - -// clang-format off -/** -Subscribes to a Pub/Sub stream / channel or replaces an existing subscription. - -The method accepts 1-2 arguments and an optional block. These are all valid ways -to call the method: - - subscribe("my_stream") {|source, msg| p msg } - subscribe("my_strea*", match: :redis) {|source, msg| p msg } - subscribe(to: "my_stream") {|source, msg| p msg } - # or use any object that answers `#call(source, msg)` - MyProc = Proc.new {|source, msg| p msg } - subscribe to: "my_stream", match: :redis, handler: MyProc - -The first argument must be either a String or a Hash. - -The second, optional, argument must be a Hash (if given). - -The options Hash supports the following possible keys (other keys are ignored, all keys are Symbols): - -- `:match` - The channel / subject name matching type to be used. Valid value is: `:redis`. Future versions hope to support `:nats` and `:rabbit` patern matching as well. -- `:to` - The channel / subject to subscribe to. -- `:as` - (only for WebSocket connections) accepts the optional value `:binary`. default is `:text`. Note that binary transmissions are illegal for some connections (such as SSE) and an attempted binary subscription will fail for these connections. -- `:handler` - Any object that answers `.call(source, msg)` where source is the stream / channel name. - -Note: if an existing subscription with the same name exists, it will be replaced by this new subscription. - -Returns the name of the subscription, which matches the name be used in {unsubscribe} (or nil on failure). - -*/ -static VALUE iodine_pubsub_subscribe(int argc, VALUE *argv, VALUE self) { - // clang-format on - iodine_sub_args_s args = iodine_subscribe_args(argc, argv); - if (args.channel == Qnil) { - return Qnil; - } - iodine_connection_data_s *c = NULL; - if (TYPE(self) == T_MODULE) { - if (!args.block) { - rb_raise(rb_eArgError, - "block or :handler required for local subscriptions."); - } - } else { - c = iodine_connection_validate_data(self); - if (!c || (c->info.type == IODINE_CONNECTION_SSE && args.binary)) { - if (args.block) { - IodineStore.remove(args.block); - } - return Qnil; /* cannot subscribe a closed / invalid connection. */ - } - if (args.block == Qnil) { - if (c->info.type == IODINE_CONNECTION_WEBSOCKET) - websocket_optimize4broadcasts((args.binary - ? WEBSOCKET_OPTIMIZE_PUBSUB_BINARY - : WEBSOCKET_OPTIMIZE_PUBSUB), - 1); - if (args.binary) { - args.block = Qtrue; - } - } - fio_atomic_add(&c->ref, 1); - } - - subscription_s *sub = - fio_subscribe(.channel = IODINE_RSTRINFO(args.channel), - .on_message = iodine_on_pubsub, - .on_unsubscribe = iodine_on_unsubscribe, .udata1 = c, - .udata2 = (void *)args.block, .match = args.pattern); - if (c) { - fio_lock(&c->lock); - if (c->info.uuid == -1) { - fio_unsubscribe(sub); - fio_unlock(&c->lock); - return Qnil; - } - iodine_sub_add(&c->subscriptions, sub); - fio_unlock(&c->lock); - } else { - fio_lock(&sub_lock); - iodine_sub_add(&sub_global, sub); - fio_unlock(&sub_lock); - } - return args.channel_org; -} - -// clang-format off -/** -Unsubscribes from a Pub/Sub stream / channel. - -The method accepts a single arguments, the name used for the subscription. i.e.: - - subscribe("my_stream") {|source, msg| p msg } - unsubscribe("my_stream") - -Returns `true` if the subscription was found. - -Returns `false` if the subscription didn't exist. -*/ -static VALUE iodine_pubsub_unsubscribe(VALUE self, VALUE name) { - // clang-format on - iodine_connection_data_s *c = NULL; - fio_lock_i *s_lock = &sub_lock; - fio_subhash_s *subs = &sub_global; - VALUE ret; - if (TYPE(self) != T_MODULE) { - c = iodine_connection_validate_data(self); - if (!c || c->info.uuid == -1) { - return Qnil; /* cannot unsubscribe a closed connection. */ - } - s_lock = &c->lock; - subs = &c->subscriptions; - } - if (TYPE(name) == T_SYMBOL) - name = rb_sym2str(name); - Check_Type(name, T_STRING); - fio_lock(s_lock); - ret = iodine_sub_unsubscribe(subs, IODINE_RSTRINFO(name)); - fio_unlock(s_lock); - return ret; -} - -// clang-format off -/** -Publishes a message to a channel. - -Can be used using two Strings: - - publish(to, message) - -The method accepts an optional `engine` argument: - - publish(to, message, my_pubsub_engine) - -*/ -static VALUE iodine_pubsub_publish(int argc, VALUE *argv, VALUE self) { - // clang-format on - VALUE rb_ch, rb_msg, rb_engine = Qnil; - const fio_pubsub_engine_s *engine = NULL; - switch (argc) { - case 3: - /* fallthrough */ - rb_engine = argv[2]; - case 2: - rb_ch = argv[0]; - rb_msg = argv[1]; - break; - case 1: { - /* single argument must be a Hash */ - Check_Type(argv[0], T_HASH); - rb_ch = rb_hash_aref(argv[0], to_id); - if (rb_ch == Qnil || rb_ch == Qfalse) { - rb_ch = rb_hash_aref(argv[0], channel_id); - } - rb_msg = rb_hash_aref(argv[0], message_id); - rb_engine = rb_hash_aref(argv[0], engine_id); - } break; - default: - rb_raise(rb_eArgError, "method accepts 1-3 arguments."); - } - - if (rb_msg == Qnil || rb_msg == Qfalse) { - rb_raise(rb_eArgError, "message is required."); - } - Check_Type(rb_msg, T_STRING); - - if (rb_ch == Qnil || rb_ch == Qfalse) - rb_raise(rb_eArgError, "target / channel is required ."); - if (TYPE(rb_ch) == T_SYMBOL) - rb_ch = rb_sym2str(rb_ch); - Check_Type(rb_ch, T_STRING); - - if (rb_engine == Qfalse) { - engine = FIO_PUBSUB_PROCESS; - } else if (rb_engine != Qnil) { - // collect engine object - iodine_pubsub_s *e = iodine_pubsub_CData(rb_engine); - if (e) { - engine = e->engine; - } - } - - fio_publish(.engine = engine, .channel = IODINE_RSTRINFO(rb_ch), - .message = IODINE_RSTRINFO(rb_msg)); - return Qtrue; - (void)self; -} - -/* ***************************************************************************** -Published C functions -***************************************************************************** */ - -#undef iodine_connection_new -VALUE iodine_connection_new(iodine_connection_s args) { - VALUE connection = IodineCaller.call(ConnectionKlass, new_id); - if (connection == Qnil) { - return Qnil; - } - IodineStore.add(connection); - iodine_connection_data_s *data = iodine_connection_ruby2C(connection); - if (data == NULL) { - FIO_LOG_ERROR("(iodine) internal error, connection object has no C data!"); - return Qnil; - } - *data = (iodine_connection_data_s){ - .info = args, - .subscriptions = FIO_SET_INIT, - .ref = 1, - .answers_on_open = (rb_respond_to(args.handler, on_open_id) != 0), - .answers_on_message = (rb_respond_to(args.handler, on_message_id) != 0), - .answers_ping = (rb_respond_to(args.handler, ping_id) != 0), - .answers_on_drained = (rb_respond_to(args.handler, on_drained_id) != 0), - .answers_on_shutdown = (rb_respond_to(args.handler, on_shutdown_id) != 0), - .answers_on_close = (rb_respond_to(args.handler, on_close_id) != 0), - .lock = FIO_LOCK_INIT, - }; - return connection; -} - -/** Fires a connection object's event */ -void iodine_connection_fire_event(VALUE connection, - iodine_connection_event_type_e ev, - VALUE msg) { - if (!connection || connection == Qnil) { - FIO_LOG_ERROR( - "(iodine) nil connection handle used by an internal API call"); - return; - } - iodine_connection_data_s *data = iodine_connection_validate_data(connection); - if (!data) { - FIO_LOG_ERROR("(iodine) invalid connection handle used by an " - "internal API call: %p", - (void *)connection); - return; - } - if (!data->info.handler || data->info.handler == Qnil) { - FIO_LOG_DEBUG("(iodine) invalid connection handler, can't fire event %d", - (int)ev); - return; - } - VALUE args[2] = {connection, msg}; - switch (ev) { - case IODINE_CONNECTION_ON_OPEN: - if (data->answers_on_open) { - IodineCaller.call2(data->info.handler, on_open_id, 1, args); - } - break; - case IODINE_CONNECTION_ON_MESSAGE: - if (data->answers_on_message) { - IodineCaller.call2(data->info.handler, on_message_id, 2, args); - } - break; - case IODINE_CONNECTION_ON_DRAINED: - if (data->answers_on_drained) { - IodineCaller.call2(data->info.handler, on_drained_id, 1, args); - } - break; - case IODINE_CONNECTION_ON_SHUTDOWN: - if (data->answers_on_shutdown) { - IodineCaller.call2(data->info.handler, on_shutdown_id, 1, args); - } - break; - case IODINE_CONNECTION_PING: - if (data->answers_ping) { - IodineCaller.call2(data->info.handler, ping_id, 1, args); - } - break; - - case IODINE_CONNECTION_ON_CLOSE: - if (data->answers_on_close) { - IodineCaller.call2(data->info.handler, on_close_id, 1, args); - } - fio_lock(&data->lock); - iodine_sub_clear_all(&data->subscriptions); - data->info.handler = Qnil; - data->info.env = Qnil; - data->info.uuid = -1; - data->info.arg = NULL; - fio_unlock(&data->lock); - IodineStore.remove(connection); - break; - default: - break; - } -} - -void iodine_connection_init(void) { - // set used constants - IodineUTF8Encoding = rb_enc_find("UTF-8"); - // used ID objects - new_id = rb_intern2("new", 3); - call_id = rb_intern2("call", 4); - - to_id = rb_intern2("to", 2); - channel_id = rb_intern2("channel", 7); - as_id = rb_intern2("as", 2); - binary_id = rb_intern2("binary", 6); - match_id = rb_intern2("match", 5); - redis_id = rb_intern2("redis", 5); - handler_id = rb_intern2("handler", 7); - engine_id = rb_intern2("engine", 6); - message_id = rb_intern2("message", 7); - on_open_id = rb_intern("on_open"); - on_message_id = rb_intern("on_message"); - on_drained_id = rb_intern("on_drained"); - on_shutdown_id = rb_intern("on_shutdown"); - on_close_id = rb_intern("on_close"); - ping_id = rb_intern("ping"); - - // globalize ID objects - if (1) { - IodineStore.add(ID2SYM(to_id)); - IodineStore.add(ID2SYM(channel_id)); - IodineStore.add(ID2SYM(as_id)); - IodineStore.add(ID2SYM(binary_id)); - IodineStore.add(ID2SYM(match_id)); - IodineStore.add(ID2SYM(redis_id)); - IodineStore.add(ID2SYM(handler_id)); - IodineStore.add(ID2SYM(engine_id)); - IodineStore.add(ID2SYM(message_id)); - IodineStore.add(ID2SYM(on_open_id)); - IodineStore.add(ID2SYM(on_message_id)); - IodineStore.add(ID2SYM(on_drained_id)); - IodineStore.add(ID2SYM(on_shutdown_id)); - IodineStore.add(ID2SYM(on_close_id)); - IodineStore.add(ID2SYM(ping_id)); - } - - // should these be globalized? - WebSocketSymbol = ID2SYM(rb_intern("websocket")); - SSESymbol = ID2SYM(rb_intern("sse")); - RAWSymbol = ID2SYM(rb_intern("raw")); - IodineStore.add(WebSocketSymbol); - IodineStore.add(SSESymbol); - IodineStore.add(RAWSymbol); - - // define the Connection Class and it's methods - ConnectionKlass = - rb_define_class_under(IodineModule, "Connection", rb_cObject); - rb_define_alloc_func(ConnectionKlass, iodine_connection_data_alloc_c); - rb_define_method(ConnectionKlass, "write", iodine_connection_write, 1); - rb_define_method(ConnectionKlass, "close", iodine_connection_close, 0); - rb_define_method(ConnectionKlass, "open?", iodine_connection_is_open, 0); - rb_define_method(ConnectionKlass, "pending", iodine_connection_pending, 0); - rb_define_method(ConnectionKlass, "protocol", iodine_connection_protocol_name, - 0); - rb_define_method(ConnectionKlass, "timeout", iodine_connection_timeout_get, - 0); - rb_define_method(ConnectionKlass, "timeout=", iodine_connection_timeout_set, - 1); - rb_define_method(ConnectionKlass, "env", iodine_connection_env, 0); - - rb_define_method(ConnectionKlass, "handler", iodine_connection_handler_get, - 0); - rb_define_method(ConnectionKlass, "handler=", iodine_connection_handler_set, - 1); - rb_define_method(ConnectionKlass, "pubsub?", iodine_connection_is_pubsub, 0); - rb_define_method(ConnectionKlass, "subscribe", iodine_pubsub_subscribe, -1); - rb_define_method(ConnectionKlass, "unsubscribe", iodine_pubsub_unsubscribe, - 1); - rb_define_method(ConnectionKlass, "publish", iodine_pubsub_publish, -1); - - // define global methods - rb_define_module_function(IodineModule, "subscribe", iodine_pubsub_subscribe, - -1); - rb_define_module_function(IodineModule, "unsubscribe", - iodine_pubsub_unsubscribe, 1); - rb_define_module_function(IodineModule, "publish", iodine_pubsub_publish, -1); -} diff --git a/ext/iodine/iodine_connection.h b/ext/iodine/iodine_connection.h index bbc725b1..579adad8 100644 --- a/ext/iodine/iodine_connection.h +++ b/ext/iodine/iodine_connection.h @@ -1,55 +1,2945 @@ -#ifndef H_IODINE_CONNECTION_H -#define H_IODINE_CONNECTION_H +#ifndef H___IODINE_CONNECTION___H +/** **************************************************************************** +Iodine::Connection +This module actually covers a LOT of the functionality of the Iodine server. + +Everything that has to do with establishing and managing connections goes here. + +***************************************************************************** */ +#define H___IODINE_CONNECTION___H #include "iodine.h" +/* ***************************************************************************** +Constants Used only by Iodine::Connection +***************************************************************************** */ +static ID IODINE_SAME_SITE_DEFAULT; +static ID IODINE_SAME_SITE_NONE; +static ID IODINE_SAME_SITE_LAX; +static ID IODINE_SAME_SITE_STRICT; + +/* ***************************************************************************** +Ruby Connection Object +***************************************************************************** */ typedef enum { - IODINE_CONNECTION_RAW, - IODINE_CONNECTION_WEBSOCKET, - IODINE_CONNECTION_SSE -} iodine_connection_type_e; + IODINE_CONNECTION_STORE_handler = 0, + IODINE_CONNECTION_STORE_env, + IODINE_CONNECTION_STORE_rack, + IODINE_CONNECTION_STORE_method, + IODINE_CONNECTION_STORE_path, + IODINE_CONNECTION_STORE_query, + IODINE_CONNECTION_STORE_version, + IODINE_CONNECTION_STORE_tmp, + IODINE_CONNECTION_STORE_FINISH, +} iodine_connection_store_e; -typedef struct { - iodine_connection_type_e type; - intptr_t uuid; - void *arg; /* holds the connection pointer (ws_s / sse_s) */ - VALUE handler; - VALUE env; +typedef enum { + IODINE_CONNECTION_HEADERS_COPIED = 1, + IODINE_CONNECTION_UPGRADE_SSE = 2, + IODINE_CONNECTION_UPGRADE_WS = 4, + IODINE_CONNECTION_UPGRADE = (2 | 4), + IODINE_CONNECTION_CLOSED = 8, + IODINE_CONNECTION_CLIENT = 16, +} iodine_connection_flags_e; + +typedef struct iodine_connection_s { + fio_io_s *io; + fio_http_s *http; + VALUE store[IODINE_CONNECTION_STORE_FINISH]; + iodine_minimap_s map; + iodine_connection_flags_e flags; } iodine_connection_s; +static size_t iodine_connection_data_size(const void *ptr_) { + iodine_connection_s *c = (iodine_connection_s *)ptr_; + return sizeof(*c) + + (iodine_hmap_count(&c->map.map) * sizeof(c->map.map.map[0])); +} + +static void iodine_connection_free(void *ptr_) { + iodine_connection_s *c = (iodine_connection_s *)ptr_; + fio_http_free(c->http); + iodine_hmap_destroy(&c->map.map); + FIO_LEAK_COUNTER_ON_FREE(iodine_connection); + ruby_xfree(c); + // fio_free(c); +} + +static void iodine_connection_mark(void *ptr_) { + iodine_connection_s *c = (iodine_connection_s *)ptr_; + for (size_t i = 0; i < IODINE_CONNECTION_STORE_FINISH; ++i) + if (!IODINE_STORE_IS_SKIP(c->store[i])) + rb_gc_mark(c->store[i]); + iodine_minimap_gc_mark(&c->map); +} + +static const rb_data_type_t IODINE_CONNECTION_DATA_TYPE = { + .wrap_struct_name = "IodineConnection", + .function = + { + .dmark = iodine_connection_mark, + .dfree = iodine_connection_free, + .dsize = iodine_connection_data_size, + }, + .data = NULL, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE iodine_connection_alloc(VALUE klass) { + // iodine_connection_s *c = (iodine_connection_s *)fio_malloc(sizeof(*c)); + // FIO_ASSERT_ALLOC(c); + // VALUE self = TypedData_Wrap_Struct(klass, &IODINE_CONNECTION_DATA_TYPE, c); + + iodine_connection_s *c = NULL; + VALUE self = TypedData_Make_Struct(klass, + iodine_connection_s, + &IODINE_CONNECTION_DATA_TYPE, + c); + *c = (iodine_connection_s){0}; + for (size_t i = 0; i < IODINE_CONNECTION_STORE_FINISH; ++i) + c->store[i] = Qnil; + FIO_LEAK_COUNTER_ON_ALLOC(iodine_connection); + return self; +} + +static iodine_connection_s *iodine_connection_ptr(VALUE self) { + iodine_connection_s *c; + TypedData_Get_Struct(self, + iodine_connection_s, + &IODINE_CONNECTION_DATA_TYPE, + c); + return c; +} + +/** Creates (and allocates) a new Iodine::Connection object. */ +static VALUE iodine_connection_create_from_io(fio_io_s *io) { + VALUE m = rb_obj_alloc(iodine_rb_IODINE_CONNECTION); + STORE.hold(m); + iodine_connection_s *c = iodine_connection_ptr(m); + c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_io_udata(io); + c->io = io; + c->http = NULL; + fio_io_udata_set(io, (void *)m); + return m; +} + +/** Creates (and allocates) a new Iodine::Connection object. */ +static VALUE iodine_connection_create_from_http(fio_http_s *h) { + VALUE m = fio_http_udata2(h) + ? (VALUE)fio_http_udata2(h) + : iodine_connection_alloc(iodine_rb_IODINE_CONNECTION); + STORE.hold(m); + iodine_connection_s *c = iodine_connection_ptr(m); + c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_http_udata(h); + c->io = fio_http_io(h); + if (!(c->flags & IODINE_CONNECTION_CLIENT)) + c->http = fio_http_dup(h); + fio_http_udata2_set(h, (void *)m); + return m; +} + +/* ***************************************************************************** +State and Misc +***************************************************************************** */ + +static VALUE iodine_connection_headers_sent_p(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) + return Qtrue; + return (fio_http_is_clean(c->http) ? Qfalse : Qtrue); +} + +static VALUE iodine_connection_is_clean(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (c->http) + return (fio_http_is_clean(c->http) ? Qfalse : Qtrue); + if (c->io) + return (fio_io_is_open(c->io) ? Qtrue : Qfalse); + return Qfalse; +} + +static VALUE iodine_connection_dup_failer(VALUE o) { + rb_raise(rb_eTypeError, "Iodine::Connection objects can't be duplicated."); + return o; +} + +static VALUE iodine_connection_to_s(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (c->http) + return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(HTTP)")); + if (c->io) + return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(RAW)")); + return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(NONE)")); +} + +static VALUE iodine_connection_is_websocket(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (c->http && fio_http_is_websocket(c->http)) + return Qtrue; + return Qfalse; +} + +static VALUE iodine_connection_is_sse(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (c->http && fio_http_is_sse(c->http)) + return Qtrue; + return Qfalse; +} + +/* ***************************************************************************** +Ruby Store Get/Set +***************************************************************************** */ + +#define IODINE_DEF_GET_FUNC(val_name) \ + /** Returns the client's current val_name object. */ \ + static VALUE iodine_connection_##val_name##_get(VALUE self) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + return c->store[IODINE_CONNECTION_STORE_##val_name]; \ + } +#define IODINE_DEF_SET_FUNC(val_name) \ + /** Sets the client's val_name object. */ \ + static VALUE iodine_connection_##val_name##_set(VALUE self, \ + VALUE updated_val) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + c->store[IODINE_CONNECTION_STORE_##val_name] = updated_val; \ + return updated_val; \ + } + +IODINE_DEF_GET_FUNC(handler); +IODINE_DEF_SET_FUNC(env); + +#undef IODINE_DEF_GET_FUNC +#undef IODINE_DEF_SET_FUNC + +/* ***************************************************************************** +Ruby Store Get/Set HTTP Properties +***************************************************************************** */ + +#define IODINE_DEF_GETSET_FUNC(val_name, frozen_) \ + /** Returns the client's current val_name object. */ \ + static VALUE iodine_connection_##val_name##_get(VALUE self) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + fio_str_info_s setter; \ + if (!c) \ + return Qnil; \ + if (FIO_UNLIKELY(c->http && \ + c->store[IODINE_CONNECTION_STORE_##val_name] == Qnil) && \ + (setter = fio_http_##val_name(c->http)).len) { \ + c->store[IODINE_CONNECTION_STORE_##val_name] = \ + (frozen_ ? STORE.frozen_str(setter) \ + : rb_str_new(setter.buf, setter.len)); \ + } \ + return c->store[IODINE_CONNECTION_STORE_##val_name]; \ + } \ + /** Sets the client's val_name object. */ \ + static VALUE iodine_connection_##val_name##_set(VALUE self, \ + VALUE updated_val) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + if (!c) \ + return Qnil; \ + c->store[IODINE_CONNECTION_STORE_##val_name] = updated_val; \ + if (!c->http) \ + return updated_val; \ + if (RB_TYPE_P(updated_val, RUBY_T_SYMBOL)) \ + updated_val = rb_sym2str(updated_val); \ + if (!RB_TYPE_P(updated_val, RUBY_T_STRING)) \ + rb_raise(rb_eTypeError, "new value must be a String or Symbol."); \ + fio_http_##val_name##_set( \ + c->http, \ + FIO_STR_INFO2(RSTRING_PTR(updated_val), \ + (size_t)RSTRING_LEN(updated_val))); \ + return updated_val; \ + } + +/* String Get/Set: */ +IODINE_DEF_GETSET_FUNC(method, 1) +IODINE_DEF_GETSET_FUNC(path, 0) +IODINE_DEF_GETSET_FUNC(query, 0) +IODINE_DEF_GETSET_FUNC(version, 1) +#undef IODINE_DEF_GETSET_FUNC + +#define IODINE_DEF_GETSET_FUNC(val_name) \ + /** Returns the client's current val_name object. */ \ + static VALUE iodine_connection_##val_name##_get(VALUE self) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + if (FIO_UNLIKELY(!c || !c->http)) \ + return Qnil; \ + const size_t setter = fio_http_##val_name(c->http); \ + return ULL2NUM(setter); \ + } \ + /** Sets the client's val_name object. */ \ + static VALUE iodine_connection_##val_name##_set(VALUE self, \ + VALUE updated_val) { \ + iodine_connection_s *c = iodine_connection_ptr(self); \ + size_t setter = 0; \ + if (FIO_UNLIKELY(!c || !c->http)) \ + return Qnil; \ + if (RB_TYPE_P(updated_val, RUBY_T_FIXNUM)) \ + setter = NUM2ULL(updated_val); \ + else if (!RB_TYPE_P(updated_val, RUBY_T_STRING)) { \ + fio_buf_info_s s = IODINE_RSTR_INFO(updated_val); \ + const char *end = s.buf + s.len; \ + setter = fio_atol(&s.buf); \ + if (s.buf != end) \ + rb_raise(rb_eArgError, \ + "passed String isn't a number: %.*s", \ + (int)s.len, \ + end - s.len); \ + } else \ + rb_raise( \ + rb_eTypeError, \ + "new value must be a Number (or a String containing a number)."); \ + fio_http_##val_name##_set(c->http, setter); \ + return ULL2NUM(setter); \ + } + +/* Numeral Get/Set: */ +IODINE_DEF_GETSET_FUNC(status) +#undef IODINE_DEF_GETSET_FUNC + +/* ***************************************************************************** +Hash Map style access +***************************************************************************** */ + +FIO_SFUNC int iodine_connection_map_headers_task(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *c_) { + iodine_connection_s *c = (iodine_connection_s *)c_; + VALUE ary = Qnil; + VALUE k = STORE.frozen_str(name); + c->store[IODINE_CONNECTION_STORE_tmp] = k; + VALUE tmp = iodine_minimap_rb_get(&c->map, k); + if (FIO_LIKELY(!tmp)) { + tmp = rb_str_new(value.buf, value.len); + iodine_hmap_set(&c->map.map, k, tmp, NULL); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return 0; + } + if (RB_TYPE_P(tmp, RUBY_T_STRING)) { + ary = rb_ary_new(); + c->store[IODINE_CONNECTION_STORE_rack] = ary; /* unused, so use it */ + rb_ary_push(ary, tmp); + iodine_hmap_set(&c->map.map, k, ary, NULL); + c->store[IODINE_CONNECTION_STORE_rack] = Qnil; + tmp = ary; + } + rb_ary_push(tmp, rb_str_new(value.buf, value.len)); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return 0; +} + +FIO_SFUNC VALUE iodine_connection_map_headers(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http || (fio_atomic_or(&c->flags, IODINE_CONNECTION_HEADERS_COPIED) & + IODINE_CONNECTION_HEADERS_COPIED)) + return o; + if ((c->flags & IODINE_CONNECTION_CLIENT)) + goto is_client; + iodine_hmap_reserve(&c->map.map, + fio_http_request_header_count(c->http, FIO_STR_INFO0)); + fio_http_request_header_each(c->http, iodine_connection_map_headers_task, c); + return o; + +is_client: + iodine_hmap_reserve(&c->map.map, + fio_http_response_header_count(c->http, FIO_STR_INFO0)); + fio_http_response_header_each(c->http, iodine_connection_map_headers_task, c); + fio_http_cookie(c->http, NULL, 0); /* force parsing of cookies */ + return o; +} + +static iodine_minimap_s *iodine_connection_map_ptr(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + return &c->map; +} + +static VALUE iodine_connection_map_set(VALUE o, VALUE key, VALUE value) { + iodine_connection_s *c = iodine_connection_ptr(o); + return iodine_minimap_store(&c->map, key, value); +} + +static VALUE iodine_connection_map_get(VALUE o, VALUE key) { + iodine_connection_s *c = iodine_connection_ptr(o); + VALUE r = iodine_minimap_rb_get(&c->map, key); + if (!r) + goto missing_item; + return r; + +missing_item: + r = Qnil; + if (!c->http || !RB_TYPE_P(key, RUBY_T_STRING)) + return r; + if (1 < fio_http_request_header_count( + c->http, + (fio_str_info_s)IODINE_RSTR_INFO(key))) { /* more than one */ + r = rb_ary_new(); + iodine_connection_map_set(o, key, r); + for (size_t i = 0;; ++i) { + fio_str_info_s h = + fio_http_request_header(c->http, + (fio_str_info_s)IODINE_RSTR_INFO(key), + i); + if (!h.buf) + break; + rb_ary_push(r, rb_str_new(h.buf, h.len)); + } + } else { + fio_str_info_s h = + fio_http_request_header(c->http, + (fio_str_info_s)IODINE_RSTR_INFO(key), + 0); + if (h.buf) + r = rb_str_new(h.buf, h.len); + iodine_connection_map_set(o, key, r); + } + return r; +} +static VALUE iodine_connection_map_each(VALUE o) { + iodine_connection_map_headers(o); + return iodine_minimap_rb_each(iodine_connection_map_ptr(o)); +} +static VALUE iodine_connection_map_count(VALUE o) { + return ULL2NUM((unsigned long long)iodine_hmap_count( + &iodine_connection_map_ptr(o)->map)); +} +FIO_SFUNC VALUE iodine_connection_map_capa(VALUE o) { + return ULL2NUM( + (unsigned long long)iodine_hmap_capa(&iodine_connection_map_ptr(o)->map)); +} + +FIO_SFUNC VALUE iodine_connection_map_reserve(VALUE o, VALUE s_) { + rb_check_type(s_, T_FIXNUM); + size_t s = (size_t)NUM2ULL(s_); + if (s > 0x0FFFFFFFULL) + rb_raise( + rb_eRangeError, + "cannot reserve negative values or values higher than 268,435,455."); + iodine_minimap_s *m = iodine_connection_map_ptr(o); + iodine_hmap_reserve(&m->map, s); + return o; +} + +/* ***************************************************************************** +HTTP IO Callbacks and Helpers +***************************************************************************** */ + +/** Callback for HTTP requests (server) or responses (client). */ +static void iodine_io_http_on_http(fio_http_s *h); +/** (optional) the callback to be performed when the HTTP service closes. */ +static void iodine_io_http_on_stop(struct fio_http_settings_s *settings); + +/** Authenticate EventSource (SSE) requests, return non-zero to deny.*/ +static int iodine_io_http_on_authenticate_sse(fio_http_s *h); +/** Authenticate WebSockets Upgrade requests, return non-zero to deny.*/ +static int iodine_io_http_on_authenticate_websocket(fio_http_s *h); + +/** Called once a WebSocket / SSE connection upgrade is complete. */ +static void iodine_io_http_on_open(fio_http_s *h); + +/** Called when a WebSocket message is received. */ +static void iodine_io_http_on_message(fio_http_s *h, + fio_buf_info_s msg, + uint8_t is_text); +/** Called when an EventSource event is received. */ +static void iodine_io_http_on_eventsource(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); +/** Called when an EventSource reconnect event requests an ID. */ +static void iodine_io_http_on_eventsource_reconnect(fio_http_s *h, + fio_buf_info_s id); + +/** Called for WebSocket / SSE connections when outgoing buffer is empty. */ +static void iodine_io_http_on_ready(fio_http_s *h); +/** Called for open WebSocket / SSE connections during shutting down. */ +static void iodine_io_http_on_shutdown(fio_http_s *h); +/** Called after a WebSocket / SSE connection is closed (for cleanup). */ +static void iodine_io_http_on_close(fio_http_s *h); +/** Called when a request / response cycle is finished with no Upgrade. */ +static void iodine_io_http_on_finish(fio_http_s *h); + +/* main argument structure */ +typedef struct { + fio_buf_info_s url; + fio_url_s url_data; + VALUE rb_tls; + fio_buf_info_s hint; + VALUE headers; + VALUE cookies; + fio_buf_info_s method; + fio_buf_info_s body; + fio_http_settings_s settings; + enum { + IODINE_SERVICE_RAW, + IODINE_SERVICE_HTTP, + IODINE_SERVICE_WS, + IODINE_SERVICE_UNKNOWN, + } service; +} iodine_connection_args_s; + +static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata); +static void *iodine_tcp_listen(iodine_connection_args_s args); + +/* ***************************************************************************** +HTTP Cookies +***************************************************************************** */ + +FIO_SFUNC VALUE iodine_connection_cookie_get(VALUE o, VALUE name) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) { + rb_raise(rb_eTypeError, + "Iodine::Connection instance should be an HTTP connection to " + "use cookies."); + } + if (RB_TYPE_P(name, RUBY_T_SYMBOL)) + name = rb_sym_to_s(name); + rb_check_type(name, RUBY_T_STRING); + fio_str_info_s str = + fio_http_cookie(c->http, RSTRING_PTR(name), RSTRING_LEN(name)); + if (!str.buf) + return Qnil; + return rb_usascii_str_new(str.buf, str.len); +} + +FIO_SFUNC VALUE iodine_connection_cookie_set(int argc, VALUE *argv, VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) { + rb_raise(rb_eTypeError, + "Iodine::Connection instance should be an HTTP connection to " + "use cookies."); + } + fio_str_info_s name = FIO_STR_INFO0; + fio_str_info_s value = FIO_STR_INFO0; + size_t max_age = 0; + fio_str_info_s domain = FIO_STR_INFO0; + fio_str_info_s path = FIO_STR_INFO0; + VALUE same_site_rb = Qnil; + uint8_t secure = 0; + uint8_t http_only = 0; + uint8_t partitioned = 0; + + fio_http_cookie_same_site_e same_site_c = + FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_STR(name, 0, "name", 1), + IODINE_ARG_STR(value, 0, "value", 0), + IODINE_ARG_SIZE_T(max_age, 0, "max_age", 0), + IODINE_ARG_STR(domain, 0, "domain", 0), + IODINE_ARG_STR(path, 0, "path", 0), + IODINE_ARG_RB(same_site_rb, 0, "same_site", 0), + IODINE_ARG_BOOL(secure, 0, "secure", 0), + IODINE_ARG_BOOL(http_only, 0, "http_only", 0), + IODINE_ARG_BOOL(partitioned, 0, "partitioned", 0)); + + if (!name.len) + return Qnil; + if (same_site_rb != Qnil) { + rb_check_type(same_site_rb, RUBY_T_SYMBOL); + ID same_site_id = rb_sym2id(same_site_rb); + if (same_site_id == IODINE_SAME_SITE_DEFAULT) + same_site_c = FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT; + else if (same_site_id == IODINE_SAME_SITE_NONE) + same_site_c = FIO_HTTP_COOKIE_SAME_SITE_NONE; + else if (same_site_id == IODINE_SAME_SITE_LAX) + same_site_c = FIO_HTTP_COOKIE_SAME_SITE_LAX; + else if (same_site_id == IODINE_SAME_SITE_STRICT) + same_site_c = FIO_HTTP_COOKIE_SAME_SITE_STRICT; + } + if (fio_http_cookie_set(c->http, + .name = name, + .value = value, + .domain = domain, + .path = path, + .max_age = (int)max_age, + .same_site = (fio_http_cookie_same_site_e)same_site_c, + .secure = secure, + .http_only = http_only, + .partitioned = partitioned)) { + return Qfalse; + rb_raise(rb_eRuntimeError, + "cookies cannot be set at this point, please check if headers " + "were sent before setting a cookie."); + } + return Qtrue; +} + +static int iodine_connection_cookie_each_task(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *info) { + VALUE *argv = (VALUE *)info; + ++argv; + argv[0] = rb_usascii_str_new(name.buf, name.len); + STORE.hold(argv[0]); /* TODO, avoid STORE for fast path if possible */ + argv[1] = rb_usascii_str_new(value.buf, value.len); + STORE.hold(argv[1]); + rb_yield_values2(2, argv); + STORE.release(argv[1]); + STORE.release(argv[0]); + argv[0] = Qnil; + argv[1] = Qnil; + return 0; + (void)h; +} + +FIO_SFUNC VALUE iodine_connection_cookie_each_caller(VALUE info_) { + VALUE *info = (VALUE *)info_; + iodine_connection_s *c = iodine_connection_ptr(info[0]); + fio_http_cookie_each(c->http, iodine_connection_cookie_each_task, info); + return info_; +} + +FIO_SFUNC VALUE iodine_connection_cookie_each(VALUE o) { + rb_block_given_p(); + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) { + rb_raise(rb_eTypeError, + "Iodine::Connection instance should be an HTTP connection to " + "use cookies."); + } + VALUE info[3] = {o, Qnil, Qnil}; + int state = 0; + rb_protect(iodine_connection_cookie_each_caller, (VALUE)info, &state); + if (!state) + return o; + STORE.release(info[2]); + STORE.release(info[1]); + VALUE exc = rb_errinfo(); + if (!rb_obj_is_instance_of(exc, rb_eLocalJumpError)) + iodine_handle_exception(NULL); + rb_set_errinfo(Qnil); + return o; +} + +/* ***************************************************************************** +HTTP Body Access +***************************************************************************** */ + +FIO_SFUNC VALUE iodine_connection_body_length(VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) + return ULL2NUM(0); + return ULL2NUM(fio_http_body_length(c->http)); +} + +FIO_SFUNC VALUE iodine_connection_body_gets(int argc, VALUE *argv, VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) + return Qnil; + size_t length = (size_t)-1; + iodine_rb2c_arg(argc, argv, IODINE_ARG_SIZE_T(length, 0, "limit", 0)); + fio_str_info_s s = fio_http_body_read_until(c->http, '\n', length); + if (!s.len) + return Qnil; + return rb_usascii_str_new(s.buf, s.len); +} + +FIO_SFUNC VALUE iodine_connection_body_read(int argc, VALUE *argv, VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) + return Qnil; + size_t length = (size_t)-1; + VALUE rbuf = Qnil; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_SIZE_T(length, 0, "maxlen", 0), + IODINE_ARG_RB(rbuf, 0, "out_string", 0)); + if (rbuf != Qnil) { + Check_Type(rbuf, T_STRING); + rb_str_set_len(rbuf, 0); + } + fio_str_info_s s = fio_http_body_read(c->http, length); + if (s.len) { + if (rbuf == Qnil) + rbuf = rb_usascii_str_new(s.buf, s.len); + else + rb_str_cat(rbuf, s.buf, s.len); + } + return rbuf; +} + +FIO_SFUNC VALUE iodine_connection_body_seek(int argc, VALUE *argv, VALUE o) { + iodine_connection_s *c = iodine_connection_ptr(o); + if (!c->http) + return Qnil; + int64_t pos = 0; + iodine_rb2c_arg(argc, argv, IODINE_ARG_NUM(pos, 0, "pos", 0)); + pos = (long long)fio_http_body_seek(c->http, (ssize_t)pos); + return ULL2NUM(pos); +} + +/* ***************************************************************************** +HTTP Response API TODO + +- `write_header(name, value)`: Sets a response header and returns `true`. If + headers were already sent or either `write` or `finish` was previously + called, it **MUST** return `false`. + + - The header `name` **MUST** be a lowercase string. Servers **MAY** enforce + this by converting string objects to lowercase. + + - Servers **MAY** accept Symbols as the header `name` well. + + - `value` **MUST** be either a String or an Array of Strings. Servers **SHOULD + NOT** (but **MAY**) accept other `value` types. + + - The `write_header` method is **irreversible**. Servers **MAY** write the + header immediately, as they see fit. + + - When `write_header` **MAY** be called multiple times for the same header + `name`. This **MAY** result in multiple headers with the same name being + sent. Servers **SHOULD** avoid sending the same header name if it is + forbidden by the HTTP standard. + +* `write(data)` - **streams** the data, using the appropriate encoding. +**Note**: + + * `data` MUST be either String object or a + [File](https://ruby-doc.org/core-2.7.0/File.html) (or + [TempFile](https://ruby-doc.org/stdlib-2.7.0/libdoc/tempfile/rdoc/Tempfile.html)) + object. + + * If `data` is a `File` instance, then the server **MUST** call it's `close` +method after the data was sent. + + * If the `"content-type"` was set to `text/event-stream`, this is an SSE / +EventSource connection and servers **MUST** send the data as is (the encoding +**MUST** be assumed to be handled by the application layer). + + * Otherwise, if the `"content-length"` header wasn't set, the server **MUST +EITHER** use `chunked` [transfer +encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding), +**OR** set the [`connection: close` +header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection) +and close the connection once `finish` was called. + + * If the headers weren't previously sent, they **MUST** be sent (or locked) +at this point. Once `write` or `finish` are called, calls to `write_header` +**MUST** return `false`. + +* `finish([data])` - completes the response. Note: + + * Subsequent calls to `finish` **MUST** be ignored (except `close` **MUST** +still be called if `data` is a `File` instance). + + * `data` MUST be either `nil`, or a String object, or a +[File](https://ruby-doc.org/core-2.7.0/File.html) (or +[TempFile](https://ruby-doc.org/stdlib-2.7.0/libdoc/tempfile/rdoc/Tempfile.html)) +object. + + * If `data` is a `File` instance, then the server **MUST** call it's `close` +method after the data was sent. + + * If the headers weren't previously sent, they **MUST** be sent before +sending any data. + + * If `data` was provided, it should be sent. If no previous calls to `write` +were made and no `"content-length"` header was set, the server **MAY** set the +`"content-length"` for the response before sending the `data`. + +* `headers_sent?` - returns `true` if additional headers cannot be sent (the +headers were already sent). Otherwise returns `false`. Servers **MAY** return +`false` **even if** the response is implemented using `chunked` encoding with +trailers, allowing certain headers to be sent after the response was sent. + +* `valid?` - returns `true` if data may still be sent (the connection is open +and `finish` hadn't been called yet). Otherwise returns `false`. + +* `dup` - (optional) **SHOULD** throw an exception, as the `event` object **MUST +NOT** be duplicated by the NeoRack Application. + +***************************************************************************** */ + +/* ***************************************************************************** +Lower-Case Helper for Header Names +***************************************************************************** */ + +#define IODINE___COPY_TO_LOWER_CASE(var_name, inpute_var) \ + FIO_STR_INFO_TMP_VAR(var_name, 4096); \ + iodine___copy_to_lower_case(&var_name, &inpute_var); + +/** Converts a Header key to lower-case */ +FIO_IFUNC void iodine___copy_to_lower_case(fio_str_info_s *t, + fio_str_info_s *k) { + if (k->len >= t->capa) + goto too_big; + for (size_t i = 0; i < k->len; ++i) { + uint8_t c = (uint8_t)k->buf[i]; + c |= (uint8_t)(c >= 'A' || c <= 'Z') << 5; + t->buf[i] = c; + } + t->len = k->len; + return; +too_big: + *t = *k; +} + +/* ***************************************************************************** +Default Handler Implementations +***************************************************************************** */ + +static VALUE iodine_handler_deafult_on_http404(VALUE handler, VALUE client) { + iodine_connection_s *c = iodine_connection_ptr(client); + if (!c->http) + return Qnil; + fio_http_status_set(c->http, 404); + fio_http_finish(c->http); + // fio_http_send_error_response(c->http, 404); + return Qnil; + (void)handler; +} + +static VALUE iodine_handler_deafult_on_event(VALUE handler, VALUE client) { + return Qnil; + (void)handler, (void)client; +} + +static VALUE iodine_handler_deafult_on_auth(VALUE handler, VALUE client) { + return Qtrue; + (void)handler, (void)client; +} +static VALUE iodine_handler_deafult_on_authx(VALUE handler, VALUE client) { + return Qfalse; + (void)handler, (void)client; +} + +static VALUE iodine_handler_on_auth_reroute(VALUE handler, VALUE client) { + iodine_caller_result_s r = + iodine_ruby_call_inside(handler, IODINE_ON_AUTHENTICATE_ID, 1, &client); + if (r.exception || r.result != Qtrue) + return Qfalse; + return Qtrue; +} + +static VALUE iodine_handler_deafult_on_message(VALUE h, VALUE c, VALUE m) { + return Qnil; + (void)h, (void)c, (void)m; +} + +static void iodine_connection___add_header(fio_http_s *h, + fio_str_info_s n, + fio_str_info_s v) { + char *eol = (char *)memchr(v.buf, '\n', v.len); + while (eol) { + ++eol; + fio_str_info_s tmp = FIO_STR_INFO2(v.buf, (size_t)(eol - v.buf)); + fio_http_response_header_add(h, n, tmp); + v.buf = eol; + v.len -= tmp.len; + } + fio_http_response_header_add(h, n, v); +} + +static int iodine_handler_deafult_on_http__header2(fio_str_info_s n, + VALUE v, + VALUE h_) { + fio_http_s *h = (fio_http_s *)h_; + FIO_STR_INFO_TMP_VAR(vstr, 24); + if (RB_TYPE_P(v, RUBY_T_ARRAY)) + goto is_array; + if (RB_TYPE_P(v, RUBY_T_SYMBOL)) + v = rb_sym_to_s(v); + if (RB_TYPE_P(v, RUBY_T_STRING)) + vstr = (fio_str_info_s)IODINE_RSTR_INFO(v); + else if (RB_TYPE_P(v, RUBY_T_FIXNUM)) + fio_string_write_i(&vstr, NULL, (int64_t)RB_NUM2LL(v)); + else + goto type_error; + iodine_connection___add_header(h, n, vstr); + return ST_CONTINUE; + +is_array: + for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) { + VALUE t = RARRAY_PTR(v)[i]; + if (!RB_TYPE_P(t, RUBY_T_STRING)) + continue; + iodine_connection___add_header(h, n, (fio_str_info_s)IODINE_RSTR_INFO(t)); + } + return ST_CONTINUE; + +type_error: + FIO_LOG_WARNING("Rack response contained an invalid header value type. " + "Header values MUST be Strings (or Arrays of Strings)"); + return ST_CONTINUE; +} + +static int iodine_handler_deafult_on_http__header(VALUE n_, VALUE v, VALUE h_) { + fio_str_info_s header_rb; + if (RB_TYPE_P(n_, RUBY_T_SYMBOL)) + n_ = rb_sym_to_s(n_); + if (!RB_TYPE_P(n_, RUBY_T_STRING)) + return ST_CONTINUE; + header_rb = (fio_str_info_s)IODINE_RSTR_INFO(n_); + IODINE___COPY_TO_LOWER_CASE(n, header_rb); + return iodine_handler_deafult_on_http__header2(n, v, h_); +} + +static VALUE iodine_handler_deafult_on_http__each_body(VALUE s, VALUE h_) { + fio_http_s *h = (fio_http_s *)h_; + if (!RB_TYPE_P(s, RUBY_T_STRING)) + goto error_in_type; + fio_http_write(h, + .buf = RSTRING_PTR(s), + .len = (size_t)RSTRING_LEN(s), + .copy = 1, + .finish = 0); + return s; +error_in_type: + if (s != Qnil) + FIO_LOG_ERROR( + "HTTP response body .each must be yield only String Objects."); + return s; +} + +static VALUE iodine_connection_env_get(VALUE self); +static VALUE iodine_connection_handler_set(VALUE client, VALUE handler); +static VALUE iodine_connection_rack_hijack(VALUE self); +static VALUE iodine_connection_peer_addr(VALUE self); +static VALUE iodine_handler_deafult_on_http(VALUE handler, VALUE client) { + /* RACK specification: https://github.com/rack/rack/blob/main/SPEC.rdoc */ + VALUE returned_value = Qnil; + iodine_connection_s *c = iodine_connection_ptr(client); + if (!c->http) + return returned_value; + VALUE r, env; + /* collect `env` and call `call`. */ + env = iodine_connection_env_get(client); + /* call `call` from top of MiddleWare / App chain */ + r = c->store[IODINE_CONNECTION_STORE_rack] = + rb_funcallv(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_CALL_ID, + 1, + &env); + if (!c->http) + return returned_value; + if (!RB_TYPE_P(r, RUBY_T_ARRAY)) + goto rack_error; + if (RARRAY_LEN(r) != 3) + goto rack_error; + { /* handle status */ + VALUE s = RARRAY_PTR(r)[0]; + long long i = 0; + switch (rb_type(s)) { + case RUBY_T_STRING: i = rb_str_to_inum(s, 10, 0); break; + case RUBY_T_FIXNUM: i = NUM2LL(s); break; + case RUBY_T_TRUE: i = 200; break; + case RUBY_T_FALSE: i = 404; break; + default: i = 0; + } + if (i > 0 && i < 1000) + fio_http_status_set(c->http, (size_t)i); + } + { /* handle headers */ + VALUE hdr = RARRAY_PTR(r)[1]; + if (RB_TYPE_P(hdr, RUBY_T_HASH)) { + rb_hash_foreach(hdr, + iodine_handler_deafult_on_http__header, + (VALUE)c->http); + } else if (RB_TYPE_P(hdr, RUBY_T_ARRAY)) { + for (size_t i = 0; i < (size_t)RARRAY_LEN(hdr); ++i) { + VALUE t = RARRAY_PTR(hdr)[i]; + if (!RB_TYPE_P(t, RUBY_T_ARRAY) || RARRAY_LEN(t) != 2) + goto rack_error; + iodine_connection___add_header( + c->http, + (fio_str_info_s)IODINE_RSTR_INFO(RARRAY_PTR(t)[0]), + (fio_str_info_s)IODINE_RSTR_INFO(RARRAY_PTR(t)[1])); + } + } else { + FIO_LOG_ERROR("Rack application response headers type error"); + goto rack_error; + } + } + if (!(c->flags & IODINE_CONNECTION_UPGRADE)) { /* handle body */ + VALUE bd = RARRAY_PTR(r)[2]; + + if (RB_TYPE_P(bd, RUBY_T_ARRAY)) { + size_t tst = 1; + const size_t len = RARRAY_LEN(bd); + for (size_t i = 0; i < len; ++i) + tst &= RB_TYPE_P(RARRAY_PTR(bd)[i], RUBY_T_STRING); + if (!tst) + goto call_using_each; + char buf[1ULL << 17]; + const size_t limit = (1ULL << 17); + size_t blen = 0; + for (size_t i = 0; i < len; ++i) { + if (blen + (size_t)RSTRING_LEN((RARRAY_PTR(bd)[i])) < limit) { + FIO_MEMCPY(buf + blen, + RSTRING_PTR((RARRAY_PTR(bd)[i])), + (size_t)RSTRING_LEN((RARRAY_PTR(bd)[i]))); + blen += (size_t)RSTRING_LEN((RARRAY_PTR(bd)[i])); + continue; + } + if (blen) { + fio_http_write(c->http, + .buf = buf, + .len = blen, + .copy = 1, + .finish = 0); + blen = 0; + } + if (i + 1 < len && + (size_t)RSTRING_LEN((RARRAY_PTR(bd)[i])) < (limit << 1)) { + --i; + continue; + } + fio_http_write(c->http, + .buf = RSTRING_PTR((RARRAY_PTR(bd)[i])), + .len = (size_t)RSTRING_LEN((RARRAY_PTR(bd)[i])), + .copy = 1, + .finish = (i + 1 == len)); + } + if (blen) + fio_http_write(c->http, + .buf = buf, + .len = blen, + .copy = 1, + .finish = 1); + } else if (RB_TYPE_P(bd, RUBY_T_STRING)) { + fio_http_write(c->http, + .buf = RSTRING_PTR(bd), + .len = (size_t)RSTRING_LEN(bd), + .copy = 1, + .finish = 1); + } else if (rb_respond_to(bd, IODINE_TO_PATH_ID)) { /* named file body... */ + VALUE p = rb_funcallv(bd, IODINE_TO_PATH_ID, 0, NULL); + if (!RB_TYPE_P(p, RUBY_T_STRING)) + goto rack_error; /* FIXME? something else? */ + if (fio_http_static_file_response(c->http, + (fio_str_info_s)IODINE_RSTR_INFO(p), + FIO_STR_INFO0, + fio_cli_get_i("-maxage"))) { + if (rb_respond_to(bd, IODINE_EACH_ID)) + goto call_using_each; + goto rack_error; /* FIXME? something else? */ + } + } else if (rb_respond_to(bd, IODINE_EACH_ID)) { /* streaming body... */ + call_using_each: + rb_block_call( + bd, + IODINE_EACH_ID, + 0, + NULL, + (rb_block_call_func_t)iodine_handler_deafult_on_http__each_body, + (VALUE)c->http); + fio_http_write(c->http, .finish = 1); + (void)c; + } else if (rb_respond_to(bd, IODINE_CALL_ID)) { /* proc streaming body... */ + VALUE nio = iodine_connection_rack_hijack(client); + rb_funcallv(bd, IODINE_CALL_ID, 1, &nio); + } else if (bd == Qnil) { + /* do nothing, no body. */ + } else { /* WTF? */ + FIO_LOG_ERROR("response body invalid for Rack application!"); + goto rack_error; + } + } + rb_check_funcall(RARRAY_PTR(r)[2], IODINE_CLOSE_ID, 0, NULL); + +after_reply: + + r = rb_hash_aref(env, IODINE_RACK_AFTER_RPLY_STR); + if (RB_TYPE_P(r, RUBY_T_ARRAY)) + for (size_t i = 0; i < (size_t)RARRAY_LEN(r); ++i) { + IODINE_DEFER_BLOCK(RARRAY_PTR(r)[i]); + } + + c->store[IODINE_CONNECTION_STORE_rack] = Qnil; + return returned_value; + +rack_error: + if (RB_TYPE_P(r, RUBY_T_ARRAY) && RARRAY_LEN(r) >= 3) + rb_check_funcall(RARRAY_PTR(r)[2], IODINE_CLOSE_ID, 0, NULL); + fio_http_send_error_response(c->http, 500); + c->store[IODINE_CONNECTION_STORE_rack] = Qnil; + goto after_reply; + (void)handler; +} + +static VALUE iodine_handler_on_auth_rack_internal(VALUE handler, + VALUE client, + VALUE upgrd_sym) { + VALUE env = iodine_connection_env_get(client); + rb_hash_aset(env, IODINE_RACK_UPGRADE_Q_STR, upgrd_sym); + iodine_handler_deafult_on_http(handler, client); + /* test old-style iodine Upgrade approach */ + VALUE hn = rb_hash_aref(env, IODINE_RACK_UPGRADE_STR); + if (hn != Qnil) { + iodine_connection_s *c = iodine_connection_ptr(client); + if (hn != Qtrue && hn != c->store[IODINE_CONNECTION_STORE_handler]) + iodine_connection_handler_set(client, hn); + return Qtrue; + } + return Qnil; +} + +static VALUE iodine_handler_on_auth_rack_ws(VALUE handler, VALUE client) { + return iodine_handler_on_auth_rack_internal(handler, + client, + IODINE_RACK_UPGRADE_WS_SYM); +} +static VALUE iodine_handler_on_auth_rack_sse(VALUE handler, VALUE client) { + return iodine_handler_on_auth_rack_internal(handler, + client, + IODINE_RACK_UPGRADE_SSE_SYM); +} + +static VALUE iodine_handler_method_injection__inner(VALUE self, + VALUE handler, + bool is_middleware) { + static VALUE previous = Qnil; + if (handler == previous) + return handler; + previous = handler; +/* test for handler responses and set a callback if missing */ +#define IODINE_DEFINE_MISSING_CALLBACK(id) \ + do { \ + if (!rb_respond_to(handler, id)) \ + rb_define_singleton_method(handler, \ + rb_id2name(id), \ + iodine_handler_deafult_on_event, \ + 1); \ + } while (0) + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_FINISH_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_CLOSE_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_EVENTSOURCE_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_EVENTSOURCE_RECONNECT_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_DRAINED_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_SHUTDOWN_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_TIMEOUT_ID); +#undef IODINE_DEFINE_MISSING_CALLBACK + +#define IODINE_DEFINE_MISSING_CALLBACK(id, callback, nargs) \ + do { \ + if (!rb_respond_to(handler, id)) \ + rb_define_singleton_method(handler, rb_id2name(id), callback, nargs); \ + } while (0) + + /* Has `call` but not `on_http`? assume Rack. */ + if (rb_respond_to(handler, IODINE_CALL_ID) && + !rb_respond_to(handler, IODINE_ON_HTTP_ID)) { + rb_define_singleton_method(handler, + "on_http", + iodine_handler_deafult_on_http, + 1); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID, + iodine_handler_on_auth_rack_sse, + 1); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID, + iodine_handler_on_auth_rack_ws, + 1); + } else { + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_HTTP_ID, + iodine_handler_deafult_on_http404, + 1); + } + + if (rb_respond_to(handler, IODINE_ON_AUTHENTICATE_ID)) { + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID, + iodine_handler_on_auth_reroute, + 1); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID, + iodine_handler_on_auth_reroute, + 1); + } else { + _Bool responds = rb_respond_to(handler, IODINE_ON_OPEN_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID, + (responds ? iodine_handler_deafult_on_auth + : iodine_handler_deafult_on_authx), + 1); + responds |= rb_respond_to(handler, IODINE_ON_MESSAGE_ID); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID, + (responds ? iodine_handler_deafult_on_auth + : iodine_handler_deafult_on_authx), + 1); + } + + /* should be defined last, as its existence controls behavior */ + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_OPEN_ID, + iodine_handler_deafult_on_event, + 1); + IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_MESSAGE_ID, + iodine_handler_deafult_on_message, + 2); +#undef IODINE_DEFINE_MISSING_CALLBACK + return handler; + (void)self; +} + +static VALUE iodine_handler_method_injection(VALUE self, VALUE handler) { + return iodine_handler_method_injection__inner(self, handler, 0); +} + +/* ***************************************************************************** +Handler Set function +***************************************************************************** */ + +/** Sets the client's handler object, adding default callback methods if + * missing. */ +static VALUE iodine_connection_handler_set(VALUE client, VALUE handler) { + iodine_connection_s *c = iodine_connection_ptr(client); + if (!c) + return Qnil; + if (IODINE_STORE_IS_SKIP(handler)) + rb_raise(rb_eArgError, "invalid handler!"); + iodine_handler_method_injection(Qnil, handler); + c->store[IODINE_CONNECTION_STORE_handler] = handler; + return handler; +} + +/* ***************************************************************************** +ENV get / conversion +***************************************************************************** */ + +static int iodine_env_populate_header_data(fio_http_s *h, + fio_str_info_s n, + fio_str_info_s v, + void *c_) { + iodine_connection_s *c = (iodine_connection_s *)c_; + VALUE key = STORE.header_name(n); + c->store[IODINE_CONNECTION_STORE_tmp] = key; + /* copy value */ + VALUE val = rb_str_new(v.buf, v.len); + c->store[IODINE_CONNECTION_STORE_rack] = val; /* unused at this point */ + /* finish up */ + rb_hash_aset(c->store[IODINE_CONNECTION_STORE_env], key, val); + c->store[IODINE_CONNECTION_STORE_rack] = Qnil; + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return 0; + (void)h; +} + +static void iodine_env_set_key_pair(VALUE env, + fio_str_info_s n, + fio_str_info_s v) { + VALUE key = STORE.frozen_str(n); + STORE.hold(key); + VALUE val = rb_str_new(v.buf, v.len); + STORE.hold(val); + rb_hash_aset(env, key, val); + STORE.release(val); + STORE.release(key); +} + +static void iodine_env_set_key_pair_const(VALUE env, + fio_str_info_s n, + fio_str_info_s v) { + VALUE key = STORE.frozen_str(n); + STORE.hold(key); + VALUE val = STORE.frozen_str(v); + STORE.hold(val); + rb_hash_aset(env, key, val); + STORE.release(val); + STORE.release(key); +} + +static void iodine_env_set_const_val(VALUE env, fio_str_info_s n, VALUE val) { + VALUE key = STORE.frozen_str(n); + STORE.hold(key); + rb_hash_aset(env, key, val); + STORE.release(key); +} + +static void iodine_connection_init_env_template(fio_buf_info_s at_url) { + VALUE env = IODINE_CONNECTION_ENV_TEMPLATE = rb_hash_new(); + STORE.hold(IODINE_CONNECTION_ENV_TEMPLATE); + /* set template, see https://github.com/rack/rack/blob/main/SPEC.rdoc */ + // TODO: REMOTE_ADDR + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"rack.multithread"), + (fio_cli_get_i("-t") ? Qtrue : Qfalse)); + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"rack.multiprocess"), + (fio_cli_get_i("-w") ? Qtrue : Qfalse)); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"rack.run_once"), Qfalse); + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"rack.errors"), + rb_stderr); + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"rack.multipart.buffer_size"), + Qnil); + iodine_env_set_const_val( + env, + FIO_STR_INFO1((char *)"rack.multipart.tempfile_factory"), + Qnil); + iodine_env_set_const_val( + env, + FIO_STR_INFO1((char *)"rack.logger"), + rb_funcallv(rb_const_get(rb_cObject, rb_intern2("Logger", 6)), + IODINE_NEW_ID, + 1, + &rb_stderr)); + iodine_env_set_key_pair( + env, + FIO_STR_INFO2((char *)"rack.url_scheme", 15), + FIO_STR_INFO2((char *)"https", + (fio_cli_get("-tls") ? 5 : 4))); /* FIXME? */ + + rb_hash_aset(env, IODINE_RACK_AFTER_RPLY_STR, Qnil); + + iodine_env_set_const_val( + env, + FIO_STR_INFO1((char *)"rack.input"), + rb_funcallv(rb_const_get(rb_cObject, rb_intern2("StringIO", 8)), + IODINE_NEW_ID, + 0, + NULL)); + /* FIXME! TODO! */ + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"rack.session"), Qnil); + { + VALUE ver = rb_ary_new(); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"rack.version"), ver); + rb_ary_push(ver, INT2NUM(1)); + rb_ary_push(ver, INT2NUM(3)); + } + // TODO! + // iodine_env_set_const_val(env, FIO_STR_INFO1((char*)"rack.hijack?"), + // Qtrue);// ??? + rb_hash_aset(env, IODINE_RACK_HIJACK_STR, Qnil); + // iodine_env_set_const_val(env, FIO_STR_INFO1((char*)"rack.hijack"), Qnil); + // // TODO? + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"neorack.client"), Qnil); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"REQUEST_METHOD"), Qtrue); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"PATH_INFO"), Qtrue); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"QUERY_STRING"), Qtrue); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"REMOTE_ADDR"), Qnil); + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PROTOCOL"), + Qtrue); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"HTTP_VERSION"), Qtrue); + iodine_env_set_key_pair(env, + FIO_STR_INFO1((char *)"SERVER_NAME"), + FIO_STR_INFO0); + iodine_env_set_key_pair(env, + FIO_STR_INFO1((char *)"SCRIPT_NAME"), + FIO_STR_INFO0); + if (at_url.len) { + fio_url_s u = fio_url_parse(at_url.buf, at_url.len); + uint64_t port_num = u.port.len ? fio_atol(&u.port.buf) : 0; + if (port_num && (port_num < 0xFFFF)) + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PORT"), + UINT2NUM((unsigned)port_num)); + } else if (fio_cli_get_i("-p")) { + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PORT"), + UINT2NUM((unsigned)fio_cli_get_i("-p"))); + } else if (fio_cli_unnamed(0)) { + fio_url_s u = fio_url_parse(fio_cli_unnamed(0), strlen(fio_cli_unnamed(0))); + uint64_t port_num = u.port.len ? fio_atol(&u.port.buf) : 0; + if (port_num && (port_num < 0xFFFF)) + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PORT"), + UINT2NUM((unsigned)port_num)); + } else if (getenv("PORT") && strlen(getenv("PORT"))) { + char *tmp = getenv("PORT"); + uint64_t port_num = fio_atol(&tmp); + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PORT"), + UINT2NUM((unsigned)port_num)); + } else { + iodine_env_set_const_val(env, + FIO_STR_INFO1((char *)"SERVER_PORT"), + UINT2NUM((unsigned)3000)); + } +} + +/** Returns the client's current `env` object. */ +static VALUE iodine_connection_env_get(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c) + return Qnil; + if (c->store[IODINE_CONNECTION_STORE_env] != Qnil) + return c->store[IODINE_CONNECTION_STORE_env]; + if (!c->http) + return (c->store[IODINE_CONNECTION_STORE_env] = rb_hash_new()); + VALUE env = c->store[IODINE_CONNECTION_STORE_env] = + rb_hash_dup(IODINE_CONNECTION_ENV_TEMPLATE); + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"neorack.client"), self); + { + VALUE tmp = rb_obj_method(self, IODINE_RACK_HIJACK_SYM); + c->store[IODINE_CONNECTION_STORE_tmp] = tmp; + rb_hash_aset(env, IODINE_RACK_HIJACK_STR, tmp); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + } + + /* populate env, see https://github.com/rack/rack/blob/main/SPEC.rdoc */ + fio_http_request_header_each(c->http, + iodine_env_populate_header_data, + (void *)c); + + if (fio_http_body_length(c->http)) { + FIO_STR_INFO_TMP_VAR(num2str, 512); + fio_string_write_u(&num2str, NULL, fio_http_body_length(c->http)); + VALUE clen_str = rb_str_new(num2str.buf, num2str.len); + c->store[IODINE_CONNECTION_STORE_tmp] = clen_str; + rb_hash_aset(env, + STORE.header_name(FIO_STR_INFO1((char *)"content-length")), + clen_str); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"rack.input"), self); + } + + iodine_env_set_key_pair_const(env, + FIO_STR_INFO2((char *)"REQUEST_METHOD", 14), + fio_keystr_info(&c->http->method)); + iodine_env_set_key_pair(env, + FIO_STR_INFO2((char *)"PATH_INFO", 9), + fio_keystr_info(&c->http->path)); + iodine_env_set_key_pair(env, + FIO_STR_INFO2((char *)"QUERY_STRING", 12), + fio_keystr_info(&c->http->query)); + { + fio_str_info_s host = + fio_http_request_header(c->http, FIO_STR_INFO2((char *)"host", 4), 0); + fio_url_s u = fio_url_parse(host.buf, host.len); + iodine_env_set_key_pair_const(env, + FIO_STR_INFO2((char *)"SERVER_NAME", 11), + FIO_BUF2STR_INFO(u.host)); + } + iodine_env_set_key_pair_const(env, + FIO_STR_INFO2((char *)"SERVER_PROTOCOL", 15), + fio_keystr_info(&c->http->version)); + iodine_env_set_key_pair_const(env, + FIO_STR_INFO2((char *)"HTTP_VERSION", 12), + fio_keystr_info(&c->http->version)); + { + VALUE addr = iodine_connection_peer_addr(self); + c->store[IODINE_CONNECTION_STORE_tmp] = addr; + iodine_env_set_const_val(env, FIO_STR_INFO1((char *)"REMOTE_ADDR"), addr); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + } + { + VALUE ary_after = rb_ary_new(); + c->store[IODINE_CONNECTION_STORE_tmp] = ary_after; + rb_hash_aset(env, IODINE_RACK_AFTER_RPLY_STR, ary_after); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + } + + return env; +} + +/* ***************************************************************************** +Raw IO Callbacks and Helpers (TCP/IP) +***************************************************************************** */ + +typedef struct { + fio_io_s *io; + size_t len; + char buf[IODINE_RAW_ON_DATA_READ_BUFFER]; +} iodine_io_raw_on_data_info_s; + +static void *iodine_io_raw_on_attach_in_GIL(void *io) { + VALUE connection = iodine_connection_create_from_io(io); + if (!connection || connection == Qnil) + return NULL; + iodine_connection_s *c = iodine_connection_ptr(connection); + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_OPEN_ID, + 1, + &connection); + return NULL; +} + +/** Called when an IO is attached to the protocol. */ +static void iodine_io_raw_on_attach(fio_io_s *io) { + /* Enter GIL */ + rb_thread_call_with_gvl(iodine_io_raw_on_attach_in_GIL, io); +} + +static void *iodine_io_raw_on_data_in_GVL(void *info_) { + iodine_io_raw_on_data_info_s *i = (iodine_io_raw_on_data_info_s *)info_; + VALUE connection = (VALUE)fio_io_udata(i->io); + if (!connection || connection == Qnil) + return NULL; + iodine_connection_s *c = iodine_connection_ptr(connection); + VALUE buf = rb_usascii_str_new(i->buf, (long)i->len); + c->store[IODINE_CONNECTION_STORE_tmp] = buf; + VALUE args[] = {connection, buf}; + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_MESSAGE_ID, + 2, + args); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return NULL; +} + +static void iodine_io_raw_on_data(fio_io_s *io) { + iodine_io_raw_on_data_info_s i; + i.io = io; + i.len = fio_io_read(io, i.buf, IODINE_RAW_ON_DATA_READ_BUFFER); + if (!i.len) + return; + rb_thread_call_with_gvl(iodine_io_raw_on_data_in_GVL, &i); +} + +#define IODINE_CONNECTION_DEF_CB(named, id) \ + static void iodine_io_raw_##named(fio_io_s *io) { \ + VALUE connection = (VALUE)fio_io_udata(io); \ + if (!connection || connection == Qnil) \ + return; \ + iodine_connection_s *c = iodine_connection_ptr(connection); \ + if (!c) \ + return; \ + iodine_ruby_call_outside(c->store[IODINE_CONNECTION_STORE_handler], \ + id, \ + 1, \ + &connection); \ + } + +/** called once all pending `write` calls are finished. */ +IODINE_CONNECTION_DEF_CB(on_ready, IODINE_ON_DRAINED_ID); +/** called if the connection is open when the server is shutting down. */ +IODINE_CONNECTION_DEF_CB(on_shutdown, IODINE_ON_SHUTDOWN_ID); +/** Called when a connection's open while server is shutting down. */ +static void iodine_io_raw_on_shutdown(fio_io_s *io); +/** Called when a connection's timeout was reached */ +IODINE_CONNECTION_DEF_CB(on_timeout, IODINE_ON_TIMEOUT_ID); + +#undef IODINE_CONNECTION_DEF_CB + +/** Called after the connection was closed (called once per IO). */ +static void iodine_io_raw_on_close(void *buf, void *udata) { + VALUE connection = (VALUE)udata; + if (!connection || connection == Qnil) + return; + iodine_connection_s *c = iodine_connection_ptr(connection); + if (!c) + return; + iodine_ruby_call_outside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_CLOSE_ID, + 1, + &connection); + (void)buf; +} + +static fio_io_protocol_s IODINE_RAW_PROTOCOL = { + .on_attach = iodine_io_raw_on_attach, + .on_data = iodine_io_raw_on_data, + .on_ready = iodine_io_raw_on_ready, + .on_timeout = iodine_io_raw_on_timeout, + .on_shutdown = iodine_io_raw_on_shutdown, + .on_close = iodine_io_raw_on_close, + .on_pubsub = FIO_ON_MESSAGE_SEND_MESSAGE, +}; + +/* ***************************************************************************** +HTTP / WebSockets Callbacks and Helpers +***************************************************************************** */ +/** TODO: fix me */ + +static void *iodine_io_http_on_http_internal(void *h_) { + fio_http_s *h = (fio_http_s *)h_; + VALUE connection = iodine_connection_create_from_http(h); + if (FIO_UNLIKELY(!connection || connection == Qnil)) { + FIO_LOG_FATAL("`on_http` couldn't allocate Iodine::Connection object!"); + return NULL; + } + iodine_connection_s *c = iodine_connection_ptr(connection); + iodine_caller_result_s e = + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_HTTP_ID, + 1, + &connection); + if (e.exception) { + fio_http_send_error_response(h, 500); + } + return NULL; +} + +static void iodine_io_http_on_http(fio_http_s *h) { + VALUE handler = (VALUE)fio_http_udata(h); + if (FIO_UNLIKELY(!handler || handler == Qnil)) { + FIO_LOG_FATAL("`on_http` callback couldn't find handler!"); + fio_http_send_error_response(h, 500); + return; + } + rb_thread_call_with_gvl(iodine_io_http_on_http_internal, (void *)h); +} + +#define IODINE_CONNECTION_DEF_CB(named, id, free_handle) \ + static void iodine_io_http_##named(fio_http_s *h) { \ + VALUE connection = (VALUE)fio_http_udata2(h); \ + if (!connection || connection == Qnil) \ + return; \ + iodine_connection_s *c = iodine_connection_ptr(connection); \ + if (!c) \ + return; \ + iodine_ruby_call_outside(c->store[IODINE_CONNECTION_STORE_handler], \ + id, \ + 1, \ + &connection); \ + if (free_handle) { \ + iodine_ruby_call_outside(c->store[IODINE_CONNECTION_STORE_handler], \ + IODINE_ON_FINISH_ID, \ + 1, \ + &connection); \ + fio_http_free(c->http); \ + c->http = NULL; \ + c->io = NULL; \ + STORE.release(connection); \ + } \ + } + +/** Called when an IO is attached to the protocol. */ +IODINE_CONNECTION_DEF_CB(on_open, IODINE_ON_OPEN_ID, 0); +/** called once all pending `write` calls are finished. */ +IODINE_CONNECTION_DEF_CB(on_ready, IODINE_ON_DRAINED_ID, 0); +/** called if the connection is open when the server is shutting down. */ +IODINE_CONNECTION_DEF_CB(on_shutdown, IODINE_ON_SHUTDOWN_ID, 0); +/** Called after the connection was closed (called once per IO). */ +IODINE_CONNECTION_DEF_CB(on_close, IODINE_ON_CLOSE_ID, 1); + +#undef IODINE_CONNECTION_DEF_CB +#define IODINE_CONNECTION_DEF_CB(named, id, flag) \ + static void *iodine_io_http_##named##_internal(void *h_) { \ + fio_http_s *h = (fio_http_s *)h_; \ + VALUE connection = iodine_connection_create_from_http(h); \ + if (!connection || connection == Qnil) \ + return (void *)-1; \ + iodine_connection_s *c = iodine_connection_ptr(connection); \ + if (!c) \ + return (void *)-1; \ + c->flags |= flag; \ + iodine_caller_result_s r = \ + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], \ + id, \ + 1, \ + &connection); \ + if (!r.exception && r.result == Qtrue) \ + return NULL; \ + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], \ + IODINE_ON_FINISH_ID, \ + 1, \ + &connection); \ + fio_http_free(c->http); \ + c->http = NULL; \ + c->io = NULL; \ + return (void *)-1; \ + } \ + static int iodine_io_http_##named(fio_http_s *h) { \ + if (!rb_thread_call_with_gvl(iodine_io_http_##named##_internal, h)) \ + return 0; \ + return -1; \ + } + +/** Called after the connection was closed (called once per IO). */ +IODINE_CONNECTION_DEF_CB(on_authenticate_sse, + IODINE_ON_AUTHENTICATE_SSE_ID, + IODINE_CONNECTION_UPGRADE_SSE); +/** Called after the connection was closed (called once per IO). */ +IODINE_CONNECTION_DEF_CB(on_authenticate_websocket, + IODINE_ON_AUTHENTICATE_WEBSOCKET_ID, + IODINE_CONNECTION_UPGRADE_WS); + +#undef IODINE_CONNECTION_DEF_CB + +typedef struct { + fio_http_s *h; + fio_buf_info_s id; + fio_buf_info_s event; + fio_buf_info_s data; +} iodine_io_http_on_eventsource_internal_s; + +static void *iodine_io_http_on_eventsource_reconnect_internal(void *info_) { + iodine_io_http_on_eventsource_internal_s *i = + (iodine_io_http_on_eventsource_internal_s *)info_; + VALUE connection = (VALUE)fio_http_udata2(i->h); + if (!connection || connection == Qnil) + return NULL; + iodine_connection_s *c = iodine_connection_ptr(connection); + VALUE args[] = {connection, rb_str_new(i->id.buf, i->id.len)}; + c->store[IODINE_CONNECTION_STORE_tmp] = args[1]; + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_EVENTSOURCE_RECONNECT_ID, + 2, + args); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return NULL; +} + +/** Called when an EventSource reconnect event requests an ID. */ +static void iodine_io_http_on_eventsource_reconnect(fio_http_s *h, + fio_buf_info_s id) { + /* TODO: FIXME! move into GVL and create message struct to pass to callback */ + VALUE connection = (VALUE)fio_http_udata2(h); + if (!connection || connection == Qnil) + return; + iodine_io_http_on_eventsource_internal_s info = {.h = h, .id = id}; + rb_thread_call_with_gvl(iodine_io_http_on_eventsource_reconnect_internal, + &info); +} + +static void *iodine_io_http_on_eventsource_internal(void *info_) { + iodine_io_http_on_eventsource_internal_s *i = + (iodine_io_http_on_eventsource_internal_s *)info_; + VALUE connection = (VALUE)fio_http_udata2(i->h); + if (!connection || connection == Qnil) + return NULL; + iodine_connection_s *c = iodine_connection_ptr(connection); + fio_msg_s msg = { + .channel = i->event, + .message = i->data, + }; + VALUE args[] = {connection, iodine_pubsub_msg_create(&msg)}; + iodine_pubsub_msg_id_set(args[1], rb_str_new(i->id.buf, i->id.len)); + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_EVENTSOURCE_ID, + 2, + args); + STORE.release(args[1]); /* Store.hold(m) called iodine_pubsub_msg_create */ + return NULL; +} +/** Called when an EventSource event is received. */ +static void iodine_io_http_on_eventsource(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data) { + iodine_io_http_on_eventsource_internal_s info = {.h = h, + .id = id, + .event = event, + .data = data}; + rb_thread_call_with_gvl(iodine_io_http_on_eventsource_internal, &info); +} + +typedef struct { + fio_http_s *h; + fio_buf_info_s msg; + uint8_t is_text; +} iodine_io_http_on_message_internal_s; + +/** Called when a websocket / SSE message arrives. */ +static void *iodine_io_http_on_message_internal(void *info) { + iodine_io_http_on_message_internal_s *i = + (iodine_io_http_on_message_internal_s *)info; + VALUE connection = (VALUE)fio_http_udata2(i->h); + if (!connection || connection == Qnil) + return NULL; + iodine_connection_s *c = iodine_connection_ptr(connection); + VALUE args[] = {connection, rb_str_new(i->msg.buf, i->msg.len)}; + c->store[IODINE_CONNECTION_STORE_tmp] = args[1]; + rb_enc_associate(args[1], + i->is_text ? IodineUTF8Encoding : IodineBinaryEncoding); + iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_MESSAGE_ID, + 2, + args); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + return NULL; +} + +/** Called when a websocket / SSE message arrives. */ +static void iodine_io_http_on_message(fio_http_s *h, + fio_buf_info_s msg, + uint8_t is_text) { + iodine_io_http_on_message_internal_s info = { + .h = h, + .msg = msg, + .is_text = is_text, + }; + rb_thread_call_with_gvl(iodine_io_http_on_message_internal, &info); +} + +/** Called when a request / response cycle is finished with no Upgrade. */ +static void iodine_io_http_on_finish(fio_http_s *h) { + VALUE connection = (VALUE)fio_http_udata2(h); + if (!connection || connection == Qnil) + return; /* done in the `on_close` */ + iodine_connection_s *c = iodine_connection_ptr(connection); + if (c->http) { + (((c->flags & IODINE_CONNECTION_CLIENT)) ? iodine_ruby_call_outside + : iodine_ruby_call_inside)( + (iodine_caller_args_s){c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_FINISH_ID, + 1, + &connection}); + fio_http_free(h); + } + c->http = NULL; + c->io = NULL; + STORE.release(connection); +} + +/* ***************************************************************************** +Subscription Helpers +***************************************************************************** */ + +static void *iodine_connection_on_pubsub_in_gvl(void *m_) { + fio_msg_s *m = (fio_msg_s *)m_; + VALUE msg = iodine_pubsub_msg_create(m); + iodine_ruby_call_inside((VALUE)m->udata, IODINE_CALL_ID, 1, &msg); + STORE.release(msg); + return m_; +} + +static void iodine_connection_on_pubsub(fio_msg_s *m) { + /* TODO? move callback to outside queue? */ + rb_thread_call_with_gvl(iodine_connection_on_pubsub_in_gvl, m); +} + +FIO_IFUNC VALUE iodine_connection_subscribe_internal(fio_io_s *io, + int argc, + VALUE *argv) { + fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0); + int64_t filter = 0; + VALUE proc = Qnil; + + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(channel, 0, "channel", 0), + IODINE_ARG_NUM(filter, 0, "filter", 0), + IODINE_ARG_PROC(proc, 0, "callback", 0)); + + if ((size_t)filter & (~(size_t)0xFFFF)) + rb_raise(rb_eRangeError, + "filter out of range (%lld > 0xFFFF)", + (long long)filter); + if (!io && proc == Qnil) + rb_raise(rb_eArgError, + "Global subscriptions require a callback (proc/block) object!"); + STORE.hold(proc); + fio_subscribe(.io = io, + .filter = (int16_t)filter, + .channel = channel, + .udata = (void *)proc, + .on_message = + (!proc || (proc == Qnil) ? NULL + : iodine_connection_on_pubsub), + .on_unsubscribe = (void (*)(void *))STORE.release); + return proc; +} + +static VALUE iodine_connection_unsubscribe_internal(fio_io_s *io, + int argc, + VALUE *argv) { + int64_t filter = 0; + fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0); + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(channel, 0, "channel", 0), + IODINE_ARG_NUM(filter, 0, "filter", 0)); + if ((size_t)filter & (~(size_t)0xFFFF)) + rb_raise(rb_eRangeError, + "filter out of range (%lld > 0xFFFF)", + (long long)filter); + return fio_unsubscribe(.io = io, .channel = channel, .filter = filter) + ? RUBY_Qfalse + : RUBY_Qtrue; +} + +/* ***************************************************************************** +Listening Argument Parsing +***************************************************************************** */ + +FIO_IFUNC iodine_connection_args_s iodine_connection_parse_args(int argc, + VALUE *argv) { + iodine_connection_args_s r = { + .url = fio_cli_get_str("-b"), + .rb_tls = ((fio_cli_get_bool("-tls") || + (fio_cli_get("-cert") && fio_cli_get("-key"))) + ? Qtrue + : Qnil), + .headers = Qnil, + .cookies = Qnil, + .settings = + { + /* defaults */ + .on_http = iodine_io_http_on_http, + .on_stop = iodine_io_http_on_stop, + .on_finish = iodine_io_http_on_finish, + .on_authenticate_sse = iodine_io_http_on_authenticate_sse, + .on_authenticate_websocket = + iodine_io_http_on_authenticate_websocket, + .on_open = iodine_io_http_on_open, + .on_message = iodine_io_http_on_message, + .on_eventsource = iodine_io_http_on_eventsource, + .on_eventsource_reconnect = + iodine_io_http_on_eventsource_reconnect, + .on_ready = iodine_io_http_on_ready, + .on_shutdown = iodine_io_http_on_shutdown, + .on_close = iodine_io_http_on_close, + .queue = &IODINE_THREAD_POOL, + .public_folder = FIO_STR_INFO1((char *)fio_cli_get("-www")), + .max_age = (size_t)fio_cli_get_i("-maxage"), + .max_header_size = (uint32_t)fio_cli_get_i("-maxhd"), + .max_line_len = (uint32_t)fio_cli_get_i("-maxln"), + .max_body_size = (size_t)fio_cli_get_i("-maxbd"), + .ws_max_msg_size = (size_t)fio_cli_get_i("-maxms"), + .timeout = (uint8_t)fio_cli_get_i("-k"), + .ws_timeout = (uint8_t)fio_cli_get_i("-ping"), + .sse_timeout = (uint8_t)fio_cli_get_i("-ping"), + .log = fio_cli_get_bool("-v"), + }, + }; + VALUE proc = Qnil, handler_tmp = Qnil; + iodine_rb2c_arg( + argc, + argv, + IODINE_ARG_BUF(r.url, 0, "url", 0), + IODINE_ARG_RB(handler_tmp, 0, "handler", 0), + IODINE_ARG_BUF(r.hint, 0, "service", 0), + IODINE_ARG_RB(r.rb_tls, 0, "tls", 0), + IODINE_ARG_STR(r.settings.public_folder, 0, "public", 0), + IODINE_ARG_SIZE_T(r.settings.max_age, 0, "max_age", 0), + IODINE_ARG_U32(r.settings.max_header_size, 0, "max_header_size", 0), + IODINE_ARG_U32(r.settings.max_line_len, 0, "max_line_len", 0), + IODINE_ARG_SIZE_T(r.settings.max_body_size, 0, "max_body_size", 0), + IODINE_ARG_SIZE_T(r.settings.ws_max_msg_size, 0, "max_msg_size", 0), + IODINE_ARG_U8(r.settings.timeout, 0, "timeout", 0), + IODINE_ARG_U8(r.settings.ws_timeout, 0, "ping", 0), + IODINE_ARG_U8(r.settings.log, 0, "log", 0), + IODINE_ARG_BUF(r.method, 0, "method", 0), + IODINE_ARG_RB(r.headers, 0, "headers", 0), + IODINE_ARG_BUF(r.body, 0, "body", 0), + IODINE_ARG_RB(r.cookies, 0, "cookies", 0), + IODINE_ARG_PROC(proc, 0, "block", 0)); + r.settings.udata = (void *)handler_tmp; + /* test for errors before allocating or protecting data */ + + if (r.headers != Qnil && !RB_TYPE_P(r.headers, RUBY_T_HASH)) + rb_raise(rb_eTypeError, "headers (if provided) MUST be type Hash"); + if (r.cookies != Qnil && !RB_TYPE_P(r.cookies, RUBY_T_HASH)) + rb_raise(rb_eTypeError, "cookies (if provided) MUST be type Hash"); + + if (!r.settings.udata || (VALUE)r.settings.udata == Qnil) { + r.settings.udata = (void *)proc; + if (proc == Qnil && r.settings.public_folder.buf) + r.settings.udata = (void *)iodine_rb_IODINE_BASE_APP404; + } + if (IODINE_STORE_IS_SKIP(((VALUE)r.settings.udata))) + rb_raise( + rb_eArgError, + "Either a `:handler` or `&block` must be provided and a valid Object!"); + if (r.url.buf) + r.url_data = fio_url_parse(r.url.buf, r.url.len); + if (!r.hint.len) + r.hint = r.url_data.scheme; + if (r.hint.len > 8) + rb_raise(rb_eArgError, "service hint too long"); + + if (!r.hint.buf || !r.hint.len || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"sses", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"sses", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"SSES", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"SSES", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"unixs", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"UNIXS", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"unixs", 5)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"UNIXS", 5)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"files", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"FILES", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"files", 5)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"FILES", 5)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"https", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"HTTPS", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"https", 5)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"HTTPS", 5))) + r.service = IODINE_SERVICE_HTTP; + else if (FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"wss", 2)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"WSS", 2)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"wss", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"WSS", 3))) + r.service = IODINE_SERVICE_WS; + else if (FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"tcps", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"TCPS", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"tcps", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"TCPS", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"raws", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"RAWS", 3)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"raws", 4)) || + FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"RAWS", 4))) + r.service = IODINE_SERVICE_RAW; + else { + rb_raise(rb_eArgError, + "URL scheme or service type hint error!\n\t" + "Must be either https, http, ws, wss, sse, sses, tcp or tcps " + "(case sensitive)."); + r.service = IODINE_SERVICE_UNKNOWN; + } + + /* No errors, start protecting Ruby data from GC */ + STORE.hold((VALUE)r.settings.udata); + /* make sure all proper callbacks are defined. */ + iodine_handler_method_injection(Qnil, (VALUE)r.settings.udata); + + r.settings.sse_timeout = r.settings.ws_timeout; + if (r.rb_tls != Qnil && r.rb_tls != Qfalse) { + rb_ivar_set((VALUE)r.settings.udata, + rb_intern2("__iodine__internal__tls", 23), + r.rb_tls); + + r.settings.tls = + (r.rb_tls == Qtrue ? fio_io_tls_new() + : fio_io_tls_dup(iodine_tls_get(r.rb_tls))); + if (r.rb_tls == Qtrue && fio_cli_get("-cert") && fio_cli_get("-key")) + fio_io_tls_cert_add(r.settings.tls, + fio_cli_get("-name"), + fio_cli_get("-cert"), + fio_cli_get("-key"), + fio_cli_get("-tls-pass")); + } + + fio_cli_set("-b", NULL); + return r; +} + +/* ***************************************************************************** +Ruby Public API. +***************************************************************************** */ + +static void iodine_connection___client_headers_add(fio_http_s *h, + fio_str_info_s n, + fio_str_info_s v) { + char *eol = (char *)memchr(v.buf, '\n', v.len); + while (eol) { + ++eol; + fio_str_info_s tmp = FIO_STR_INFO2(v.buf, (size_t)(eol - v.buf)); + fio_http_request_header_add(h, n, tmp); + v.buf = eol; + v.len -= tmp.len; + } + fio_http_request_header_add(h, n, v); +} + +static int iodine_connection___client_headers(VALUE n, VALUE v, VALUE h_) { + fio_http_s *h = (fio_http_s *)h_; + if (RB_TYPE_P(v, RUBY_T_ARRAY)) + goto is_array; + if (!RB_TYPE_P(v, RUBY_T_STRING)) + return ST_CONTINUE; + iodine_connection___client_headers_add(h, + (fio_str_info_s)IODINE_RSTR_INFO(n), + (fio_str_info_s)IODINE_RSTR_INFO(v)); + return ST_CONTINUE; +is_array: + for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) { + VALUE t = RARRAY_PTR(v)[i]; + if (!RB_TYPE_P(t, RUBY_T_STRING)) + continue; + iodine_connection___client_headers_add(h, + (fio_str_info_s)IODINE_RSTR_INFO(n), + (fio_str_info_s)IODINE_RSTR_INFO(t)); + } + return ST_CONTINUE; +} + +static int iodine_connection___client_cookie(VALUE n, VALUE v, VALUE h_) { + fio_http_s *h = (fio_http_s *)h_; + if (!RB_TYPE_P(n, RUBY_T_STRING) || !RB_TYPE_P(v, RUBY_T_STRING)) + goto not_string; + fio_http_cookie_set(h, + .name = IODINE_RSTR_INFO(n), + .value = IODINE_RSTR_INFO(v)); + return ST_CONTINUE; + +not_string: + FIO_LOG_WARNING("Client cookie name and value MUST be type String"); + return ST_CONTINUE; +} + +/** Called after the connection was closed (called once per IO). */ +static void iodine_io_raw_client_on_close(void *buf, void *udata) { + VALUE connection = (VALUE)udata; + if (!connection || connection == Qnil) + return; + iodine_connection_s *c = iodine_connection_ptr(connection); + if (c) { + iodine_ruby_call_outside(c->store[IODINE_CONNECTION_STORE_handler], + IODINE_ON_CLOSE_ID, + 1, + &connection); + fio_io_protocol_s *p = fio_io_protocol(c->io); + FIO_MEM_FREE(p, sizeof(*p)); + } + STORE.release(connection); + (void)buf; +} + +/** Initializes a Connection object. */ +static VALUE iodine_connection_initialize(int argc, VALUE *argv, VALUE self) { + // if (!argc) + // rb_raise(rb_eException, "Iodine::Connection.new shouldn't be called!"); + STORE.hold(self); + iodine_connection_s *c = iodine_connection_ptr(self); + iodine_connection_args_s args = iodine_connection_parse_args(argc, argv); + if (!args.url.len) + rb_raise( + rb_eException, + "Iodine::Connection.new client requires a valid URL to connect to!\n\t" + "See documentation, use tcp(s)://.../ or http(s)://.../ or " + "ws(s):/.../ etc'"); + /* test if HTTP scheme */ + if (args.service == IODINE_SERVICE_HTTP || + args.service == IODINE_SERVICE_WS) { + + fio_http_s *h = fio_http_new(); + + if (args.headers != Qnil) + rb_hash_foreach(args.headers, + iodine_connection___client_headers, + (VALUE)h); + if (args.cookies != Qnil) + rb_hash_foreach(args.cookies, + iodine_connection___client_cookie, + (VALUE)h); + // args.settings.on_finish = + c->io = fio_http_connect FIO_NOOP(args.url.buf, h, args.settings); + c->http = h; + fio_http_udata2_set(h, (void *)self); + + } else { /* Raw Connection */ + rb_raise(rb_eException, + "Iodine::Connection.new using Raw Sockets is on the TODO list..."); + fio_io_protocol_s *protocol = + FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); + FIO_ASSERT_ALLOC(protocol); + *protocol = IODINE_RAW_PROTOCOL; + protocol->on_close = iodine_io_raw_client_on_close; + protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout; + c->io = fio_io_connect(args.url.buf, + .protocol = protocol, + .udata = args.settings.udata, + .tls = args.settings.tls, + .on_failed = iodine_tcp_on_stop); + } + c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)args.settings.udata; + c->flags |= IODINE_CONNECTION_CLIENT; + return self; +} + +/** Returns true if the connection appears to be open (no known issues). */ +static VALUE iodine_connection_is_open(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (c->http) + return ((fio_http_is_websocket(c->http) && !fio_http_is_finished(c->http)) + ? Qfalse + : Qtrue); + if (c->io) + return (fio_io_is_open(c->io) ? Qtrue : Qfalse); + return Qfalse; +} + /** - * Creates a new connection object. + * Returns the number of bytes that need to be sent before the next + * `on_drained` callback is called. */ -VALUE iodine_connection_new(iodine_connection_s args); -#define iodine_connection_new(...) \ - iodine_connection_new((iodine_connection_s){__VA_ARGS__}) +static VALUE iodine_connection_pending(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (c && c->io) + return RB_SIZE2NUM(((size_t)fio_io_backlog(c->io))); + return Qfalse; +} -typedef enum { - IODINE_CONNECTION_ON_OPEN, - IODINE_CONNECTION_ON_MESSAGE, - IODINE_CONNECTION_ON_DRAINED, - IODINE_CONNECTION_PING, - IODINE_CONNECTION_ON_SHUTDOWN, - IODINE_CONNECTION_ON_CLOSE -} iodine_connection_event_type_e; +/** Schedules the connection to be closed. */ +static VALUE iodine_connection_close(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (c) { + if (c->http) { + /* avoid `rack.input` call to `close` closing the HTTP connection. */ + if (fio_http_is_upgraded(c->http)) + fio_http_close(c->http); + } else if (c->io) + fio_io_close(c->io); + c->flags |= IODINE_CONNECTION_CLOSED; + } + return self; +} /** - * Fires a connection object's event. `data` is only for the on_message event. + * @deprecated use `Server.extensions[:pubsub]` instead. + * + * Always returns true, since Iodine connections support the pub/sub + * extension. */ -void iodine_connection_fire_event(VALUE connection, - iodine_connection_event_type_e ev, - VALUE data); +static VALUE iodine_connection_has_pubsub(VALUE self) { + FIO_LOG_WARNING( + "pubsub? is deprecated. Test using `Server.extensions[:pubsub]` or " + "`#respond_to?(:subscribe)` instead."); + return Qtrue; +} -/** Initializes the Connection Ruby class. */ -void iodine_connection_init(void); +/** Writes data to the connection asynchronously. */ +static VALUE iodine_connection_write_header___value(fio_http_s *h, + fio_str_info_s n, + VALUE v, + size_t depth) { + VALUE r = Qtrue; + FIO_STR_INFO_TMP_VAR(vstr, 24); -extern const rb_data_type_t iodine_connection_data_type; + if (depth > 31) + goto too_deep; + if (v == Qnil || v == Qfalse) + goto skip; -static inline iodine_connection_s *iodine_connection_CData(VALUE self) { - iodine_connection_s *c = NULL; - TypedData_Get_Struct(self, iodine_connection_s, &iodine_connection_data_type, - c); - return c; + if (RB_TYPE_P(v, RUBY_T_ARRAY)) + goto is_array; + if (RB_TYPE_P(v, RUBY_T_SYMBOL)) + v = rb_sym_to_s(v); + if (RB_TYPE_P(v, RUBY_T_STRING)) + vstr = (fio_str_info_s)IODINE_RSTR_INFO(v); + else if (RB_TYPE_P(v, RUBY_T_FIXNUM)) + fio_string_write_i(&vstr, NULL, (int64_t)RB_NUM2LL(v)); + else + goto type_error; + iodine_connection___add_header(h, n, vstr); + return r; + +is_array: + for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) { + VALUE t = RARRAY_PTR(v)[i]; + iodine_connection_write_header___value(h, n, t, depth + 1); + } + return r; + +type_error: + r = Qfalse; + rb_raise(rb_eTypeError, + "write_header called with a non-String header value(s)."); + return r; + +too_deep: + r = Qfalse; + rb_raise(rb_eTypeError, + "write_header called with deeply nested Arrays. Nesting should be " + "avoided."); + return r; + +skip: + return r; +} + +/** Writes data to the connection asynchronously. */ +static VALUE iodine_connection_write_header(VALUE self, VALUE name, VALUE v) { + VALUE r = Qfalse; + iodine_connection_s *c = iodine_connection_ptr(self); + fio_http_s *h = c->http; + if (!fio_http_is_clean(h)) + return r; + + fio_str_info_s header_rb; + + if (RB_TYPE_P(name, RUBY_T_SYMBOL)) + name = rb_sym_to_s(name); + if (!RB_TYPE_P(name, RUBY_T_STRING)) + goto type_error; + + c->store[IODINE_CONNECTION_STORE_tmp] = name; + + header_rb = (fio_str_info_s)IODINE_RSTR_INFO(name); + IODINE___COPY_TO_LOWER_CASE(n, header_rb); + c->store[IODINE_CONNECTION_STORE_tmp] = Qnil; + + if (v == Qnil || v == Qfalse) + goto clear_header; + + r = iodine_connection_write_header___value(h, n, v, 0); + return r; + +clear_header: + fio_http_response_header_set(h, n, FIO_STR_INFO0); + return r; + +type_error: + r = Qfalse; + rb_raise(rb_eTypeError, "write_header called with non-String header name."); + return r; +} + +/** Writes data to the connection asynchronously. */ +FIO_IFUNC VALUE iodine_connection_write_internal(VALUE self, + VALUE data, + _Bool finish) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c || (!c->http && !c->io)) + return Qfalse; + fio_str_info_s to_write; + unsigned to_copy = 1; + int fileno; + void (*dealloc)(void *) = NULL; + + /* TODO! SSE connections? id / event / data combo? */ + + if (RB_TYPE_P(data, RUBY_T_SYMBOL)) + data = rb_sym_to_s(data); + if (RB_TYPE_P(data, RUBY_T_STRING)) { + to_write = FIO_STR_INFO2(RSTRING_PTR(data), (size_t)RSTRING_LEN(data)); + // TODO: use Ruby encoding info for WebSocket? + // fio_http_websocket_write(c->http, to_write.buf, len, is_text) + } else if (rb_respond_to(data, IODINE_FILENO_ID)) { + goto is_file; + } else { + dealloc = (void (*)(void *))fio_bstr_free; + to_copy = 0; + to_write = fio_bstr_info(iodine_json_stringify2bstr(NULL, data)); + } + if (c->http) + fio_http_write(c->http, + .buf = to_write.buf, + .len = to_write.len, + .dealloc = dealloc, + .copy = to_copy, + .finish = finish); + else if (c->io) { + fio_io_write2(c->io, + .buf = to_write.buf, + .len = to_write.len, + .dealloc = dealloc, + .copy = to_copy); + if (finish) + fio_io_close(c->io); + } + return Qtrue; + +is_file: + fileno = fio_file_dup(FIX2INT(rb_funcallv(data, IODINE_FILENO_ID, 0, NULL))); + if (rb_respond_to(data, IODINE_CLOSE_ID)) + rb_funcallv(data, IODINE_CLOSE_ID, 0, NULL); + if (fileno == -1) + return Qfalse; + if (c->http) + fio_http_write(c->http, .fd = fileno, .finish = finish); + else if (c->io) { + fio_io_write2(c->io, .fd = fileno); + if (finish) + fio_io_close(c->io); + } + return Qtrue; +} + +/** Writes data to the connection asynchronously. */ +static VALUE iodine_connection_write(VALUE self, VALUE data) { + return iodine_connection_write_internal(self, data, 0); +} + +/** Writes data to the connection asynchronously. */ +static VALUE iodine_connection_write_sse(int argc, VALUE *argv, VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c || !c->http) + return Qfalse; + fio_buf_info_s to_write; + fio_buf_info_s id = {0}, event = {0}; + VALUE data = Qnil; + void (*dealloc)(void *) = NULL; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(id, 0, "id", 0), + IODINE_ARG_BUF(event, 0, "event", 0), + IODINE_ARG_RB(data, 0, "data", 0)); + if (data == Qnil) + return Qfalse; /* no data payload */ + if (RB_TYPE_P(data, RUBY_T_SYMBOL)) + data = rb_sym_to_s(data); + if (RB_TYPE_P(data, RUBY_T_STRING)) { + to_write = (fio_buf_info_s)IODINE_RSTR_INFO(data); + } else if (rb_respond_to(data, IODINE_CLOSE_ID)) { + rb_funcallv(data, IODINE_CLOSE_ID, 0, NULL); + return Qfalse; + } else if (fio_http_is_sse(c->http)) { + dealloc = (void (*)(void *))fio_bstr_free; + to_write = fio_bstr_buf(iodine_json_stringify2bstr(NULL, data)); + } + /* don't test `fio_http_is_sse` earlier, as we may need to close files */ + if (to_write.len && fio_http_is_sse(c->http)) + fio_http_sse_write(c->http, .id = id, .event = event, .data = to_write); + if (dealloc) + dealloc(to_write.buf); + return Qtrue; +} + +/** Writes data to the connection asynchronously. */ +static VALUE iodine_connection_finish(int argc, VALUE *argv, VALUE self) { + VALUE data = Qnil; + iodine_rb2c_arg(argc, argv, IODINE_ARG_RB(data, 0, "data", 0)); + if (data != Qnil) + return iodine_connection_write_internal(self, data, 1); + iodine_connection_s *c = iodine_connection_ptr(self); + if ((!c->http && !c->io) || (c->flags & IODINE_CONNECTION_CLIENT)) + return Qfalse; + if (c->http) { + if (fio_http_is_finished(c->http)) + return Qfalse; + fio_http_write(c->http, .finish = 1); + } else if (c->io) { + if (!fio_io_is_open(c->io)) + return Qfalse; + fio_io_close(c->io); + } + return Qtrue; +} + +/** Returns the peer's network address or `nil`. */ +static VALUE iodine_connection_peer_addr(VALUE self) { + fio_buf_info_s buf = FIO_BUF_INFO0; + iodine_connection_s *c = iodine_connection_ptr(self); + if (c->io) + buf = fio_sock_peer_addr(fio_io_fd(c->io)); + if (!buf.len) + return Qnil; + return rb_str_new(buf.buf, buf.len); } -#endif +/** Returns the published peer's address. If unknown, returns {#peer_addr}. */ +static VALUE iodine_connection_from(VALUE self) { + FIO_STR_INFO_TMP_VAR(addr, 128); + fio_buf_info_s buf = FIO_BUF_INFO0; + iodine_connection_s *c = iodine_connection_ptr(self); + if (c->http) { + fio_http_from(&addr, c->http); + buf = FIO_STR2BUF_INFO(addr); + } else if (c->io) + buf = fio_sock_peer_addr(fio_io_fd(c->io)); + if (!buf.len) + return Qnil; + return rb_str_new(buf.buf, buf.len); +} + +/* ***************************************************************************** +Ruby Public API - Hijacking (Rack) +***************************************************************************** */ + +/** Method for calling `env['rack.hijack'].call`. */ +static VALUE iodine_connection_rack_hijack(VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c->http) + return Qnil; + int new_fd = fio_sock_dup(fio_io_fd(fio_http_io(c->http))); + VALUE nio = rb_io_fdopen(new_fd, O_RDWR, NULL); + if (nio && nio != Qnil) { + fio_http_close(c->http); + fio_http_free(c->http); + c->http = NULL; + c->io = NULL; + if (c->store[IODINE_CONNECTION_STORE_env] && + RB_TYPE_P(c->store[IODINE_CONNECTION_STORE_env], RUBY_T_HASH)) { + rb_hash_aset(c->store[IODINE_CONNECTION_STORE_env], + STORE.frozen_str(FIO_STR_INFO1((char *)"rack.hijack_io")), + nio); + } + } else { + fio_sock_close(new_fd); + } + return nio; +} + +/* ***************************************************************************** +Ruby Public API - Pub/Sub +***************************************************************************** */ + +/** + * Un-Subscribes to a combination of a channel (String) and filter (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the subscription's channel name (String). + * - `filter`: the subscription's filter (Number < 32,767). + * + * i.e.: + * + * unsubscribe("name") + * unsubscribe(channel: "name") + * # or with filter + * unsubscribe("name", 1) + * unsubscribe(channel: "name", filter: 1) + * # or only a filter + * unsubscribe(nil, 1) + * unsubscribe(filter: 1) + * + */ +static VALUE iodine_connection_unsubscribe(int argc, VALUE *argv, VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c || (!c->http && !c->io)) + return Qnil; + return iodine_connection_unsubscribe_internal(c->io, argc, argv); +} + +/** + * Un-Subscribes to a combination of a channel (String) and filter (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the subscription's channel name (String). + * - `filter`: the subscription's filter (Number < 32,767). + * + * i.e.: + * + * unsubscribe("name") + * unsubscribe(channel: "name") + * # or with filter + * unsubscribe("name", 1) + * unsubscribe(channel: "name", filter: 1) + * # or only a filter + * unsubscribe(nil, 1) + * unsubscribe(filter: 1) + * + */ +static VALUE iodine_connection_unsubscribe_klass(int argc, + VALUE *argv, + VALUE self) { + return iodine_connection_unsubscribe_internal(NULL, argc, argv); +} + +/** + * Subscribes to a combination of a channel (String) and filter (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the subscription's channel name (String). + * - `filter`: the subscription's filter (Number < 32,767). + * - `callback`: an optional object that answers to `call` and accepts a + * single argument (the message structure). + * + * The message structure (the argument passed to `proc`) answers to the + * following: + * + * - `id`: the message's (somewhat unique) ID (Number). + * - `channel`: the message's target channel name (String or `nil`). + * - `filter`: the message's target filter (Number < 32,767). + * - `message`: the message's content (String or `nil`). + * - `published`: a Unix timestamp in Milliseconds (Number). + * + * i.e.: + * + * subscribe("name") + * subscribe(channel: "name") + * # or with filter + * subscribe("name", 1) + * subscribe(channel: "name", filter: 1) + * # or only a filter + * subscribe(nil, 1) + * subscribe(filter: 1) + * # with / without a callback + * subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == Qnil; } + * subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; } + * + */ +static VALUE iodine_connection_subscribe(int argc, VALUE *argv, VALUE self) { + iodine_connection_s *c = iodine_connection_ptr(self); + if (!c || (!c->http && !c->io)) + return Qnil; + if (c->io) + iodine_connection_subscribe_internal(c->io, argc, argv); + return self; +} + +/** + * Subscribes to a combination of a channel (String) and filter (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the subscription's channel name (String). + * - `filter`: the subscription's filter (Number < 32,767). + * - `proc`: an optional object that answers to `call` and accepts a single + * argument (the message structure). + * + * Either a `proc` or a `block` MUST be provided for global subscriptions. + * + * i.e.: + * + * Iodine.subscribe("name") + * Iodine.subscribe(channel: "name") + * # or with filter + * Iodine.subscribe("name", 1) + * Iodine.subscribe(channel: "name", filter: 1) + * # or only a filter + * Iodine.subscribe(nil, 1) + * Iodine.subscribe(filter: 1) + * # with / without a proc + * Iodine.subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == + * Qnil;} Iodine.subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; } + * + */ +static VALUE iodine_connection_subscribe_klass(int argc, + VALUE *argv, + VALUE self) { + iodine_connection_subscribe_internal(NULL, argc, argv); + return self; +} + +static VALUE iodine_connection_publish_internal(fio_io_s *io, + int argc, + VALUE *argv, + VALUE self) { + VALUE message = Qnil; + VALUE engine = Qnil; + int64_t filter = 0; + fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0); + fio_buf_info_s msg = FIO_BUF_INFO2(NULL, 0); + char *to_free = NULL; + fio_pubsub_engine_s *eng = NULL; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(channel, 0, "channel", 0), + IODINE_ARG_RB(message, 0, "message", 0), + IODINE_ARG_NUM(filter, 0, "filter", 0), + IODINE_ARG_RB(engine, 0, "engine", 0)); + if ((size_t)filter & (~(size_t)0xFFFF)) + rb_raise(rb_eRangeError, + "filter out of range (%lld > 0xFFFF)", + (long long)filter); + if (RB_TYPE_P(message, RUBY_T_SYMBOL)) + message = rb_sym_to_s(message); + if (RB_TYPE_P(message, RUBY_T_STRING)) { + msg = FIO_BUF_INFO2(RSTRING_PTR(message), (size_t)RSTRING_LEN(message)); + } else { + to_free = iodine_json_stringify2bstr(NULL, message); + msg = fio_bstr_buf(to_free); + } + if (engine != Qnil) + eng = iodine_pubsub_eng_get(engine)->ptr; + fio_publish(.from = io, + .filter = (int16_t)filter, + .channel = channel, + .message = msg, + .engine = eng); + fio_bstr_free(to_free); + return self; +} +/** + * Publishes a message to a combination of a channel (String) and filter + * (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the channel name to publish to (String). + * - `filter`: the filter to publish to (Number < 32,767). + * - `message`: the actual message to publish. + * - `engine`: the pub/sub engine to use (if not the default one). + * + * i.e.: + * + * Iodine.publish("channel_name", 0, "payload") + * Iodine.publish(channel: "name", message: "payload") + */ +static VALUE iodine_connection_publish(int argc, VALUE *argv, VALUE self) { + return iodine_connection_publish_internal(iodine_connection_ptr(self)->io, + argc, + argv, + self); +} + +/** + * Publishes a message to a combination of a channel (String) and filter + * (number). + * + * Accepts the following (possibly named) arguments and an optional block: + * + * - `channel`: the channel name to publish to (String). + * - `filter`: the filter to publish to (Number < 32,767). + * - `message`: the actual message to publish. + * - `engine`: the pub/sub engine to use (if not the default one). + * + * i.e.: + * + * Iodine.publish("channel_name", 0, "payload") + * Iodine.publish(channel: "name", message: "payload") + */ +static VALUE iodine_connection_publish_klass(int argc, + VALUE *argv, + VALUE self) { + return iodine_connection_publish_internal(NULL, argc, argv, self); +} + +/* ***************************************************************************** +Listen to incoming TCP/IP Connections +***************************************************************************** */ + +static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata) { + STORE.release((VALUE)udata); + FIO_MEM_FREE(p, sizeof(*p)); +} + +static void *iodine_tcp_listen(iodine_connection_args_s args) { + fio_io_protocol_s *protocol = FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); + if (!protocol) + return NULL; + STORE.hold((VALUE)args.settings.udata); + *protocol = IODINE_RAW_PROTOCOL; + protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout; + + return fio_io_listen(.url = args.url.buf, + .protocol = protocol, + .udata = args.settings.udata, + .tls = args.settings.tls, + .on_stop = iodine_tcp_on_stop, + .queue_for_accept = 0); +} + +/* ***************************************************************************** +Listen to incoming HTTP Connections +***************************************************************************** */ + +static void iodine_io_http_on_stop(struct fio_http_settings_s *s) { + STORE.release((VALUE)s->udata); +} + +/* ***************************************************************************** +Listen function routing +***************************************************************************** */ + +static VALUE iodine_listen_rb(int argc, VALUE *argv, VALUE self) { + iodine_connection_args_s s = iodine_connection_parse_args(argc, argv); + void *listener = NULL; + switch (s.service) { + case IODINE_SERVICE_RAW: listener = iodine_tcp_listen(s); break; + case IODINE_SERVICE_HTTP: /* fall through */ + case IODINE_SERVICE_UNKNOWN: /* fall through */ + case IODINE_SERVICE_WS: + s.settings.on_stop = NULL; /* leak the memory, better than accidental.. */ + listener = fio_http_listen FIO_NOOP(s.url.buf, s.settings); + } + fio_io_tls_free(s.settings.tls); + if (!listener) + rb_raise(rb_eRuntimeError, "Couldn't open listening socket."); + if (fio_io_listener_is_tls(listener)) + iodine_env_set_key_pair(IODINE_CONNECTION_ENV_TEMPLATE, + FIO_STR_INFO2((char *)"rack.url_scheme", 15), + FIO_STR_INFO2((char *)"https", 5)); + return (VALUE)s.settings.udata; + (void)self; +} + +/* ***************************************************************************** +Initialize Connection related STORE Cache +***************************************************************************** */ + +static void iodine_connection_cache_common_strings(void) { + fio_str_info_s common_headers[] = { + FIO_STR_INFO1((char *)""), + FIO_STR_INFO1((char *)"a-im"), + FIO_STR_INFO1((char *)"accept"), + FIO_STR_INFO1((char *)"accept-ch"), + FIO_STR_INFO1((char *)"accept-charset"), + FIO_STR_INFO1((char *)"accept-datetime"), + FIO_STR_INFO1((char *)"accept-encoding"), + FIO_STR_INFO1((char *)"accept-language"), + FIO_STR_INFO1((char *)"accept-patch"), + FIO_STR_INFO1((char *)"accept-ranges"), + FIO_STR_INFO1((char *)"access-control-allow-credentials"), + FIO_STR_INFO1((char *)"access-control-allow-headers"), + FIO_STR_INFO1((char *)"access-control-allow-methods"), + FIO_STR_INFO1((char *)"access-control-allow-origin"), + FIO_STR_INFO1((char *)"access-control-expose-headers"), + FIO_STR_INFO1((char *)"access-control-max-age"), + FIO_STR_INFO1((char *)"access-control-request-headers"), + FIO_STR_INFO1((char *)"access-control-request-method"), + FIO_STR_INFO1((char *)"age"), + FIO_STR_INFO1((char *)"allow"), + FIO_STR_INFO1((char *)"alt-svc"), + FIO_STR_INFO1((char *)"authorization"), + FIO_STR_INFO1((char *)"cache-control"), + FIO_STR_INFO1((char *)"common"), + FIO_STR_INFO1((char *)"connection"), + FIO_STR_INFO1((char *)"content-disposition"), + FIO_STR_INFO1((char *)"content-encoding"), + FIO_STR_INFO1((char *)"content-language"), + FIO_STR_INFO1((char *)"content-length"), + FIO_STR_INFO1((char *)"content-location"), + FIO_STR_INFO1((char *)"content-md5"), + FIO_STR_INFO1((char *)"content-range"), + FIO_STR_INFO1((char *)"content-security-policy"), + FIO_STR_INFO1((char *)"content-type"), + FIO_STR_INFO1((char *)"cookie"), + FIO_STR_INFO1((char *)"correlates"), + FIO_STR_INFO1((char *)"date"), + FIO_STR_INFO1((char *)"delta-base"), + FIO_STR_INFO1((char *)"dnt"), + FIO_STR_INFO1((char *)"etag"), + FIO_STR_INFO1((char *)"example"), + FIO_STR_INFO1((char *)"expect"), + FIO_STR_INFO1((char *)"expect-ct"), + FIO_STR_INFO1((char *)"expires"), + FIO_STR_INFO1((char *)"field"), + FIO_STR_INFO1((char *)"forwarded"), + FIO_STR_INFO1((char *)"from"), + FIO_STR_INFO1((char *)"front-end-https"), + FIO_STR_INFO1((char *)"host"), + FIO_STR_INFO1((char *)"http2-settings"), + FIO_STR_INFO1((char *)"if-match"), + FIO_STR_INFO1((char *)"if-modified-since"), + FIO_STR_INFO1((char *)"if-none-match"), + FIO_STR_INFO1((char *)"if-range"), + FIO_STR_INFO1((char *)"if-unmodified-since"), + FIO_STR_INFO1((char *)"im"), + FIO_STR_INFO1((char *)"last-modified"), + FIO_STR_INFO1((char *)"link"), + FIO_STR_INFO1((char *)"location"), + FIO_STR_INFO1((char *)"mandatory"), + FIO_STR_INFO1((char *)"max-forwards"), + FIO_STR_INFO1((char *)"must"), + FIO_STR_INFO1((char *)"nel"), + FIO_STR_INFO1((char *)"only"), + FIO_STR_INFO1((char *)"origin"), + FIO_STR_INFO1((char *)"p3p"), + FIO_STR_INFO1((char *)"permanent"), + FIO_STR_INFO1((char *)"permissions-policy"), + FIO_STR_INFO1((char *)"pragma"), + FIO_STR_INFO1((char *)"prefer"), + FIO_STR_INFO1((char *)"preference-applied"), + FIO_STR_INFO1((char *)"proxy-authenticate"), + FIO_STR_INFO1((char *)"proxy-authorization"), + FIO_STR_INFO1((char *)"proxy-connection"), + FIO_STR_INFO1((char *)"public-key-pins"), + FIO_STR_INFO1((char *)"range"), + FIO_STR_INFO1((char *)"referer"), + FIO_STR_INFO1((char *)"refresh"), + FIO_STR_INFO1((char *)"report-to"), + FIO_STR_INFO1((char *)"response"), + FIO_STR_INFO1((char *)"retry-after"), + FIO_STR_INFO1((char *)"rfc"), + FIO_STR_INFO1((char *)"save-data"), + FIO_STR_INFO1((char *)"sec-ch-ua"), + FIO_STR_INFO1((char *)"sec-ch-ua-mobile"), + FIO_STR_INFO1((char *)"sec-ch-ua-platform"), + FIO_STR_INFO1((char *)"sec-fetch-dest"), + FIO_STR_INFO1((char *)"sec-fetch-mode"), + FIO_STR_INFO1((char *)"sec-fetch-site"), + FIO_STR_INFO1((char *)"sec-fetch-user"), + FIO_STR_INFO1((char *)"sec-gpc"), + FIO_STR_INFO1((char *)"server"), + FIO_STR_INFO1((char *)"set-cookie"), + FIO_STR_INFO1((char *)"standard"), + FIO_STR_INFO1((char *)"status"), + FIO_STR_INFO1((char *)"strict-transport-security"), + FIO_STR_INFO1((char *)"te"), + FIO_STR_INFO1((char *)"timing-allow-origin"), + FIO_STR_INFO1((char *)"tk"), + FIO_STR_INFO1((char *)"trailer"), + FIO_STR_INFO1((char *)"transfer-encoding"), + FIO_STR_INFO1((char *)"upgrade"), + FIO_STR_INFO1((char *)"upgrade-insecure-requests"), + FIO_STR_INFO1((char *)"user-agent"), + FIO_STR_INFO1((char *)"vary"), + FIO_STR_INFO1((char *)"via"), + FIO_STR_INFO1((char *)"warning"), + FIO_STR_INFO1((char *)"when"), + FIO_STR_INFO1((char *)"www-authenticate"), + FIO_STR_INFO1((char *)"x-att-deviceid"), + FIO_STR_INFO1((char *)"x-content-duration"), + FIO_STR_INFO1((char *)"x-content-security-policy"), + FIO_STR_INFO1((char *)"x-content-type-options"), + FIO_STR_INFO1((char *)"x-correlation-id"), + FIO_STR_INFO1((char *)"x-csrf-token"), + FIO_STR_INFO1((char *)"x-forwarded-for"), + FIO_STR_INFO1((char *)"x-forwarded-host"), + FIO_STR_INFO1((char *)"x-forwarded-proto"), + FIO_STR_INFO1((char *)"x-frame-options"), + FIO_STR_INFO1((char *)"x-http-method-override"), + FIO_STR_INFO1((char *)"x-powered-by"), + FIO_STR_INFO1((char *)"x-redirect-by"), + FIO_STR_INFO1((char *)"x-request-id"), + FIO_STR_INFO1((char *)"x-requested-with"), + FIO_STR_INFO1((char *)"x-ua-compatible"), + FIO_STR_INFO1((char *)"x-uidh"), + FIO_STR_INFO1((char *)"x-wap-profile"), + FIO_STR_INFO1((char *)"x-webkit-csp"), + FIO_STR_INFO1((char *)"x-xss-protection"), + {0}, + }; + for (size_t i = 0; common_headers[i].buf; ++i) { + STORE.release(STORE.frozen_str(common_headers[i])); + STORE.release(STORE.header_name(common_headers[i])); + } +} +/* ***************************************************************************** +Initialize Connection Class +***************************************************************************** */ + +/* Initialize Iodine::Connection */ // clang-format off +/** + * Iodine::Connection is the connection instance class used for all + * connection callbacks to control the state of the connection. + */ +static void Init_Iodine_Connection(void) { + rb_define_singleton_method(iodine_rb_IODINE_BASE, + "add_missing_handlar_methods", + iodine_handler_method_injection, 1); + VALUE m = iodine_rb_IODINE_CONNECTION = rb_define_class_under(iodine_rb_IODINE, "Connection", rb_cObject); + rb_define_alloc_func(m, iodine_connection_alloc); + STORE.hold(iodine_rb_IODINE_CONNECTION); + IODINE_CONST_ID_STORE(IODINE_SAME_SITE_DEFAULT, "default"); + IODINE_CONST_ID_STORE(IODINE_SAME_SITE_NONE, "none"); + IODINE_CONST_ID_STORE(IODINE_SAME_SITE_LAX, "lax"); + IODINE_CONST_ID_STORE(IODINE_SAME_SITE_STRICT, "strict"); + + rb_define_method(m, "initialize", iodine_connection_initialize, -1); + + #define IODINE_DEFINE_GETSET_METHOD(name)\ + rb_define_method(m, #name, iodine_connection_##name##_get, 0);\ + rb_define_method(m, #name "=", iodine_connection_##name##_set, 1)\ + + IODINE_DEFINE_GETSET_METHOD(env); + IODINE_DEFINE_GETSET_METHOD(handler); + IODINE_DEFINE_GETSET_METHOD(status); + IODINE_DEFINE_GETSET_METHOD(method); + IODINE_DEFINE_GETSET_METHOD(path); + IODINE_DEFINE_GETSET_METHOD(query); + IODINE_DEFINE_GETSET_METHOD(version); + #undef IODINE_DEFINE_GETSET_METHOD + + + + rb_define_method(m, "headers_sent?", iodine_connection_headers_sent_p, 0); + rb_define_method(m, "valid?", iodine_connection_is_clean, 0); + rb_define_method(m, "dup", iodine_connection_dup_failer, 0); + rb_define_method(m, "to_s", iodine_connection_to_s, 0); + + rb_define_method(m, "[]", iodine_connection_map_get, 1); + rb_define_method(m, "[]=", iodine_connection_map_set, 2); + + rb_define_method(m, "each", iodine_connection_map_each, 0); + rb_define_method(m, "store_count", iodine_connection_map_count, 0); + rb_define_method(m, "store_capa", iodine_connection_map_capa, 0); + rb_define_method(m, "store_reserve", iodine_connection_map_reserve, 1); + + rb_define_method(m, "headers", iodine_connection_map_headers, 0); + rb_define_method(m, "rack_hijack", iodine_connection_rack_hijack, 0); + + rb_define_method(m, "cookie", iodine_connection_cookie_get, 1); + rb_define_method(m, "set_cookie", iodine_connection_cookie_set, -1); + rb_define_method(m, "each_cookie", iodine_connection_cookie_each, 0); + + rb_define_method(m, "length", iodine_connection_body_length, 0); + rb_define_method(m, "gets", iodine_connection_body_gets, -1); + rb_define_method(m, "read", iodine_connection_body_read, -1); + rb_define_method(m, "seek", iodine_connection_body_seek, -1); + rb_define_method(m, "rewind", iodine_connection_body_seek, -1); + + rb_define_method(m, "open?", iodine_connection_is_open, 0); + rb_define_method(m, "pending", iodine_connection_pending, 0); + rb_define_method(m, "close", iodine_connection_close, 0); + rb_define_method(m, "write_header", iodine_connection_write_header, 2); + rb_define_method(m, "write", iodine_connection_write, 1); + rb_define_method(m, "write_sse", iodine_connection_write_sse, -1); + rb_define_method(m, "finish", iodine_connection_finish, -1); + + + rb_define_method(m, "websocket?", iodine_connection_is_websocket, 0); + rb_define_method(m, "sse?", iodine_connection_is_sse, 0); + + rb_define_method(m, "peer_addr", iodine_connection_peer_addr, 0); + rb_define_method(m, "from", iodine_connection_from, 0); + + rb_define_method(m, "subscribe", iodine_connection_subscribe, -1); + rb_define_singleton_method(m, "subscribe", iodine_connection_subscribe_klass, -1); + rb_define_singleton_method(iodine_rb_IODINE, "subscribe", iodine_connection_subscribe_klass, -1); + + rb_define_method(m, "unsubscribe", iodine_connection_unsubscribe, -1); + rb_define_singleton_method(m, "unsubscribe", iodine_connection_unsubscribe_klass, -1); + rb_define_singleton_method(iodine_rb_IODINE, "unsubscribe", iodine_connection_unsubscribe_klass, -1); + + rb_define_method(m, "publish", iodine_connection_publish, -1); + rb_define_singleton_method(m, "publish", iodine_connection_publish_klass, -1); + rb_define_singleton_method(iodine_rb_IODINE, "publish", iodine_connection_publish_klass, -1); + + rb_define_method(m, "pubsub?", iodine_connection_has_pubsub, 0); + rb_define_singleton_method(m, "pubsub?", iodine_connection_has_pubsub, 0); + rb_define_singleton_method(iodine_rb_IODINE, "pubsub?", iodine_connection_has_pubsub, 0); + + iodine_connection_cache_common_strings(); + iodine_connection_init_env_template(FIO_BUF_INFO0); +} // clang-format on + +#endif /* H___IODINE_CONNECTION___H */ diff --git a/ext/iodine/iodine_core.h b/ext/iodine/iodine_core.h new file mode 100644 index 00000000..1e682a51 --- /dev/null +++ b/ext/iodine/iodine_core.h @@ -0,0 +1,203 @@ +#ifndef H___IODINE_CORE___H +#define H___IODINE_CORE___H +#include "iodine.h" + +/* ***************************************************************************** +Starting / Stooping the IO Reactor +***************************************************************************** */ + +static void iodine_stop___(void *ignr_) { + fio_io_stop(); + (void)ignr_; +} + +static void iodine_connection_cache_common_strings(void); +static void *iodine___run(void *ignr_) { + VALUE ver = rb_const_get(iodine_rb_IODINE, rb_intern2("VERSION", 7)); + unsigned threads = (unsigned)fio_io_workers((int)fio_cli_get_i("-t")); + unsigned workers = (unsigned)fio_io_workers((int)fio_cli_get_i("-w")); + fio_io_async_attach(&IODINE_THREAD_POOL, (uint32_t)threads); + + iodine_env_set_const_val(IODINE_CONNECTION_ENV_TEMPLATE, + FIO_STR_INFO1((char *)"rack.multithread"), + (fio_cli_get_i("-t") ? Qtrue : Qfalse)); + iodine_env_set_const_val(IODINE_CONNECTION_ENV_TEMPLATE, + FIO_STR_INFO1((char *)"rack.multiprocess"), + (fio_cli_get_i("-w") ? Qtrue : Qfalse)); + + FIO_LOG_INFO("\n\tStarting the iodine server." + "\n\tVersion: %s" + "\n\tEngine: " FIO_POLL_ENGINE_STR "\n\tWorkers: %d\t(%s)" + "\n\tThreads: 1+%d\t(per worker)" + "\n\tPress ^C to exit.", + (ver == Qnil ? "unknown" : RSTRING_PTR(ver)), + workers, + (workers ? "cluster mode" : "single process"), + (int)IODINE_THREAD_POOL.count); + + fio_io_start((int)fio_cli_get_i("-w")); + return ignr_; +} + +/** Starts the Iodine IO reactor. */ +static VALUE iodine_start(VALUE self) { // clang-format on + rb_thread_call_without_gvl(iodine___run, NULL, iodine_stop___, NULL); + return self; +} + +/** + * Stops the current process' IO reactor. + * + * If this is a worker process, the process will exit and if Iodine is running a + * new worker will be spawned. + */ +static VALUE iodine_stop(VALUE klass) { + fio_io_stop(); + return klass; +} + +/** Return `true` if reactor is running */ +static VALUE iodine_is_running(VALUE klass) { + return fio_io_is_running() ? Qtrue : Qfalse; +} + +/** Return `true` if this is the master process. */ +static VALUE iodine_is_master(VALUE klass) { + return fio_io_is_master() ? Qtrue : Qfalse; +} + +/** Return `true` if this is a worker process. */ +static VALUE iodine_is_worker(VALUE klass) { + return fio_io_is_worker() ? Qtrue : Qfalse; +} + +/* ***************************************************************************** +Workers +***************************************************************************** */ + +/** Returns the number of process workers that the reactor (will) use. */ +static VALUE iodine_workers(VALUE klass) { + if (!fio_cli_get("-w")) + return Qnil; + unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-w")); + return LL2NUM(tmp); + (void)klass; +} + +/** + * Sets the number of process workers that the reactor will use. + * + * Settable only in the root / master process. + */ +static VALUE iodine_workers_set(VALUE klass, VALUE workers) { + if (workers != Qnil && fio_io_is_master()) { + if (TYPE(workers) != T_FIXNUM) { + rb_raise(rb_eTypeError, "workers must be a number."); + return Qnil; + } + fio_cli_set_i("-w", NUM2LL(workers)); + } else { + FIO_LOG_ERROR( + "cannot set workers except as a numeral value in the master process"); + } + workers = LL2NUM(fio_cli_get_i("-w")); + return workers; +} + +/* ***************************************************************************** +Threads +***************************************************************************** */ + +/** Returns the number of threads that the reactor (will) use. */ +static VALUE iodine_threads(VALUE klass) { + if (!fio_cli_get("-t")) + return Qnil; + unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-t")); + return LL2NUM(tmp); + (void)klass; +} + +/** + * Sets the number of threads that the reactor will use. + * + * Settable only in the root / master process. + */ +static VALUE iodine_threads_set(VALUE klass, VALUE threads) { + if (threads != Qnil && fio_io_is_master()) { + if (TYPE(threads) != T_FIXNUM) { + rb_raise(rb_eTypeError, "threads must be a number."); + return Qnil; + } + fio_cli_set_i("-t", NUM2LL(threads)); + } else { + FIO_LOG_ERROR( + "cannot set threads except as a numeral value in the master process"); + } + threads = LL2NUM(fio_cli_get_i("-t")); + return threads; +} + +/* ***************************************************************************** +Verbosity +***************************************************************************** */ + +/** Returns the current verbosity (logging) level.*/ +static VALUE iodine_verbosity(VALUE klass) { + return RB_INT2FIX(((long)FIO_LOG_LEVEL_GET())); + (void)klass; +} + +/** Sets the current verbosity (logging) level. */ +static VALUE iodine_verbosity_set(VALUE klass, VALUE num) { + FIO_LOG_LEVEL_SET(RB_FIX2INT(num)); + return num; +} + +/* ***************************************************************************** +FIOBJ => Ruby translation later +***************************************************************************** */ +typedef struct iodine_fiobj2ruby_task_s { + VALUE out; +} iodine_fiobj2ruby_task_s; + +static VALUE iodine_fiobj2ruby(FIOBJ o); + +static int iodine_fiobj2ruby_array_task(fiobj_array_each_s *e) { + rb_ary_push((VALUE)e->udata, iodine_fiobj2ruby(e->value)); + return 0; +} +static int iodine_fiobj2ruby_hash_task(fiobj_hash_each_s *e) { + rb_hash_aset((VALUE)e->udata, + iodine_fiobj2ruby(e->key), + iodine_fiobj2ruby(e->value)); + return 0; +} + +/** Converts FIOBJ to VALUE. Does NOT place VALUE in STORE automatically. */ +static VALUE iodine_fiobj2ruby(FIOBJ o) { + VALUE r; + switch (FIOBJ_TYPE(o)) { + case FIOBJ_T_TRUE: return Qtrue; + case FIOBJ_T_FALSE: return Qfalse; + case FIOBJ_T_NUMBER: return RB_LL2NUM(fiobj_num2i(o)); + case FIOBJ_T_FLOAT: return rb_float_new(fiobj_float2f(o)); + case FIOBJ_T_STRING: return rb_str_new(fiobj_str_ptr(o), fiobj_str_len(o)); + case FIOBJ_T_ARRAY: + r = rb_ary_new_capa(fiobj_array_count(o)); + STORE.hold(r); + fiobj_array_each(o, iodine_fiobj2ruby_array_task, (void *)r, 0); + STORE.release(r); + return r; + case FIOBJ_T_HASH: + r = rb_hash_new(); + STORE.hold(r); + fiobj_hash_each(o, iodine_fiobj2ruby_hash_task, (void *)r, 0); + STORE.release(r); + return r; + // case FIOBJ_T_NULL: /* fall through */ + // case FIOBJ_T_INVALID: /* fall through */ + default: return Qnil; + } +} + +#endif /* H___IODINE_CORE___H */ diff --git a/ext/iodine/iodine_defer.c b/ext/iodine/iodine_defer.c deleted file mode 100644 index 1fa9122a..00000000 --- a/ext/iodine/iodine_defer.c +++ /dev/null @@ -1,420 +0,0 @@ -#include "iodine.h" - -#include - -#include -// clang-format on - -#define FIO_INCLUDE_LINKED_LIST -#include "fio.h" - -#include - -static ID STATE_PRE_START; -static ID STATE_BEFORE_FORK; -static ID STATE_AFTER_FORK; -static ID STATE_ENTER_CHILD; -static ID STATE_ENTER_MASTER; -static ID STATE_ON_START; -static ID STATE_ON_PARENT_CRUSH; -static ID STATE_ON_CHILD_CRUSH; -static ID STATE_START_SHUTDOWN; -static ID STATE_ON_FINISH; - -/* ***************************************************************************** -IO flushing dedicated thread for protection against blocking code -***************************************************************************** */ - -static fio_lock_i sock_io_thread_flag = 0; -static pthread_t sock_io_pthread; -typedef struct { - size_t threads; - size_t processes; -} iodine_start_settings_s; - -static void *iodine_io_thread(void *arg) { - (void)arg; - while (sock_io_thread_flag) { - if (fio_flush_all()) - fio_throttle_thread(500000UL); - else - fio_throttle_thread(150000000UL); - } - return NULL; -} -static void iodine_start_io_thread(void *a_) { - if (!fio_trylock(&sock_io_thread_flag)) { - if (pthread_create(&sock_io_pthread, NULL, iodine_io_thread, NULL)) { - FIO_LOG_ERROR("Couldn't spawn IO thread."); - }; - FIO_LOG_DEBUG("IO thread started."); - } - (void)a_; -} - -static void iodine_join_io_thread(void) { - if (fio_unlock(&sock_io_thread_flag) && sock_io_pthread) { - pthread_join(sock_io_pthread, NULL); - sock_io_pthread = (pthread_t)NULL; - FIO_LOG_DEBUG("IO thread stopped and joined."); - } -} - -/* ***************************************************************************** -The Defer library overriding functions -***************************************************************************** */ - -/* used to create Ruby threads and pass them the information they need */ -struct CreateThreadArgs { - void *(*thread_func)(void *); - void *arg; - fio_lock_i lock; -}; - -/* used for GVL signalling */ -static void call_async_signal(void *pool) { - fio_stop(); - (void)pool; -} - -static void *defer_thread_start(void *args_) { - struct CreateThreadArgs *args = args_; - IodineCaller.set_GVL(0); - args->thread_func(args->arg); - return NULL; -} - -/* the thread's GVL release */ -static VALUE defer_thread_inGVL(void *args_) { - struct CreateThreadArgs *old_args = args_; - struct CreateThreadArgs args = *old_args; - IodineCaller.set_GVL(1); - fio_unlock(&old_args->lock); - rb_thread_call_without_gvl(defer_thread_start, &args, - (void (*)(void *))call_async_signal, args.arg); - return Qnil; -} - -/* Within the GVL, creates a Ruby thread using an API call */ -static void *create_ruby_thread_gvl(void *args) { - return (void *)IodineStore.add(rb_thread_create(defer_thread_inGVL, args)); -} - -static void *fork_using_ruby(void *ignr) { - // stop IO thread, if running (shouldn't occur) - if (sock_io_pthread) { - iodine_join_io_thread(); - } - // fork using Ruby - const VALUE ProcessClass = rb_const_get(rb_cObject, rb_intern2("Process", 7)); - const VALUE rb_pid = IodineCaller.call(ProcessClass, rb_intern2("fork", 4)); - intptr_t pid = 0; - if (rb_pid != Qnil) { - pid = NUM2INT(rb_pid); - } else { - pid = 0; - } - // manage post forking state for Iodine - IodineCaller.set_GVL(1); /* enforce GVL state in thread storage */ - if (!pid) { - IodineStore.after_fork(); - } - return (void *)pid; - (void)ignr; -} - -/** -OVERRIDE THIS to replace the default pthread implementation. -*/ -void *fio_thread_new(void *(*thread_func)(void *), void *arg) { - struct CreateThreadArgs data = (struct CreateThreadArgs){ - .thread_func = thread_func, - .arg = arg, - .lock = FIO_LOCK_INIT, - }; - fio_lock(&data.lock); - void *thr = IodineCaller.enterGVL(create_ruby_thread_gvl, &data); - if (!thr || thr == (void *)Qnil || thr == (void *)Qfalse) { - thr = NULL; - } else { - /* wait for thread to signal it's alive. */ - fio_lock(&data.lock); - } - return thr; -} - -/** -OVERRIDE THIS to replace the default pthread implementation. -*/ -int fio_thread_join(void *thr) { - if (!thr || (VALUE)thr == Qfalse || (VALUE)thr == Qnil) - return -1; - IodineCaller.call((VALUE)thr, rb_intern("join")); - IodineStore.remove((VALUE)thr); - return 0; -} - -// void defer_free_thread(void *thr) { (void)thr; } -void fio_thread_free(void *thr) { IodineStore.remove((VALUE)thr); } - -/** -OVERRIDE THIS to replace the default `fork` implementation or to inject hooks -into the forking function. - -Behaves like the system's `fork`. -*/ -int fio_fork(void) { - intptr_t pid = (intptr_t)IodineCaller.enterGVL(fork_using_ruby, NULL); - return (int)pid; -} - -/* ***************************************************************************** -Task performance -***************************************************************************** */ - -static ID call_id; - -static void iodine_defer_performe_once(void *block, void *ignr) { - IodineCaller.call((VALUE)block, call_id); - IodineStore.remove((VALUE)block); - (void)ignr; -} - -static void iodine_defer_run_timer(void *block) { - /* TODO, update return value to allow timer cancellation */ - IodineCaller.call((VALUE)block, call_id); -} - -/* ***************************************************************************** -Defer API -***************************************************************************** */ - -/** - * Runs a block of code asyncronously (adds the code to the event queue). - * - * Always returns the block of code to executed (Proc object). - * - * Code will be executed only while Iodine is running (after {Iodine.start}). - * - * Code blocks that where scheduled to run before Iodine enters cluster mode - * will run on all child processes. - */ -static VALUE iodine_defer_run(VALUE self) { - rb_need_block(); - VALUE block = IodineStore.add(rb_block_proc()); - fio_defer(iodine_defer_performe_once, (void *)block, NULL); - return block; - (void)self; -} - -/** -Runs the required block after the specified number of milliseconds have passed. -Time is counted only once Iodine started running (using {Iodine.start}). - -Tasks scheduled before calling {Iodine.start} will run once for every process. - -Always returns a copy of the block object. -*/ -static VALUE iodine_defer_run_after(VALUE self, VALUE milliseconds) { - (void)(self); - if (milliseconds == Qnil) { - return iodine_defer_run(self); - } - if (TYPE(milliseconds) != T_FIXNUM) { - rb_raise(rb_eTypeError, "milliseconds must be a number"); - return Qnil; - } - size_t milli = FIX2UINT(milliseconds); - if (milli == 0) { - return iodine_defer_run(self); - } - // requires a block to be passed - rb_need_block(); - VALUE block = rb_block_proc(); - if (block == Qnil) - return Qfalse; - IodineStore.add(block); - if (fio_run_every(milli, 1, iodine_defer_run_timer, (void *)block, - (void (*)(void *))IodineStore.remove) == -1) { - perror("ERROR: Iodine couldn't initialize timer"); - return Qnil; - } - return block; -} - -// clang-format off -/** -Runs the required block after the specified number of milliseconds have passed. -Time is counted only once Iodine started running (using {Iodine.start}). - -Accepts: - -| | | -|---|---| -| `:milliseconds` | the number of milliseconds between event repetitions.| -| `:repetitions` | the number of event repetitions. Defaults to 0 (never ending).| -| `:block` | (required) a block is required, as otherwise there is nothing to| -perform. - -The event will repeat itself until the number of repetitions had been delpeted. - -Always returns a copy of the block object. -*/ -static VALUE iodine_defer_run_every(int argc, VALUE *argv, VALUE self) { - // clang-format on - (void)(self); - VALUE milliseconds, repetitions, block; - - rb_scan_args(argc, argv, "11&", &milliseconds, &repetitions, &block); - - if (TYPE(milliseconds) != T_FIXNUM) { - rb_raise(rb_eTypeError, "milliseconds must be a number."); - return Qnil; - } - if (repetitions != Qnil && TYPE(repetitions) != T_FIXNUM) { - rb_raise(rb_eTypeError, "repetitions must be a number or `nil`."); - return Qnil; - } - - size_t milli = FIX2UINT(milliseconds); - size_t repeat = (repetitions == Qnil) ? 0 : FIX2UINT(repetitions); - // requires a block to be passed - rb_need_block(); - IodineStore.add(block); - if (fio_run_every(milli, repeat, iodine_defer_run_timer, (void *)block, - (void (*)(void *))IodineStore.remove) == -1) { - perror("ERROR: Iodine couldn't initialize timer"); - return Qnil; - } - return block; -} - -/* ***************************************************************************** -Pre/Post `fork` -***************************************************************************** */ - -/* performs a Ruby state callback without clearing the Ruby object's memory */ -static void iodine_perform_state_callback_persist(void *blk_) { - VALUE blk = (VALUE)blk_; - IodineCaller.call(blk, call_id); -} - -// clang-format off -/** -Sets a block of code to run when Iodine's core state is updated. - -@param [Symbol] event the state event for which the block should run (see list). -@since 0.7.9 - -The state event Symbol can be any of the following: - -| | | -|---|---| -| `:pre_start` | the block will be called once before starting up the IO reactor. | -| `:before_fork` | the block will be called before each time the IO reactor forks a new worker. | -| `:after_fork` | the block will be called after each fork (both in parent and workers). | -| `:enter_child` | the block will be called by a worker process right after forking. | -| `:enter_master` | the block will be called by the master process after spawning a worker (after forking). | -| `:on_start` | the block will be called every time a *worker* proceess starts. In single process mode, the master process is also a worker. | -| `:on_parent_crush` | the block will be called by each worker the moment it detects the master process crashed. | -| `:on_child_crush` | the block will be called by the parent (master) after a worker process crashed. | -| `:start_shutdown` | the block will be called before starting the shutdown sequence. | -| `:on_finish` | the block will be called just before finishing up (both on chlid and parent processes). | - -Code runs in both the parent and the child. -*/ -static VALUE iodine_on_state(VALUE self, VALUE event) { - // clang-format on - rb_need_block(); - Check_Type(event, T_SYMBOL); - VALUE block = rb_block_proc(); - IodineStore.add(block); - ID state = rb_sym2id(event); - - if (state == STATE_PRE_START) { - fio_state_callback_add(FIO_CALL_PRE_START, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_BEFORE_FORK) { - fio_state_callback_add(FIO_CALL_BEFORE_FORK, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_AFTER_FORK) { - fio_state_callback_add(FIO_CALL_AFTER_FORK, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ENTER_CHILD) { - fio_state_callback_add(FIO_CALL_IN_CHILD, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ENTER_MASTER) { - fio_state_callback_add(FIO_CALL_IN_MASTER, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ON_START) { - fio_state_callback_add(FIO_CALL_ON_START, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ON_PARENT_CRUSH) { - fio_state_callback_add(FIO_CALL_ON_PARENT_CRUSH, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ON_CHILD_CRUSH) { - fio_state_callback_add(FIO_CALL_ON_CHILD_CRUSH, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_START_SHUTDOWN) { - fio_state_callback_add(FIO_CALL_ON_SHUTDOWN, - iodine_perform_state_callback_persist, - (void *)block); - } else if (state == STATE_ON_FINISH) { - fio_state_callback_add(FIO_CALL_ON_FINISH, - iodine_perform_state_callback_persist, - (void *)block); - } else { - IodineStore.remove(block); - rb_raise(rb_eTypeError, "unknown event in Iodine.on_state"); - } - return block; - (void)self; -} - -/* Performs any cleanup before worker dies */ -static void iodine_defer_on_finish(void *ignr) { - (void)ignr; - iodine_join_io_thread(); -} - -/* ***************************************************************************** -Add defer API to Iodine -***************************************************************************** */ - -void iodine_defer_initialize(void) { - call_id = rb_intern2("call", 4); - rb_define_module_function(IodineModule, "run", iodine_defer_run, 0); - rb_define_module_function(IodineModule, "defer", iodine_defer_run, 0); - - rb_define_module_function(IodineModule, "run_after", iodine_defer_run_after, - 1); - rb_define_module_function(IodineModule, "run_every", iodine_defer_run_every, - -1); - rb_define_module_function(IodineModule, "on_state", iodine_on_state, 1); - - STATE_PRE_START = rb_intern("pre_start"); - STATE_BEFORE_FORK = rb_intern("before_fork"); - STATE_AFTER_FORK = rb_intern("after_fork"); - STATE_ENTER_CHILD = rb_intern("enter_child"); - STATE_ENTER_MASTER = rb_intern("enter_master"); - STATE_ON_START = rb_intern("on_start"); - STATE_ON_PARENT_CRUSH = rb_intern("on_parent_crush"); - STATE_ON_CHILD_CRUSH = rb_intern("on_child_crush"); - STATE_START_SHUTDOWN = rb_intern("start_shutdown"); - STATE_ON_FINISH = rb_intern("on_finish"); - - /* start the IO thread is workrs (only starts in root if root is worker) */ - fio_state_callback_add(FIO_CALL_ON_START, iodine_start_io_thread, NULL); - /* stop the IO thread before exit */ - fio_state_callback_add(FIO_CALL_ON_FINISH, iodine_defer_on_finish, NULL); - /* kill IO thread even after a non-graceful iodine shutdown (force-quit) */ - fio_state_callback_add(FIO_CALL_AT_EXIT, iodine_defer_on_finish, NULL); -} diff --git a/ext/iodine/iodine_defer.h b/ext/iodine/iodine_defer.h index e72244c6..afd9f61e 100644 --- a/ext/iodine/iodine_defer.h +++ b/ext/iodine/iodine_defer.h @@ -1,6 +1,216 @@ -#ifndef H_IODINE_DEFER_H -#define H_IODINE_DEFER_H +#ifndef H___IODINE_DEFER___H +#define H___IODINE_DEFER___H +#include "iodine.h" -void iodine_defer_initialize(void); +/* ***************************************************************************** -#endif + Iodine's `defer` and `run_after` functions + +***************************************************************************** */ + +/* ***************************************************************************** +Iodine's `on_state` +***************************************************************************** */ + +static ID IODINE_STATE_PRE_START; +static ID IODINE_STATE_BEFORE_FORK; +static ID IODINE_STATE_AFTER_FORK; +static ID IODINE_STATE_ENTER_CHILD; +static ID IODINE_STATE_ENTER_MASTER; +static ID IODINE_STATE_ON_START; +static ID IODINE_STATE_ON_PARENT_CRUSH; +static ID IODINE_STATE_ON_CHILD_CRUSH; +static ID IODINE_STATE_ON_SHUTDOWN; +static ID IODINE_STATE_ON_STOP; + +/* performs a Ruby state callback without clearing the Ruby object's memory */ +static void iodine_perform_state_callback_persist(void *blk_) { + VALUE blk = (VALUE)blk_; + // iodine_caller_result_s r = + iodine_ruby_call_outside(blk, IODINE_CALL_ID, 0, NULL); +} + +// clang-format off +/** +Sets a block of code to run when Iodine's core state is updated. + +@param [Symbol] event the state event for which the block should run (see list). +@since 0.7.9 + +The state event Symbol can be any of the following: + +| | | +|---|---| +| `:pre_start` | the block will be called once before starting up the IO reactor. | +| `:before_fork` | the block will be called before each time the IO reactor forks a new worker. | +| `:after_fork` | the block will be called after each fork (both in parent and workers). | +| `:enter_child` | the block will be called by a worker process right after forking. | +| `:enter_master` | the block will be called by the master process after spawning a worker (after forking). | +| `:on_start` | the block will be called every time a *worker* process starts. In single process mode, the master process is also a worker. | +| `:on_parent_crush` | the block will be called by each worker the moment it detects the master process crashed. | +| `:on_child_crush` | the block will be called by the parent (master) after a worker process crashed. | +| `:start_shutdown` | the block will be called before starting the shutdown sequence. | +| `:on_finish` | the block will be called just before finishing up (both on chlid and parent processes). | + +Code runs in both the parent and the child. +*/ +static VALUE iodine_on_state(VALUE self, VALUE event) { // clang-format on + + rb_need_block(); + Check_Type(event, T_SYMBOL); + VALUE block = rb_block_proc(); + STORE.hold(block); + ID state = rb_sym2id(event); + + if (state == IODINE_STATE_PRE_START) { + fio_state_callback_add(FIO_CALL_PRE_START, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_BEFORE_FORK) { + fio_state_callback_add(FIO_CALL_BEFORE_FORK, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_AFTER_FORK) { + fio_state_callback_add(FIO_CALL_AFTER_FORK, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ENTER_CHILD) { + fio_state_callback_add(FIO_CALL_IN_CHILD, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ENTER_MASTER) { + fio_state_callback_add(FIO_CALL_IN_MASTER, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ON_START) { + fio_state_callback_add(FIO_CALL_ON_START, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ON_PARENT_CRUSH) { + fio_state_callback_add(FIO_CALL_ON_PARENT_CRUSH, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ON_CHILD_CRUSH) { + fio_state_callback_add(FIO_CALL_ON_CHILD_CRUSH, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ON_SHUTDOWN) { + fio_state_callback_add(FIO_CALL_ON_SHUTDOWN, + iodine_perform_state_callback_persist, + (void *)block); + } else if (state == IODINE_STATE_ON_STOP) { + fio_state_callback_add(FIO_CALL_ON_STOP, + iodine_perform_state_callback_persist, + (void *)block); + } else { + STORE.release(block); + rb_raise(rb_eTypeError, "unknown event in Iodine.on_state"); + } + return block; + (void)self; +} + +/* ***************************************************************************** +Task performance +***************************************************************************** */ + +static void iodine_defer_performe_once(void *block, void *ignr) { + iodine_ruby_call_outside((VALUE)block, IODINE_CALL_ID, 0, NULL); + STORE.release((VALUE)block); + (void)ignr; +} + +static int iodine_defer_run_timer(void *block, void *ignr) { + iodine_caller_result_s r = + iodine_ruby_call_outside((VALUE)block, IODINE_CALL_ID, 0, NULL); + return 0 - (r.exception || (r.result == Qfalse)); + (void)ignr; +} + +static void iodine_defer_after_timer(void *block, void *ignr) { + STORE.release((VALUE)block); + (void)ignr; +} + +/* ***************************************************************************** +Defer API +***************************************************************************** */ + +/** + * Runs a block of code asynchronously (adds the code to the event queue). + * + * Always returns the block of code to executed (Proc object). + * + * Code will be executed only while Iodine is running (after {Iodine.start}). + * + * Code blocks that where scheduled to run before Iodine enters cluster mode + * will run on all child processes. + */ +static VALUE iodine_defer_run(VALUE self) { + rb_need_block(); + VALUE block = rb_block_proc(); + STORE.hold(block); + fio_io_defer(iodine_defer_performe_once, (void *)block, NULL); + return block; + (void)self; +} + +/** + * Runs a block of code asynchronously (adds the code to the event queue). + * + * Always returns the block of code to executed (Proc object). + * + * Code will be executed only while Iodine is running (after {Iodine.start}). + * + * Code blocks that where scheduled to run before Iodine enters cluster mode + * will run on all child processes. + */ +static VALUE iodine_defer_run_async(VALUE self) { + rb_need_block(); + VALUE block = rb_block_proc(); + STORE.hold(block); + IODINE_DEFER_BLOCK(block); + return block; + (void)self; +} + +// clang-format off +/** +Runs the required block after the specified number of milliseconds have passed. +Time is counted only once Iodine started running (using {Iodine.start}). + +Accepts: + +| | | +|---|---| +| `:milliseconds` | the number of milliseconds between event repetitions.| +| `:repetitions` | the number of event repetitions. Defaults to 1 (performed once). Set to 0 for never ending. | +| `:block` | (required) a block is required, as otherwise there is nothing to| +perform. + +The event will repeat itself until the number of repetitions had been delpeted. + +Always returns a copy of the block object. +*/ +static VALUE iodine_defer_run_after(int argc, VALUE *argv, VALUE self) { + // clang-format on + (void)(self); + int64_t milli = 0; + int64_t repeat = 1; + VALUE block = Qnil; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_NUM(milli, 0, "milliseconds", 1), // required + IODINE_ARG_NUM(repeat, 0, "repetitions", 0), + IODINE_ARG_PROC(block, 0, "block", 1)); + STORE.hold(block); + repeat -= 1; + fio_io_run_every(.every = (uint32_t)milli, + .repetitions = (int32_t)repeat, + .fn = iodine_defer_run_timer, + .udata1 = (void *)block, + .on_finish = iodine_defer_after_timer); + return block; +} + +#endif /* H___IODINE_DEFER___H */ diff --git a/ext/iodine/iodine_fiobj2rb.h b/ext/iodine/iodine_fiobj2rb.h deleted file mode 100644 index 6888be4c..00000000 --- a/ext/iodine/iodine_fiobj2rb.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef H_RB_FIOBJ2RUBY_H -/* -Copyright: Boaz segev, 2016-2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#define H_RB_FIOBJ2RUBY_H -#include -#include - -#include "iodine_store.h" - -typedef struct { - FIOBJ stack; - uintptr_t count; - VALUE rb; - uint8_t str2sym; -} fiobj2rb_s; - -typedef struct { - uint8_t str2sym; -} fiobj2rb_settings_s; - -static inline VALUE fiobj2rb(FIOBJ o, uint8_t str2sym) { - VALUE rb; - if (!o) - return Qnil; - switch (FIOBJ_TYPE(o)) { - case FIOBJ_T_NUMBER: - rb = LONG2FIX(fiobj_obj2num(o)); - break; - case FIOBJ_T_TRUE: - rb = Qtrue; - break; - case FIOBJ_T_FALSE: - rb = Qfalse; - break; - case FIOBJ_T_FLOAT: - rb = rb_float_new(fiobj_obj2float(o)); - break; - case FIOBJ_T_DATA: /* fallthrough */ - case FIOBJ_T_UNKNOWN: /* fallthrough */ - case FIOBJ_T_STRING: { - fio_str_info_s tmp = fiobj_obj2cstr(o); - if (str2sym) { - rb = rb_intern2(tmp.data, tmp.len); - rb = ID2SYM(rb); - } else { - rb = rb_str_new(tmp.data, tmp.len); - } - - } break; - case FIOBJ_T_ARRAY: - rb = rb_ary_new(); - break; - case FIOBJ_T_HASH: - rb = rb_hash_new(); - break; - case FIOBJ_T_NULL: /* fallthrough */ - default: - rb = Qnil; - break; - }; - return rb; -} - -static int fiobj2rb_task(FIOBJ o, void *data_) { - fiobj2rb_s *data = data_; - VALUE rb_tmp; - rb_tmp = fiobj2rb(o, 0); - IodineStore.add(rb_tmp); - if (data->rb) { - if (RB_TYPE_P(data->rb, T_HASH)) { - rb_hash_aset(data->rb, fiobj2rb(fiobj_hash_key_in_loop(), data->str2sym), - rb_tmp); - } else { - rb_ary_push(data->rb, rb_tmp); - } - --(data->count); - IodineStore.remove(rb_tmp); - } else { - data->rb = rb_tmp; - // IodineStore.add(rb_tmp); - } - if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) { - fiobj_ary_push(data->stack, (FIOBJ)data->count); - fiobj_ary_push(data->stack, (FIOBJ)data->rb); - data->count = fiobj_ary_count(o); - data->rb = rb_tmp; - } else if (FIOBJ_TYPE_IS(o, FIOBJ_T_HASH)) { - fiobj_ary_push(data->stack, (FIOBJ)data->count); - fiobj_ary_push(data->stack, (FIOBJ)data->rb); - data->count = fiobj_hash_count(o); - data->rb = rb_tmp; - } - while (data->count == 0 && fiobj_ary_count(data->stack)) { - data->rb = fiobj_ary_pop(data->stack); - data->count = fiobj_ary_pop(data->stack); - } - return 0; -} - -static inline VALUE fiobj2rb_deep(FIOBJ obj, uint8_t str2sym) { - fiobj2rb_s data = {.stack = fiobj_ary_new2(4), .str2sym = str2sym}; - - /* deep copy */ - fiobj_each2(obj, fiobj2rb_task, &data); - /* cleanup (shouldn't happen, but what the hell)... */ - while (fiobj_ary_pop(data.stack)) - ; - fiobj_free(data.stack); - // IodineStore.remove(data.rb); // don't remove data - return data.rb; -} - -// require 'iodine' -// Iodine::JSON.parse "{\"1\":[1,2,3,4]}" -// Iodine::JSON.parse IO.binread("") -#endif /* H_RB_FIOBJ2RUBY_H */ diff --git a/ext/iodine/iodine_helpers.c b/ext/iodine/iodine_helpers.c deleted file mode 100644 index 6016f8e8..00000000 --- a/ext/iodine/iodine_helpers.c +++ /dev/null @@ -1,282 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include "iodine.h" - -#include "http.h" -#include - -/* -Add all sorts of useless stuff here. -*/ - -static ID iodine_to_i_func_id; -static rb_encoding *IodineUTF8Encoding; - -/* ***************************************************************************** -URL Decoding -***************************************************************************** */ -/** -Decodes a URL encoded String in place. - -Raises an exception on error... but this might result in a partially decoded -String. -*/ -static VALUE url_decode_inplace(VALUE self, VALUE str) { - Check_Type(str, T_STRING); - ssize_t len = - http_decode_url(RSTRING_PTR(str), RSTRING_PTR(str), RSTRING_LEN(str)); - if (len < 0) - rb_raise(rb_eRuntimeError, "Malformed URL string - couldn't decode (String " - "might have been partially altered)."); - rb_str_set_len(str, len); - return str; - (void)self; -} - -/** -Decodes a URL encoded String, returning a new String with the decoded data. -*/ -static VALUE url_decode(VALUE self, VALUE str) { - Check_Type(str, T_STRING); - VALUE str2 = rb_str_buf_new(RSTRING_LEN(str)); - ssize_t len = - http_decode_url(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str)); - if (len < 0) - rb_raise(rb_eRuntimeError, "Malformed URL string - couldn't decode."); - rb_str_set_len(str2, len); - return str2; - (void)self; -} - -/** -Decodes a percent encoded String (normally the "path" of a request), editing the -String in place. - -Raises an exception on error... but this might result in a partially decoded -String. -*/ -static VALUE path_decode_inplace(VALUE self, VALUE str) { - Check_Type(str, T_STRING); - ssize_t len = - http_decode_path(RSTRING_PTR(str), RSTRING_PTR(str), RSTRING_LEN(str)); - if (len < 0) - rb_raise(rb_eRuntimeError, - "Malformed URL path string - couldn't decode (String " - "might have been partially altered)."); - rb_str_set_len(str, len); - return str; - (void)self; -} - -/** -Decodes a percent encoded String (normally the "path" of a request), returning a -new String with the decoded data. -*/ -static VALUE path_decode(VALUE self, VALUE str) { - Check_Type(str, T_STRING); - VALUE str2 = rb_str_buf_new(RSTRING_LEN(str)); - ssize_t len = - http_decode_path(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str)); - if (len < 0) - rb_raise(rb_eRuntimeError, "Malformed URL path string - couldn't decode."); - rb_str_set_len(str2, len); - return str2; - (void)self; -} - -/** -Decodes a URL encoded String, returning a new String with the decoded data. - -This variation matches the Rack::Utils.unescape signature by accepting and -mostly ignoring an optional Encoding argument. -*/ -static VALUE unescape(int argc, VALUE *argv, VALUE self) { - if (argc < 1 || argc > 2) - rb_raise(rb_eArgError, - "wrong number of arguments (given %d, expected 1..2).", argc); - VALUE str = argv[0]; - Check_Type(str, T_STRING); - VALUE str2 = rb_str_buf_new(RSTRING_LEN(str)); - ssize_t len = - http_decode_url(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str)); - if (len < 0) - rb_raise(rb_eRuntimeError, "Malformed URL path string - couldn't decode."); - rb_str_set_len(str2, len); - rb_encoding *enc = IodineUTF8Encoding; - if (argc == 2 && argv[1] != Qnil && argv[1] != Qfalse) { - enc = rb_enc_get(argv[1]); - if (!enc) - enc = IodineUTF8Encoding; - } - rb_enc_associate(str2, enc); - return str2; - (void)self; -} - -/* ***************************************************************************** -HTTP Dates -***************************************************************************** */ - -/** -Takes an optional Integer for Unix Time and returns a faster (though less -localized) HTTP Date formatted String. - - - Iodine::Rack.time2str => "Sun, 11 Jun 2017 06:14:08 GMT" - - Iodine::Rack.time2str(Time.now.to_i) => "Wed, 15 Nov 1995 06:25:24 GMT" - -Since Iodine uses time caching within it's reactor, using the default value -(now) will be faster than providing an explicit time using `Time.now.to_i`. - -*/ -static VALUE date_str(int argc, VALUE *argv, VALUE self) { - if (argc > 1) - rb_raise(rb_eArgError, - "wrong number of arguments (given %d, expected 0..1).", argc); - time_t last_tick; - if (argc) { - if (TYPE(argv[0]) != T_FIXNUM) - argv[0] = rb_funcallv(argv[0], iodine_to_i_func_id, 0, NULL); - Check_Type(argv[0], T_FIXNUM); - last_tick = - FIX2ULONG(argv[0]) ? FIX2ULONG(argv[0]) : fio_last_tick().tv_sec; - } else - last_tick = fio_last_tick().tv_sec; - VALUE str = rb_str_buf_new(32); - struct tm tm; - - http_gmtime(last_tick, &tm); - size_t len = http_date2str(RSTRING_PTR(str), &tm); - rb_str_set_len(str, len); - return str; - (void)self; -} - -/** -Takes `time` and returns a faster (though less localized) HTTP Date formatted -String. - - - Iodine::Rack.rfc2822(Time.now) => "Sun, 11 Jun 2017 06:14:08 -0000" - - Iodine::Rack.rfc2822(0) => "Sun, 11 Jun 2017 06:14:08 -0000" - -Since Iodine uses time caching within it's reactor, using the default value -(by passing 0) will be faster than providing an explicit time using `Time.now`. -*/ -static VALUE iodine_rfc2822(VALUE self, VALUE rtm) { - time_t last_tick; - rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL); - last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : fio_last_tick().tv_sec; - VALUE str = rb_str_buf_new(34); - struct tm tm; - - http_gmtime(last_tick, &tm); - size_t len = http_date2rfc2822(RSTRING_PTR(str), &tm); - rb_str_set_len(str, len); - return str; - (void)self; -} - -/** -Takes `time` and returns a faster (though less localized) HTTP Date formatted -String. - - - Iodine::Rack.rfc2109(Time.now) => "Sun, 11-Jun-2017 06:14:08 GMT" - - Iodine::Rack.rfc2109(0) => "Sun, 11-Jun-2017 06:14:08 GMT" - -Since Iodine uses time caching within it's reactor, using the default value -(by passing 0) will be faster than providing an explicit time using `Time.now`. -*/ -static VALUE iodine_rfc2109(VALUE self, VALUE rtm) { - time_t last_tick; - rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL); - last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : fio_last_tick().tv_sec; - VALUE str = rb_str_buf_new(32); - struct tm tm; - - http_gmtime(last_tick, &tm); - size_t len = http_date2rfc2109(RSTRING_PTR(str), &tm); - rb_str_set_len(str, len); - return str; - (void)self; -} - -/* ***************************************************************************** -Ruby Initialization -***************************************************************************** */ - -void iodine_init_helpers(void) { - iodine_to_i_func_id = rb_intern("to_i"); - IodineUTF8Encoding = rb_enc_find("UTF-8"); - VALUE tmp = rb_define_module_under(IodineModule, "Rack"); - // clang-format off - /* -Iodine does NOT monkey patch Rack automatically. However, it's possible and recommended to moneky patch Rack::Utils to use the methods in this module. - -Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine): - - require 'iodine' - require 'rack' - # a String in need of decoding - s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8' - Benchmark.bm do |bm| - # Pre-Patch - bm.report(" Rack.unescape") {1_000_000.times { Rack::Utils.unescape s } } - bm.report(" Rack.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } } - bm.report(" Rack.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } } - # Perform Patch - Iodine.patch_rack - puts " --- Monkey Patching Rack ---" - # Post Patch - bm.report("Patched.unescape") {1_000_000.times { Rack::Utils.unescape s } } - bm.report(" Patched.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } } - bm.report(" Patched.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } } - end && nil - -Results: - user system total real - Rack.unescape 8.706881 0.019995 8.726876 ( 8.740530) - Rack.rfc2822 3.270305 0.007519 3.277824 ( 3.279416) - Rack.rfc2109 3.152188 0.003852 3.156040 ( 3.157975) - --- Monkey Patching Rack --- - Patched.unescape 0.327231 0.003125 0.330356 ( 0.337090) - Patched.rfc2822 0.691304 0.003330 0.694634 ( 0.701172) - Patched.rfc2109 0.685029 0.001956 0.686985 ( 0.687607) - - */ - tmp = rb_define_module_under(tmp, "Utils"); - // clang-format on - rb_define_module_function(tmp, "decode_url!", url_decode_inplace, 1); - rb_define_module_function(tmp, "decode_url", url_decode, 1); - rb_define_module_function(tmp, "decode_path!", path_decode_inplace, 1); - rb_define_module_function(tmp, "decode_path", path_decode, 1); - rb_define_module_function(tmp, "time2str", date_str, -1); - rb_define_module_function(tmp, "rfc2109", iodine_rfc2109, 1); - rb_define_module_function(tmp, "rfc2822", iodine_rfc2822, 1); - - /* -The monkey-patched methods are in this module, allowing Iodine::Rack::Utils to -include non-patched methods as well. - */ - tmp = rb_define_module_under(IodineBaseModule, "MonkeyPatch"); - tmp = rb_define_module_under(tmp, "RackUtils"); - // clang-format on - /* we define it all twice for easier monkey patching */ - rb_define_method(tmp, "unescape", unescape, -1); - rb_define_method(tmp, "unescape_path", path_decode, 1); - rb_define_method(tmp, "rfc2109", iodine_rfc2109, 1); - rb_define_method(tmp, "rfc2822", iodine_rfc2822, 1); - rb_define_singleton_method(tmp, "unescape", unescape, -1); - rb_define_singleton_method(tmp, "unescape_path", path_decode, 1); - rb_define_singleton_method(tmp, "rfc2109", iodine_rfc2109, 1); - rb_define_singleton_method(tmp, "rfc2822", iodine_rfc2822, 1); - // rb_define_module_function(IodineUtils, "time2str", date_str, -1); -} diff --git a/ext/iodine/iodine_helpers.h b/ext/iodine/iodine_helpers.h deleted file mode 100644 index c355889f..00000000 --- a/ext/iodine/iodine_helpers.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef H_IODINE_HELPERS_H -#define H_IODINE_HELPERS_H -/* -Copyright: Boaz segev, 2016-2018 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ - -void iodine_init_helpers(void); - -#endif diff --git a/ext/iodine/iodine_http.c b/ext/iodine/iodine_http.c deleted file mode 100644 index 021690cc..00000000 --- a/ext/iodine/iodine_http.c +++ /dev/null @@ -1,1181 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include "iodine.h" - -#include "http.h" - -#include -#include -// #include "iodine_websockets.h" - -#ifndef __MINGW32__ -#include -#endif -#include -#include -#include -#ifndef __MINGW32__ -#include -#endif - -/* ***************************************************************************** -Available Globals -***************************************************************************** */ - -typedef struct { - VALUE app; - VALUE env; -} iodine_http_settings_s; - -/* these three are used also by iodin_rack_io.c */ -VALUE IODINE_R_INPUT_DEFAULT; -VALUE IODINE_R_INPUT; -VALUE IODINE_R_HIJACK; -VALUE IODINE_R_HIJACK_IO; -VALUE IODINE_R_HIJACK_CB; - -static VALUE RACK_UPGRADE; -static VALUE RACK_UPGRADE_Q; -static VALUE RACK_UPGRADE_SSE; -static VALUE RACK_UPGRADE_WEBSOCKET; -static VALUE UPGRADE_TCP; - -static VALUE HTTP_ACCEPT; -static VALUE HTTP_USER_AGENT; -static VALUE HTTP_ACCEPT_ENCODING; -static VALUE HTTP_ACCEPT_LANGUAGE; -static VALUE HTTP_CONNECTION; -static VALUE HTTP_HOST; - -static VALUE hijack_func_sym; -static ID close_method_id; -static ID each_method_id; -static ID attach_method_id; -static ID iodine_call_proc_id; - -static VALUE env_template_no_upgrade; -static VALUE env_template_websockets; -static VALUE env_template_sse; - -static rb_encoding *IodineUTF8Encoding; -static rb_encoding *IodineBinaryEncoding; - -static uint8_t support_xsendfile = 0; - -#define rack_declare(rack_name) static VALUE rack_name - -#define rack_set(rack_name, str) \ - (rack_name) = rb_enc_str_new((str), strlen((str)), IodineBinaryEncoding); \ - rb_global_variable(&(rack_name)); \ - rb_obj_freeze(rack_name); -#define rack_set_sym(rack_name, sym) \ - (rack_name) = rb_id2sym(rb_intern2((sym), strlen((sym)))); \ - rb_global_variable(&(rack_name)); - -#define rack_autoset(rack_name) rack_set((rack_name), #rack_name) - -// static uint8_t IODINE_IS_DEVELOPMENT_MODE = 0; - -rack_declare(HTTP_SCHEME); -rack_declare(HTTPS_SCHEME); -rack_declare(QUERY_ESTRING); -rack_declare(REQUEST_METHOD); -rack_declare(PATH_INFO); -rack_declare(QUERY_STRING); -rack_declare(QUERY_ESTRING); -rack_declare(SERVER_NAME); -rack_declare(SERVER_PORT); -rack_declare(SERVER_PROTOCOL); -rack_declare(HTTP_VERSION); -rack_declare(REMOTE_ADDR); -rack_declare(CONTENT_LENGTH); -rack_declare(CONTENT_TYPE); -rack_declare(R_URL_SCHEME); // rack.url_scheme -rack_declare(R_INPUT); // rack.input -rack_declare(XSENDFILE); // for X-Sendfile support -rack_declare(XSENDFILE_TYPE); // for X-Sendfile support -rack_declare(XSENDFILE_TYPE_HEADER); // for X-Sendfile support -rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support - -/* used internally to handle requests */ -typedef struct { - http_s *h; - FIOBJ body; - enum iodine_http_response_type_enum { - IODINE_HTTP_NONE, - IODINE_HTTP_SENDBODY, - IODINE_HTTP_XSENDFILE, - IODINE_HTTP_EMPTY, - IODINE_HTTP_ERROR, - } type; - enum iodine_upgrade_type_enum { - IODINE_UPGRADE_NONE = 0, - IODINE_UPGRADE_WEBSOCKET, - IODINE_UPGRADE_SSE, - } upgrade; -} iodine_http_request_handle_s; - -/* ***************************************************************************** -WebSocket support -***************************************************************************** */ - -typedef struct { - char *data; - size_t size; - uint8_t is_text; - VALUE io; -} iodine_msg2ruby_s; - -static void *iodine_ws_fire_message(void *msg_) { - iodine_msg2ruby_s *msg = msg_; - VALUE data = rb_enc_str_new( - msg->data, msg->size, - (msg->is_text ? rb_utf8_encoding() : rb_ascii8bit_encoding())); - iodine_connection_fire_event(msg->io, IODINE_CONNECTION_ON_MESSAGE, data); - return NULL; -} - -static void iodine_ws_on_message(ws_s *ws, fio_str_info_s data, - uint8_t is_text) { - iodine_msg2ruby_s msg = { - .data = data.data, - .size = data.len, - .is_text = is_text, - .io = (VALUE)websocket_udata_get(ws), - }; - IodineCaller.enterGVL(iodine_ws_fire_message, &msg); -} -/** - * The (optional) on_open callback will be called once the websocket - * connection is established and before is is registered with `facil`, so no - * `on_message` events are raised before `on_open` returns. - */ -static void iodine_ws_on_open(ws_s *ws) { - VALUE h = (VALUE)websocket_udata_get(ws); - iodine_connection_s *c = iodine_connection_CData(h); - c->arg = ws; - c->uuid = websocket_uuid(ws); - iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil); -} -/** - * The (optional) on_ready callback will be after a the underlying socket's - * buffer changes it's state from full to empty. - * - * If the socket's buffer is never used, the callback is never called. - */ -static void iodine_ws_on_ready(ws_s *ws) { - iodine_connection_fire_event((VALUE)websocket_udata_get(ws), - IODINE_CONNECTION_ON_DRAINED, Qnil); -} -/** - * The (optional) on_shutdown callback will be called if a websocket - * connection is still open while the server is shutting down (called before - * `on_close`). - */ -static void iodine_ws_on_shutdown(ws_s *ws) { - iodine_connection_fire_event((VALUE)websocket_udata_get(ws), - IODINE_CONNECTION_ON_SHUTDOWN, Qnil); -} -/** - * The (optional) on_close callback will be called once a websocket connection - * is terminated or failed to be established. - * - * The `uuid` is the connection's unique ID that can identify the Websocket. A - * value of `uuid == 0` indicates the Websocket connection wasn't established - * (an error occured). - * - * The `udata` is the user data as set during the upgrade or using the - * `websocket_udata_set` function. - */ -static void iodine_ws_on_close(intptr_t uuid, void *udata) { - iodine_connection_fire_event((VALUE)udata, IODINE_CONNECTION_ON_CLOSE, Qnil); - (void)uuid; -} - -static void iodine_ws_attach(http_s *h, VALUE handler, VALUE env) { - VALUE io = - iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL, - .handler = handler, .env = env, .uuid = 0); - if (io == Qnil) - return; - - http_upgrade2ws(h, .on_message = iodine_ws_on_message, - .on_open = iodine_ws_on_open, .on_ready = iodine_ws_on_ready, - .on_shutdown = iodine_ws_on_shutdown, - .on_close = iodine_ws_on_close, .udata = (void *)io); -} - -/* ***************************************************************************** -SSE support -***************************************************************************** */ - -static void iodine_sse_on_ready(http_sse_s *sse) { - iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_DRAINED, - Qnil); -} - -static void iodine_sse_on_shutdown(http_sse_s *sse) { - iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_SHUTDOWN, - Qnil); -} -static void iodine_sse_on_close(http_sse_s *sse) { - iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_CLOSE, - Qnil); -} - -static void iodine_sse_on_open(http_sse_s *sse) { - VALUE h = (VALUE)sse->udata; - iodine_connection_s *c = iodine_connection_CData(h); - c->arg = sse; - c->uuid = http_sse2uuid(sse); - iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil); - sse->on_ready = iodine_sse_on_ready; - fio_force_event(c->uuid, FIO_EVENT_ON_READY); -} - -static void iodine_sse_attach(http_s *h, VALUE handler, VALUE env) { - VALUE io = iodine_connection_new(.type = IODINE_CONNECTION_SSE, .arg = NULL, - .handler = handler, .env = env, .uuid = 0); - if (io == Qnil) - return; - - http_upgrade2sse(h, .on_open = iodine_sse_on_open, - .on_ready = NULL /* will be set after the on_open */, - .on_shutdown = iodine_sse_on_shutdown, - .on_close = iodine_sse_on_close, .udata = (void *)io); -} - -/* ***************************************************************************** -Copying data from the C request to the Rack's ENV -***************************************************************************** */ - -#define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c)) - -static int iodine_copy2env_task(FIOBJ o, void *env_) { - VALUE env = (VALUE)env_; - FIOBJ name = fiobj_hash_key_in_loop(); - fio_str_info_s tmp = fiobj_obj2cstr(name); - VALUE hname = (VALUE)0; - /* test for common header names, using pre-allocated memory */ - if (tmp.len == 6 && !memcmp("accept", tmp.data, 6)) { - hname = HTTP_ACCEPT; - } else if (tmp.len == 10 && !memcmp("user-agent", tmp.data, 10)) { - hname = HTTP_USER_AGENT; - } else if (tmp.len == 15 && !memcmp("accept-encoding", tmp.data, 15)) { - hname = HTTP_ACCEPT_ENCODING; - } else if (tmp.len == 15 && !memcmp("accept-language", tmp.data, 15)) { - hname = HTTP_ACCEPT_LANGUAGE; - } else if (tmp.len == 10 && !memcmp("connection", tmp.data, 10)) { - hname = HTTP_CONNECTION; - } else if (tmp.len == 4 && !memcmp("host", tmp.data, 4)) { - hname = HTTP_HOST; - } else if (tmp.len > 123) { - char *buf = fio_malloc(tmp.len + 5); - memcpy(buf, "HTTP_", 5); - for (size_t i = 0; i < tmp.len; ++i) { - buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]); - } - hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding); - fio_free(buf); - } else { - char buf[128]; - memcpy(buf, "HTTP_", 5); - for (size_t i = 0; i < tmp.len; ++i) { - buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]); - } - hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding); - } - - if (FIOBJ_TYPE_IS(o, FIOBJ_T_STRING)) { - tmp = fiobj_obj2cstr(o); - rb_hash_aset(env, hname, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - - } else { - /* it's an array */ - size_t count = fiobj_ary_count(o); - VALUE ary = rb_ary_new2(count); - rb_hash_aset(env, hname, ary); - for (size_t i = 0; i < count; ++i) { - tmp = fiobj_obj2cstr(fiobj_ary_index(o, i)); - rb_ary_push(ary, rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - } - } - return 0; -} - -static inline VALUE copy2env(iodine_http_request_handle_s *handle) { - VALUE env; - http_s *h = handle->h; - switch (handle->upgrade) { - case IODINE_UPGRADE_WEBSOCKET: - env = rb_hash_dup(env_template_websockets); - break; - case IODINE_UPGRADE_SSE: - env = rb_hash_dup(env_template_sse); - break; - case IODINE_UPGRADE_NONE: /* fallthrough */ - default: - env = rb_hash_dup(env_template_no_upgrade); - break; - } - IodineStore.add(env); - - fio_str_info_s tmp; - char *pos = NULL; - /* Copy basic data */ - tmp = fiobj_obj2cstr(h->method); - rb_hash_aset(env, REQUEST_METHOD, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - tmp = fiobj_obj2cstr(h->path); - rb_hash_aset(env, PATH_INFO, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - if (h->query) { - tmp = fiobj_obj2cstr(h->query); - rb_hash_aset(env, QUERY_STRING, - tmp.len - ? rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding) - : QUERY_ESTRING); - } else { - rb_hash_aset(env, QUERY_STRING, QUERY_ESTRING); - } - { - // HTTP version appears twice - tmp = fiobj_obj2cstr(h->version); - VALUE hname = rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding); - rb_hash_aset(env, SERVER_PROTOCOL, hname); - rb_hash_aset(env, HTTP_VERSION, hname); - } - - { // Support for Ruby web-console. - fio_str_info_s peer = http_peer_addr(h); - if (peer.len) { - rb_hash_aset(env, REMOTE_ADDR, rb_str_new(peer.data, peer.len)); - } - } - - /* handle the HOST header, including the possible host:#### format*/ - static uint64_t host_hash = 0; - if (!host_hash) - host_hash = fiobj_hash_string("host", 4); - tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, host_hash)); - pos = tmp.data; - while (*pos && *pos != ':') - pos++; - if (*pos == 0) { - rb_hash_aset(env, SERVER_NAME, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - } else { - rb_hash_aset( - env, SERVER_NAME, - rb_enc_str_new(tmp.data, pos - tmp.data, IodineBinaryEncoding)); - ++pos; - rb_hash_aset( - env, SERVER_PORT, - rb_enc_str_new(pos, tmp.len - (pos - tmp.data), IodineBinaryEncoding)); - } - - /* remove special headers */ - { - static uint64_t content_length_hash = 0; - if (!content_length_hash) - content_length_hash = fiobj_hash_string("content-length", 14); - FIOBJ cl = fiobj_hash_get2(h->headers, content_length_hash); - if (cl) { - tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, content_length_hash)); - if (tmp.data) { - rb_hash_aset(env, CONTENT_LENGTH, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - fiobj_hash_delete2(h->headers, content_length_hash); - } - } - } - { - static uint64_t content_type_hash = 0; - if (!content_type_hash) - content_type_hash = fiobj_hash_string("content-type", 12); - FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash); - if (ct) { - tmp = fiobj_obj2cstr(ct); - if (tmp.len && tmp.data) { - rb_hash_aset(env, CONTENT_TYPE, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - fiobj_hash_delete2(h->headers, content_type_hash); - } - } - } - /* handle scheme / sepcial forwarding headers */ - { - FIOBJ objtmp; - static uint64_t xforward_hash = 0; - if (!xforward_hash) - xforward_hash = fiobj_hash_string("x-forwarded-proto", 27); - static uint64_t forward_hash = 0; - if (!forward_hash) - forward_hash = fiobj_hash_string("forwarded", 9); - if ((objtmp = fiobj_hash_get2(h->headers, xforward_hash))) { - tmp = fiobj_obj2cstr(objtmp); - if (tmp.len >= 5 && !strncasecmp(tmp.data, "https", 5)) { - rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME); - } else if (tmp.len == 4 && !strncasecmp(tmp.data, "http", 4)) { - rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME); - } else { - rb_hash_aset(env, R_URL_SCHEME, - rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)); - } - } else if ((objtmp = fiobj_hash_get2(h->headers, forward_hash))) { - tmp = fiobj_obj2cstr(objtmp); - pos = tmp.data; - if (pos) { - while (*pos) { - if (((*(pos++) | 32) == 'p') && ((*(pos++) | 32) == 'r') && - ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == 't') && - ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == '=')) { - if ((pos[0] | 32) == 'h' && (pos[1] | 32) == 't' && - (pos[2] | 32) == 't' && (pos[3] | 32) == 'p') { - if ((pos[4] | 32) == 's') { - rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME); - } else { - rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME); - } - } else { - char *tmp = pos; - while (*tmp && *tmp != ';') - tmp++; - rb_hash_aset(env, R_URL_SCHEME, rb_str_new(pos, tmp - pos)); - } - break; - } - } - } - } else if (http_settings(h)->tls) { - /* no forwarding information, but we do have TLS */ - rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME); - } else { - /* no TLS, no forwarding, assume `http`, which is the default */ - } - } - - /* add all remaining headers */ - fiobj_each1(h->headers, 0, iodine_copy2env_task, (void *)env); - return env; -} -#undef add_str_to_env -#undef add_value_to_env -#undef add_header_to_env - -/* ***************************************************************************** -Handling the HTTP response -***************************************************************************** */ - -// itterate through the headers and add them to the response buffer -// (we are recycling the request's buffer) -static int for_each_header_data(VALUE key, VALUE val, VALUE h_) { - http_s *h = (http_s *)h_; - // fprintf(stderr, "For_each - headers\n"); - if (TYPE(val) == T_NIL || TYPE(key) == T_NIL) - return ST_CONTINUE; - if (TYPE(val) == T_ARRAY) - goto array_style_multi_value; - if (TYPE(key) != T_STRING) - key = IodineCaller.call(key, iodine_to_s_id); - if (TYPE(key) != T_STRING) - return ST_CONTINUE; - if (TYPE(val) != T_STRING) { - val = IodineCaller.call(val, iodine_to_s_id); - if (TYPE(val) != T_STRING) - return ST_STOP; - } - char *key_s = RSTRING_PTR(key); - int key_len = RSTRING_LEN(key); - char *val_s = RSTRING_PTR(val); - int val_len = RSTRING_LEN(val); - // make the headers lowercase - - FIOBJ name = fiobj_str_new(key_s, key_len); - { - fio_str_info_s tmp = fiobj_obj2cstr(name); - for (int i = 0; i < key_len; ++i) { - tmp.data[i] = tolower(tmp.data[i]); - } - } - // scan the value for newline (\n) delimiters - char *pos_s = val_s; - char *pos_e = val_s + val_len; - while (pos_s < pos_e) { - // scanning for newline (\n) delimiters - char *const start = pos_s; - pos_s = memchr(pos_s, '\n', pos_e - pos_s); - if (!pos_s) - pos_s = pos_e; - http_set_header(h, name, fiobj_str_new(start, (pos_s - start))); - // move forward (skip the '\n' if exists) - ++pos_s; - } - fiobj_free(name); - // no errors, return 0 - return ST_CONTINUE; - -array_style_multi_value: - for (size_t i = 0, end = RARRAY_LEN(val); i < end; ++i) { - if (for_each_header_data(key, RARRAY_AREF(val, i), h_) == ST_CONTINUE) - continue; - return ST_STOP; - } - return ST_CONTINUE; -} - -// writes the body to the response object -static VALUE for_each_body_string(VALUE str, VALUE body_, int argc, - VALUE *argv) { - // fprintf(stderr, "For_each - body\n"); - // write body - if (TYPE(str) != T_STRING) { - FIO_LOG_ERROR("(Iodine) response body not a String\n"); - return Qfalse; - } - if (RSTRING_LEN(str) && RSTRING_PTR(str)) { - fiobj_str_write((FIOBJ)body_, RSTRING_PTR(str), RSTRING_LEN(str)); - } - return Qtrue; - (void)argc; - (void)argv; -} - -static inline int ruby2c_response_send(iodine_http_request_handle_s *handle, - VALUE rbresponse, VALUE env) { - (void)(env); - VALUE body = rb_ary_entry(rbresponse, 2); - if (handle->h->status < 200 || handle->h->status == 204 || - handle->h->status == 304) { - if (body && rb_respond_to(body, close_method_id)) - IodineCaller.call(body, close_method_id); - body = Qnil; - handle->type = IODINE_HTTP_NONE; - return 0; - } - if (TYPE(body) == T_ARRAY) { - if (RARRAY_LEN(body) == 0) { // only headers - handle->type = IODINE_HTTP_EMPTY; - return 0; - } else if (RARRAY_LEN(body) == 1) { // [String] is likely - body = rb_ary_entry(body, 0); - // fprintf(stderr, "Body was a single item array, unpacket to string\n"); - } - } - - if (TYPE(body) == T_STRING) { - // fprintf(stderr, "Review body as String\n"); - handle->type = IODINE_HTTP_NONE; - if (RSTRING_LEN(body)) { - handle->body = fiobj_str_new(RSTRING_PTR(body), RSTRING_LEN(body)); - handle->type = IODINE_HTTP_SENDBODY; - } - return 0; - } else if (rb_respond_to(body, each_method_id)) { - // fprintf(stderr, "Review body as for-each ...\n"); - handle->body = fiobj_str_buf(1); - handle->type = IODINE_HTTP_SENDBODY; - IodineCaller.call_with_block(body, each_method_id, 0, NULL, - (VALUE)handle->body, for_each_body_string); - // we need to call `close` in case the object is an IO / BodyProxy - if (rb_respond_to(body, close_method_id)) - IodineCaller.call(body, close_method_id); - return 0; - } - return -1; -} - -/* ***************************************************************************** -Handling Upgrade cases -***************************************************************************** */ - -static inline int ruby2c_review_upgrade(iodine_http_request_handle_s *req, - VALUE rbresponse, VALUE env) { - http_s *h = req->h; - VALUE handler; - if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_CB)) != Qnil) { - // send headers - http_finish(h); - // call the callback - VALUE io_ruby = IodineCaller.call(rb_hash_aref(env, IODINE_R_HIJACK), - iodine_call_proc_id); - IodineCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby); - goto upgraded; - } else if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_IO)) != Qnil) { - // do nothing, just cleanup - goto upgraded; - } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil) { - goto tcp_ip_upgrade; - } else { - switch (req->upgrade) { - case IODINE_UPGRADE_WEBSOCKET: - if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) { - // use response as existing base for native websocket upgrade - iodine_ws_attach(h, handler, env); - goto upgraded; - } - break; - case IODINE_UPGRADE_SSE: - if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) { - // use response as existing base for SSE upgrade - iodine_sse_attach(h, handler, env); - goto upgraded; - } - break; - default: - if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) { - tcp_ip_upgrade : { - // use response as existing base for raw TCP/IP upgrade - intptr_t uuid = http_hijack(h, NULL); - // send headers - http_finish(h); - // upgrade protocol to raw TCP/IP - iodine_tcp_attch_uuid(uuid, handler); - goto upgraded; - } - } - break; - } - } - return 0; - -upgraded: - // get body object to close it (if needed) - handler = rb_ary_entry(rbresponse, 2); - // we need to call `close` in case the object is an IO / BodyProxy - if (handler != Qnil && rb_respond_to(handler, close_method_id)) - IodineCaller.call(handler, close_method_id); - return 1; -} - -/* ***************************************************************************** -Handling HTTP requests -***************************************************************************** */ - -static inline void *iodine_handle_request_in_GVL(void *handle_) { - iodine_http_request_handle_s *handle = handle_; - VALUE rbresponse = 0; - VALUE env = 0; - http_s *h = handle->h; - if (!h->udata) - goto err_not_found; - - // create / register env variable - env = copy2env(handle); - // create rack.io - VALUE tmp = IodineRackIO.create(h, env); - // pass env variable to handler - rbresponse = - IodineCaller.call2((VALUE)h->udata, iodine_call_proc_id, 1, &env); - // close rack.io - IodineRackIO.close(tmp); - // test handler's return value - if (rbresponse == 0 || rbresponse == Qnil || TYPE(rbresponse) != T_ARRAY) - goto internal_error; - IodineStore.add(rbresponse); - - // set response status - tmp = rb_ary_entry(rbresponse, 0); - if (TYPE(tmp) == T_STRING) { - char *data = RSTRING_PTR(tmp); - h->status = fio_atol(&data); - } else if (TYPE(tmp) == T_FIXNUM) { - h->status = FIX2ULONG(tmp); - } else { - goto internal_error; - } - - // handle header copy from ruby land to C land. - VALUE response_headers = rb_ary_entry(rbresponse, 1); - if (TYPE(response_headers) != T_HASH) - goto internal_error; - // extract the X-Sendfile header (never show original path) - // X-Sendfile support only present when iodine serves static files. - VALUE xfiles; - if (support_xsendfile && - (xfiles = rb_hash_aref(response_headers, XSENDFILE)) != Qnil && - TYPE(xfiles) == T_STRING) { - if (OBJ_FROZEN(response_headers)) { - response_headers = rb_hash_dup(response_headers); - } - IodineStore.add(response_headers); - handle->body = fiobj_str_new(RSTRING_PTR(xfiles), RSTRING_LEN(xfiles)); - handle->type = IODINE_HTTP_XSENDFILE; - rb_hash_delete(response_headers, XSENDFILE); - // remove content length headers, as this will be controled by iodine - rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER); - // review each header and write it to the response. - rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h)); - IodineStore.remove(response_headers); - // send the file directly and finish - goto finish; - } - // review each header and write it to the response. - rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h)); - // review for upgrade. - if ((intptr_t)h->status < 300 && - ruby2c_review_upgrade(handle, rbresponse, env)) - goto external_done; - // send the request body. - if (ruby2c_response_send(handle, rbresponse, env)) - goto internal_error; - -finish: - IodineStore.remove(rbresponse); - IodineStore.remove(env); - return NULL; - -external_done: - IodineStore.remove(rbresponse); - IodineStore.remove(env); - handle->type = IODINE_HTTP_NONE; - return NULL; - -err_not_found: - IodineStore.remove(rbresponse); - IodineStore.remove(env); - h->status = 404; - handle->type = IODINE_HTTP_ERROR; - return NULL; - -internal_error: - IodineStore.remove(rbresponse); - IodineStore.remove(env); - h->status = 500; - handle->type = IODINE_HTTP_ERROR; - return NULL; -} - -static inline void -iodine_perform_handle_action(iodine_http_request_handle_s handle) { - switch (handle.type) { - case IODINE_HTTP_SENDBODY: { - fio_str_info_s data = fiobj_obj2cstr(handle.body); - http_send_body(handle.h, data.data, data.len); - fiobj_free(handle.body); - break; - } - case IODINE_HTTP_XSENDFILE: { - /* remove chunked content-encoding header, if any (Rack issue #1266) */ - if (fiobj_obj2cstr( - fiobj_hash_get2(handle.h->private_data.out_headers, - fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING))) - .len == 7) - fiobj_hash_delete2(handle.h->private_data.out_headers, - fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING)); - fio_str_info_s data = fiobj_obj2cstr(handle.body); - if (http_sendfile2(handle.h, data.data, data.len, NULL, 0)) { - http_send_error(handle.h, 404); - } - fiobj_free(handle.body); - break; - } - case IODINE_HTTP_EMPTY: - http_finish(handle.h); - fiobj_free(handle.body); - break; - case IODINE_HTTP_NONE: - /* nothing to do - this had to be performed within the Ruby GIL :-( */ - break; - case IODINE_HTTP_ERROR: - http_send_error(handle.h, handle.h->status); - fiobj_free(handle.body); - break; - } -} -static void on_rack_request(http_s *h) { - iodine_http_request_handle_s handle = (iodine_http_request_handle_s){ - .h = h, - .upgrade = IODINE_UPGRADE_NONE, - }; - IodineCaller.enterGVL((void *(*)(void *))iodine_handle_request_in_GVL, - &handle); - iodine_perform_handle_action(handle); -} - -static void on_rack_upgrade(http_s *h, char *proto, size_t len) { - iodine_http_request_handle_s handle = (iodine_http_request_handle_s){.h = h}; - if (len == 9 && (proto[1] == 'e' || proto[1] == 'E')) { - handle.upgrade = IODINE_UPGRADE_WEBSOCKET; - } else if (len == 3 && proto[0] == 's') { - handle.upgrade = IODINE_UPGRADE_SSE; - } - /* when we stop supporting custom Upgrade headers: */ - // else { - // http_send_error(h, 400); - // return; - // } - IodineCaller.enterGVL(iodine_handle_request_in_GVL, &handle); - iodine_perform_handle_action(handle); - (void)proto; - (void)len; -} - -/* ***************************************************************************** -Rack `env` Template Initialization -***************************************************************************** */ - -static void initialize_env_template(void) { - if (env_template_no_upgrade) - return; - env_template_no_upgrade = rb_hash_new(); - IodineStore.add(env_template_no_upgrade); - -#define add_str_to_env(env, key, value) \ - { \ - VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \ - rb_obj_freeze(k); \ - VALUE v = rb_enc_str_new((value), strlen((value)), IodineBinaryEncoding); \ - rb_obj_freeze(v); \ - rb_hash_aset(env, k, v); \ - } -#define add_value_to_env(env, key, value) \ - { \ - VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \ - rb_obj_freeze(k); \ - rb_hash_aset((env), k, value); \ - } - - /* Set global template */ - rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE_Q, Qnil); - rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE, Qnil); - { - /* add the rack.version */ - static VALUE rack_version = 0; - if (!rack_version) { - rack_version = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible - rb_ary_push(rack_version, INT2FIX(1)); - rb_ary_push(rack_version, INT2FIX(3)); - rb_global_variable(&rack_version); - rb_ary_freeze(rack_version); - } - add_value_to_env(env_template_no_upgrade, "rack.version", rack_version); - } - - { - const char *sn = getenv("SCRIPT_NAME"); - if (!sn || (sn[0] == '/' && sn[1] == 0)) { - sn = ""; - } - add_str_to_env(env_template_no_upgrade, "SCRIPT_NAME", sn); - } - rb_hash_aset(env_template_no_upgrade, IODINE_R_INPUT, IODINE_R_INPUT_DEFAULT); - add_value_to_env(env_template_no_upgrade, "rack.errors", rb_stderr); - add_value_to_env(env_template_no_upgrade, "rack.hijack?", Qtrue); - add_value_to_env(env_template_no_upgrade, "rack.multiprocess", Qtrue); - add_value_to_env(env_template_no_upgrade, "rack.multithread", Qtrue); - add_value_to_env(env_template_no_upgrade, "rack.run_once", Qfalse); - /* default schema to http, it might be updated later */ - rb_hash_aset(env_template_no_upgrade, R_URL_SCHEME, HTTP_SCHEME); - /* placeholders... minimize rehashing*/ - rb_hash_aset(env_template_no_upgrade, HTTP_VERSION, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, IODINE_R_HIJACK, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, PATH_INFO, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, QUERY_STRING, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, REMOTE_ADDR, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, REQUEST_METHOD, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, SERVER_NAME, QUERY_STRING); - rb_hash_aset(env_template_no_upgrade, SERVER_PROTOCOL, QUERY_STRING); - - /* WebSocket upgrade support */ - env_template_websockets = rb_hash_dup(env_template_no_upgrade); - IodineStore.add(env_template_websockets); - rb_hash_aset(env_template_websockets, RACK_UPGRADE_Q, RACK_UPGRADE_WEBSOCKET); - - /* SSE upgrade support */ - env_template_sse = rb_hash_dup(env_template_no_upgrade); - IodineStore.add(env_template_sse); - rb_hash_aset(env_template_sse, RACK_UPGRADE_Q, RACK_UPGRADE_SSE); - -#undef add_value_to_env -#undef add_str_to_env -} - -/* ***************************************************************************** -Listenninng to HTTP -***************************************************************************** -*/ - -static void free_iodine_http(http_settings_s *s) { - IodineStore.remove((VALUE)s->udata); -} - -// clang-format off -/** -Listens to incoming HTTP connections and handles incoming requests using the -Rack specification. - -This is delegated to a lower level C HTTP and Websocket implementation, no -Ruby object will be crated except the `env` object required by the Rack -specifications. - -Accepts a single Hash argument with the following properties: - -(it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash) - -app:: the Rack application that handles incoming requests. Default: `nil`. -port:: the port to listen to. Default: 3000. -address:: the address to bind to. Default: binds to all possible addresses. -log:: enable response logging (Hijacked sockets aren't logged). Default: off. -public:: The root public folder for static file service. Default: none. -timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds. -max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib. -max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib. -max_msg:: The maximum Websocket message size allowed. Default: ~250Kib. -ping:: The Websocket `ping` interval. Default: 40 seconds. - -Either the `app` or the `public` properties are required. If niether exists, -the function will fail. If both exist, Iodine will serve static files as well -as dynamic requests. - -When using the static file server, it's possible to serve `gzip` versions of -the static files by saving a compressed version with the `gz` extension (i.e. -`styles.css.gz`). - -`gzip` will only be served to clients tat support the `gzip` transfer -encoding. - -Once HTTP/2 is supported (planned, but probably very far away), HTTP/2 -timeouts will be dynamically managed by Iodine. The `timeout` option is only -relevant to HTTP/1.x connections. -*/ -intptr_t iodine_http_listen(iodine_connection_args_s args){ - // clang-format on - if (args.public.data) { - rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE); - rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE); - support_xsendfile = 1; - } - IodineStore.add(args.handler); -#ifdef __MINGW32__ - intptr_t uuid = http_listen( - args.port.data, args.address.data, .on_request = on_rack_request, - .on_upgrade = on_rack_upgrade, .udata = (void *)args.handler, - .timeout = args.timeout, .ws_timeout = args.ping, - .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers, - .on_finish = free_iodine_http, .log = args.log, - .max_body_size = args.max_body, .public_folder = args.public.data); -#else - intptr_t uuid = http_listen( - args.port.data, args.address.data, .on_request = on_rack_request, - .on_upgrade = on_rack_upgrade, .udata = (void *)args.handler, - .tls = args.tls, .timeout = args.timeout, .ws_timeout = args.ping, - .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers, - .on_finish = free_iodine_http, .log = args.log, - .max_body_size = args.max_body, .public_folder = args.public.data); -#endif - if (uuid == -1) - return uuid; - - if ((args.handler == Qnil || args.handler == Qfalse)) { - FIO_LOG_WARNING("(listen) no handler / app, the HTTP service on port %s " - "will only serve " - "static files.", - args.port.data ? args.port.data : args.address.data); - } - if (args.public.data) { - FIO_LOG_INFO("Serving static files from %s", args.public.data); - } - - return uuid; -} - -/* ***************************************************************************** -HTTP Websocket Connect -***************************************************************************** */ - -typedef struct { - FIOBJ method; - FIOBJ headers; - FIOBJ cookies; - FIOBJ body; - VALUE io; -} request_data_s; - -static request_data_s *request_data_create(iodine_connection_args_s *args) { - request_data_s *r = fio_malloc(sizeof(*r)); - FIO_ASSERT_ALLOC(r); - VALUE io = - iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL, - .handler = args->handler, .env = Qnil, .uuid = 0); - - *r = (request_data_s){ - .method = fiobj_str_new(args->method.data, args->method.len), - .headers = fiobj_dup(args->headers), - .cookies = fiobj_dup(args->cookies), - .body = fiobj_str_new(args->body.data, args->body.len), - .io = io, - }; - return r; -} - -static void request_data_destroy(request_data_s *r) { - fiobj_free(r->method); - fiobj_free(r->body); - fiobj_free(r->headers); - fiobj_free(r->cookies); - fio_free(r); -} - -static int each_header_ws_client_task(FIOBJ val, void *h_) { - http_s *h = h_; - FIOBJ key = fiobj_hash_key_in_loop(); - http_set_header(h, key, fiobj_dup(val)); - return 0; -} -static int each_cookie_ws_client_task(FIOBJ val, void *h_) { - http_s *h = h_; - FIOBJ key = fiobj_hash_key_in_loop(); - fio_str_info_s n = fiobj_obj2cstr(key); - fio_str_info_s v = fiobj_obj2cstr(val); - http_set_cookie(h, .name = n.data, .name_len = n.len, .value = v.data, - .value_len = v.len); - return 0; -} - -static void ws_client_http_connected(http_s *h) { - request_data_s *s = h->udata; - if (!s) - return; - h->udata = http_settings(h)->udata = NULL; - if (!h->path) { - h->path = fiobj_str_new("/", 1); - } - /* TODO: add headers and cookies */ - fiobj_each1(s->headers, 0, each_header_ws_client_task, h); - fiobj_each1(s->headers, 0, each_cookie_ws_client_task, h); - if (s->io && s->io != Qnil) - http_upgrade2ws( - h, .on_message = iodine_ws_on_message, .on_open = iodine_ws_on_open, - .on_ready = iodine_ws_on_ready, .on_shutdown = iodine_ws_on_shutdown, - .on_close = iodine_ws_on_close, .udata = (void *)s->io); - request_data_destroy(s); -} - -static void ws_client_http_connection_finished(http_settings_s *settings) { - if (!settings) - return; - request_data_s *s = settings->udata; - if (s) { - if (s->io && s->io != Qnil) - iodine_connection_fire_event(s->io, IODINE_CONNECTION_ON_CLOSE, Qnil); - request_data_destroy(s); - } -} - -/** Connects to a (remote) WebSocket service. */ -intptr_t iodine_ws_connect(iodine_connection_args_s args) { - // http_connect(url, unixaddr, struct http_settings_s) - uint8_t is_unix_socket = 0; - if (memchr(args.address.data, '/', args.address.len)) { - is_unix_socket = 1; - } - FIOBJ url_tmp = FIOBJ_INVALID; - if (!args.url.data) { - url_tmp = fiobj_str_buf(64); -#ifndef __MINGW32__ - if (args.tls) - fiobj_str_write(url_tmp, "wss://", 6); - else -#endif - fiobj_str_write(url_tmp, "ws://", 5); - if (!is_unix_socket) { - fiobj_str_write(url_tmp, args.address.data, args.address.len); - if (args.port.data) { - fiobj_str_write(url_tmp, ":", 1); - fiobj_str_write(url_tmp, args.port.data, args.port.len); - } - } - if (args.path.data) - fiobj_str_write(url_tmp, args.path.data, args.path.len); - else - fiobj_str_write(url_tmp, "/", 1); - args.url = fiobj_obj2cstr(url_tmp); - } - -#ifdef __MINGW32__ - intptr_t uuid = - http_connect(args.url.data, (is_unix_socket ? args.address.data : NULL), - .udata = request_data_create(&args), - .on_response = ws_client_http_connected, - .on_finish = ws_client_http_connection_finished); -#else - intptr_t uuid = http_connect( - args.url.data, (is_unix_socket ? args.address.data : NULL), - .udata = request_data_create(&args), - .on_response = ws_client_http_connected, - .on_finish = ws_client_http_connection_finished, .tls = args.tls); -#endif - fiobj_free(url_tmp); - return uuid; -} - -/* ***************************************************************************** -Initialization -***************************************************************************** */ - -void iodine_init_http(void) { - - rack_autoset(REQUEST_METHOD); - rack_autoset(PATH_INFO); - rack_autoset(QUERY_STRING); - rack_autoset(SERVER_NAME); - rack_autoset(SERVER_PORT); - rack_autoset(CONTENT_LENGTH); - rack_autoset(CONTENT_TYPE); - rack_autoset(SERVER_PROTOCOL); - rack_autoset(HTTP_VERSION); - rack_autoset(REMOTE_ADDR); - - rack_autoset(HTTP_ACCEPT); - rack_autoset(HTTP_USER_AGENT); - rack_autoset(HTTP_ACCEPT_ENCODING); - rack_autoset(HTTP_ACCEPT_LANGUAGE); - rack_autoset(HTTP_CONNECTION); - rack_autoset(HTTP_HOST); - - rack_set(HTTP_SCHEME, "http"); - rack_set(HTTPS_SCHEME, "https"); - rack_set(QUERY_ESTRING, ""); - rack_set(R_URL_SCHEME, "rack.url_scheme"); - rack_set(R_INPUT, "rack.input"); - rack_set(XSENDFILE, "X-Sendfile"); - rack_set(XSENDFILE_TYPE, "sendfile.type"); - rack_set(XSENDFILE_TYPE_HEADER, "HTTP_X_SENDFILE_TYPE"); - rack_set(CONTENT_LENGTH_HEADER, "Content-Length"); - - rack_set(IODINE_R_INPUT, "rack.input"); - rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io"); - rack_set(IODINE_R_HIJACK, "rack.hijack"); - rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb"); - - rack_set(RACK_UPGRADE, "rack.upgrade"); - rack_set(RACK_UPGRADE_Q, "rack.upgrade?"); - rack_set_sym(RACK_UPGRADE_SSE, "sse"); - rack_set_sym(RACK_UPGRADE_WEBSOCKET, "websocket"); - - UPGRADE_TCP = IodineStore.add(rb_str_new("upgrade.tcp", 11)); - - hijack_func_sym = ID2SYM(rb_intern("_hijack")); - close_method_id = rb_intern("close"); - each_method_id = rb_intern("each"); - attach_method_id = rb_intern("attach_fd"); - iodine_call_proc_id = rb_intern("call"); - - IodineUTF8Encoding = rb_enc_find("UTF-8"); - IodineBinaryEncoding = rb_enc_find("binary"); - - { - VALUE STRIO_CLASS = rb_const_get(rb_cObject, rb_intern("StringIO")); - IODINE_R_INPUT_DEFAULT = rb_str_new_static("", 0); - IODINE_R_INPUT_DEFAULT = - rb_funcallv(STRIO_CLASS, rb_intern("new"), 1, &IODINE_R_INPUT_DEFAULT); - rb_global_variable(&IODINE_R_INPUT_DEFAULT); - } - initialize_env_template(); -} diff --git a/ext/iodine/iodine_http.h b/ext/iodine/iodine_http.h deleted file mode 100644 index f6a5b0db..00000000 --- a/ext/iodine/iodine_http.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef H_IODINE_HTTP_H -#define H_IODINE_HTTP_H -/* -Copyright: Boaz segev, 2016-2018 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include "iodine.h" - -/* these three are used also by rb-rack-io.c */ -extern VALUE IODINE_R_INPUT; -extern VALUE IODINE_R_INPUT_DEFAULT; -extern VALUE IODINE_R_HIJACK; -extern VALUE IODINE_R_HIJACK_IO; -extern VALUE IODINE_R_HIJACK_CB; -void iodine_init_http(void); - -intptr_t iodine_http_listen(iodine_connection_args_s args); -// intptr_t iodine_http_connect(iodine_connection_args_s args); // not yet... -intptr_t iodine_ws_connect(iodine_connection_args_s args); - -#endif diff --git a/ext/iodine/iodine_json.c b/ext/iodine/iodine_json.c deleted file mode 100644 index 6edad0e6..00000000 --- a/ext/iodine/iodine_json.c +++ /dev/null @@ -1,302 +0,0 @@ -#include "iodine.h" - -#include "fio.h" - -#include "fio_json_parser.h" -#include "fiobj.h" -#include "iodine_fiobj2rb.h" -#include "iodine_store.h" - -static VALUE max_nesting; -static VALUE allow_nan; -static VALUE symbolize_names; -static VALUE create_additions; -static VALUE object_class; -static VALUE array_class; - -#define FIO_ARY_NAME fio_json_stack -#define FIO_ARY_TYPE VALUE -#include "fio.h" - -/* ***************************************************************************** -JSON Callacks - these must be implemented in the C file that uses the parser -***************************************************************************** */ - -/* ***************************************************************************** -JSON Parser handling (practically copied from the FIOBJ library) -***************************************************************************** */ - -typedef struct { - json_parser_s p; - VALUE key; - VALUE top; - VALUE target; - fio_json_stack_s stack; - uint8_t is_hash; - uint8_t symbolize; -} iodine_json_parser_s; - -static inline void iodine_json_add2parser(iodine_json_parser_s *p, VALUE o) { - if (p->top) { - if (p->is_hash) { - if (p->key) { - rb_hash_aset(p->top, p->key, o); - IodineStore.remove(p->key); - p->key = (VALUE)0; - } else { - p->key = o; - IodineStore.add(o); - } - } else { - rb_ary_push(p->top, o); - } - } else { - IodineStore.add(o); - p->top = o; - } -} - -/** a NULL object was detected */ -static void fio_json_on_null(json_parser_s *p) { - iodine_json_add2parser((iodine_json_parser_s *)p, Qnil); -} -/** a TRUE object was detected */ -static void fio_json_on_true(json_parser_s *p) { - iodine_json_add2parser((iodine_json_parser_s *)p, Qtrue); -} -/** a FALSE object was detected */ -static void fio_json_on_false(json_parser_s *p) { - iodine_json_add2parser((iodine_json_parser_s *)p, Qfalse); -} -/** a Numberl was detected (long long). */ -static void fio_json_on_number(json_parser_s *p, long long i) { - iodine_json_add2parser((iodine_json_parser_s *)p, LONG2NUM(i)); -} -/** a Float was detected (double). */ -static void fio_json_on_float(json_parser_s *p, double f) { - iodine_json_add2parser((iodine_json_parser_s *)p, DBL2NUM(f)); -} -/** a String was detected (int / float). update `pos` to point at ending */ -static void fio_json_on_string(json_parser_s *p, void *start, size_t length) { - /* Ruby overhead for a rb_str_buf_new is very high. Double copy is faster. */ - char *tmp = fio_malloc(length); - size_t new_len = fio_json_unescape_str(tmp, start, length); - VALUE buf; - if (((iodine_json_parser_s *)p)->symbolize && - ((iodine_json_parser_s *)p)->is_hash && - !((iodine_json_parser_s *)p)->key) { - ID id = rb_intern2(tmp, new_len); - buf = rb_id2sym(id); - } else { - buf = rb_str_new(tmp, new_len); - } - iodine_json_add2parser((iodine_json_parser_s *)p, buf); - fio_free(tmp); -} -/** a dictionary object was detected, should return 0 unless error occurred. */ -static int fio_json_on_start_object(json_parser_s *p) { - iodine_json_parser_s *pr = (iodine_json_parser_s *)p; - if (pr->target) { - /* push NULL, don't free the objects */ - fio_json_stack_push(&pr->stack, pr->top); - pr->top = pr->target; - pr->target = 0; - } else { - VALUE h = rb_hash_new(); - iodine_json_add2parser(pr, h); - fio_json_stack_push(&pr->stack, pr->top); - pr->top = h; - } - pr->is_hash = 1; - return 0; -} -/** a dictionary object closure detected */ -static void fio_json_on_end_object(json_parser_s *p) { - iodine_json_parser_s *pr = (iodine_json_parser_s *)p; - if (pr->key) { - FIO_LOG_WARNING("(JSON parsing) malformed JSON, " - "ignoring dangling Hash key."); - IodineStore.remove(pr->key); - pr->key = (VALUE)0; - } - fio_json_stack_pop(&pr->stack, &pr->top); - pr->is_hash = (TYPE(pr->top) == T_HASH); -} -/** an array object was detected */ -static int fio_json_on_start_array(json_parser_s *p) { - iodine_json_parser_s *pr = (iodine_json_parser_s *)p; - if (pr->target) - return -1; - VALUE ary = rb_ary_new(); - iodine_json_add2parser(pr, ary); - fio_json_stack_push(&pr->stack, pr->top); - pr->top = ary; - pr->is_hash = 0; - return 0; -} -/** an array closure was detected */ -static void fio_json_on_end_array(json_parser_s *p) { - iodine_json_parser_s *pr = (iodine_json_parser_s *)p; - fio_json_stack_pop(&pr->stack, &pr->top); - pr->is_hash = (TYPE(pr->top) == T_HASH); -} -/** the JSON parsing is complete */ -static void fio_json_on_json(json_parser_s *p) { (void)p; /* do nothing */ } -/** the JSON parsing is complete */ -static void fio_json_on_error(json_parser_s *p) { - iodine_json_parser_s *pr = (iodine_json_parser_s *)p; -#if DEBUG - FIO_LOG_ERROR("JSON on error called."); -#endif - IodineStore.remove((VALUE)fio_json_stack_get(&pr->stack, 0)); - IodineStore.remove(pr->key); - fio_json_stack_free(&pr->stack); - *pr = (iodine_json_parser_s){.top = 0}; -} - -/* ***************************************************************************** -Iodine JSON Implementation -***************************************************************************** */ - -static inline VALUE iodine_json_convert(VALUE str, fiobj2rb_settings_s s) { - - iodine_json_parser_s p = {.top = 0, .symbolize = s.str2sym}; - size_t consumed = fio_json_parse(&p.p, RSTRING_PTR(str), RSTRING_LEN(str)); - if (!consumed || p.p.depth) { - IodineStore.remove((VALUE)fio_json_stack_get(&p.stack, 0)); - p.top = FIOBJ_INVALID; - } - fio_json_stack_free(&p.stack); - if (p.key) { - IodineStore.remove((VALUE)p.key); - } - if (!p.top) { - rb_raise(rb_eEncodingError, "Malformed JSON format."); - } - IodineStore.remove(p.top); - return p.top; -} - -// static inline VALUE iodine_json_convert2(VALUE str, fiobj2rb_settings_s s) { -// FIOBJ json; -// if (!fiobj_json2obj(&json, RSTRING_PTR(str), RSTRING_LEN(str)) || !json) { -// rb_raise(rb_eRuntimeError, "JSON parsing failed. Not JSON?"); -// return Qnil; -// } -// VALUE ret = fiobj2rb_deep(json, s.str2sym); -// fiobj_free(json); -// IodineStore.remove(ret); -// return ret; -// } - -static inline void iodine_json_update_settings(VALUE h, - fiobj2rb_settings_s *s) { - VALUE tmp; - if (rb_hash_aref(h, max_nesting) != Qnil) - FIO_LOG_WARNING("max_nesting ignored on this JSON implementation."); - if (rb_hash_aref(h, allow_nan) != Qnil) - fprintf(stderr, "WARNING: allow_nan ignored on this JSON implementation. " - "NaN always allowed.\n"); - if (rb_hash_aref(h, create_additions) != Qnil) - FIO_LOG_WARNING("create_additions ignored on this JSON implementation."); - if (rb_hash_aref(h, object_class) != Qnil) - FIO_LOG_WARNING("object_class ignored on this JSON implementation."); - if (rb_hash_aref(h, array_class) != Qnil) - FIO_LOG_WARNING("array_class ignored on this JSON implementation."); - if ((tmp = rb_hash_aref(h, symbolize_names)) != Qnil) { - if (tmp == Qtrue) - s->str2sym = 1; - else if (tmp == Qfalse) - s->str2sym = 0; - } -} - -/** -Parse a JSON string using the iodine lenient parser (it's also faster). -*/ -static VALUE iodine_json_parse(int argc, VALUE *argv, VALUE self) { - fiobj2rb_settings_s s = {.str2sym = 0}; - if (argc > 2) - rb_raise(rb_eTypeError, "function requires supports up to two arguments."); - if (argc == 2) { - Check_Type(argv[1], T_HASH); - iodine_json_update_settings(argv[1], &s); - } - if (argc >= 1) - Check_Type(argv[0], T_STRING); - else - rb_raise(rb_eTypeError, "function requires at least one argument."); - return iodine_json_convert(argv[0], s); - (void)self; -} - -/** -Parse a JSON string using the iodine lenient parser with a default Symbol -rather than String key (this is often faster than the regular -{Iodine::JSON.parse} function). -*/ -static VALUE iodine_json_parse_bang(int argc, VALUE *argv, VALUE self) { - fiobj2rb_settings_s s = {.str2sym = 0}; - if (argc > 2) - rb_raise(rb_eTypeError, "function requires supports up to two arguments."); - if (argc == 2) { - Check_Type(argv[1], T_HASH); - iodine_json_update_settings(argv[1], &s); - } - if (argc >= 1) - Check_Type(argv[0], T_STRING); - else - rb_raise(rb_eTypeError, "function requires at least one argument."); - return iodine_json_convert(argv[0], s); - (void)self; -} - -void iodine_init_json(void) { - /** - Iodine::JSON offers a fast(er) JSON parser that is also lenient and supports - some JSON extensions such as Hex number recognition and comments. - - You can test the parser using: - - JSON_FILENAME="foo.json" - - require 'json' - require 'iodine' - TIMES = 100 - STR = IO.binread(JSON_FILENAME); nil - - JSON.parse(STR) == Iodine::JSON.parse(STR) # => true - JSON.parse(STR, - symbolize_names: true) == Iodine::JSON.parse(STR, - symbolize_names: true) # => true - JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => true/false (unknown) - - # warm-up - TIMES.times { JSON.parse STR } - TIMES.times { Iodine::JSON.parse STR } - - Benchmark.bm do |b| - sys = b.report("system") { TIMES.times { JSON.parse STR } } - sys_sym = b.report("system sym") { TIMES.times { JSON.parse STR, - symbolize_names: true } } - iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } } - iodine_sym = b.report("iodine sym") do - TIMES.times { Iodine::JSON.parse STR, - symbolize_names: true } - end - puts "System / Iodine: #{sys/iodine}" - puts "System-sym/Iodine-sym: #{sys_sym/iodine_sym}" - end; nil - - - */ - VALUE tmp = rb_define_module_under(IodineModule, "JSON"); - max_nesting = ID2SYM(rb_intern("max_nesting")); - allow_nan = ID2SYM(rb_intern("allow_nan")); - symbolize_names = ID2SYM(rb_intern("symbolize_names")); - create_additions = ID2SYM(rb_intern("create_additions")); - object_class = ID2SYM(rb_intern("object_class")); - array_class = ID2SYM(rb_intern("array_class")); - rb_define_module_function(tmp, "parse", iodine_json_parse, -1); - rb_define_module_function(tmp, "parse!", iodine_json_parse_bang, -1); -} diff --git a/ext/iodine/iodine_json.h b/ext/iodine/iodine_json.h index 11c337a1..3f69f32a 100644 --- a/ext/iodine/iodine_json.h +++ b/ext/iodine/iodine_json.h @@ -1,6 +1,285 @@ -#ifndef H_IODINE_JSON_H -#define H_IODINE_JSON_H +#ifndef H___IODINE_JSON___H +#define H___IODINE_JSON___H +#include "iodine.h" -void iodine_init_json(void); +/* ***************************************************************************** +JSON Stringifier. +***************************************************************************** */ +static char *iodine_json_stringify2bstr(char *dest, VALUE o); -#endif +static int iodine_json_hash_each_callback(VALUE k, VALUE v, VALUE dest_) { + char **pdest = (char **)dest_; + *pdest = iodine_json_stringify2bstr(*pdest, k); + *pdest = fio_bstr_write(*pdest, ":", 1); + *pdest = iodine_json_stringify2bstr(*pdest, v); + *pdest = fio_bstr_write(*pdest, ",", 1); + return ST_CONTINUE; +} + +/* Converts a VALUE to a fio_bstr */ +static char *iodine_json_stringify2bstr(char *dest, VALUE o) { + size_t tmp; + switch (rb_type(o)) { + case RUBY_T_NIL: return (dest = fio_bstr_write(dest, "null", 4)); + case RUBY_T_TRUE: return (dest = fio_bstr_write(dest, "true", 4)); + case RUBY_T_FALSE: return (dest = fio_bstr_write(dest, "false", 5)); + case RUBY_T_ARRAY: + tmp = rb_array_len(o); + if (!tmp) { + dest = fio_bstr_write(dest, "[]", 2); + return dest; + } + dest = fio_bstr_write(dest, "[", 1); + for (size_t i = 0; i < tmp; ++i) { + dest = iodine_json_stringify2bstr(dest, RARRAY_PTR(o)[i]); + dest = fio_bstr_write(dest, ",", 1); + } + dest[fio_bstr_len(dest) - 1] = ']'; + return dest; + case RUBY_T_HASH: + tmp = rb_hash_size_num(o); + if (!tmp) { + dest = fio_bstr_write(dest, "{}", 2); + return dest; + } + dest = fio_bstr_write(dest, "{", 1); + rb_hash_foreach(o, iodine_json_hash_each_callback, (VALUE)(&dest)); + dest[fio_bstr_len(dest) - 1] = '}'; + return dest; + case RUBY_T_FIXNUM: return (dest = fio_bstr_write_i(dest, RB_NUM2LL(o))); + case RUBY_T_FLOAT: { + FIO_STR_INFO_TMP_VAR(buf, 232); + buf.len = fio_ftoa(buf.buf, RFLOAT_VALUE(o), 10); + dest = fio_bstr_write(dest, buf.buf, buf.len); + return dest; + } + default: + if (RB_TYPE_P(o, RUBY_T_SYMBOL)) + // o = rb_sym_to_s(o); + o = rb_sym2str(o); + if (!RB_TYPE_P(o, RUBY_T_STRING)) + o = rb_funcallv(o, IODINE_TO_S_ID, 0, NULL); + if (!RB_TYPE_P(o, RUBY_T_STRING)) { + FIO_LOG_ERROR("Iodine::JSON.stringify called with an object that doesn't " + "respond to #to_s."); + return (dest = fio_bstr_write(dest, "null", 4)); + } + // fall through + case RUBY_T_STRING: + dest = fio_bstr_write(dest, "\"", 1); + dest = fio_bstr_write_escape(dest, RSTRING_PTR(o), (size_t)RSTRING_LEN(o)); + dest = fio_bstr_write(dest, "\"", 1); + return dest; + } +} + +/* ***************************************************************************** +JSON Parser (direct 2 Ruby) +***************************************************************************** */ + +/** The JSON parser settings. */ +/** NULL object was detected. Returns new object as `void *`. */ +FIO_SFUNC void *iodine___json_on_null(void) { return (void *)Qnil; } +/** TRUE object was detected. Returns new object as `void *`. */ +FIO_SFUNC void *iodine___json_on_true(void) { return (void *)Qtrue; } +/** FALSE object was detected. Returns new object as `void *`. */ +FIO_SFUNC void *iodine___json_on_false(void) { return (void *)Qfalse; } +/** Number was detected (long long). Returns new object as `void *`. */ +FIO_SFUNC void *iodine___json_on_number(int64_t i) { + return (void *)(LL2NUM(((long long)i))); +} +/** Float was detected (double).Returns new object as `void *`. */ +FIO_SFUNC void *iodine___json_on_float(double f) { return (void *)DBL2NUM(f); } +/** String was detected (int / float). update `pos` to point at ending */ +FIO_SFUNC void *iodine___json_on_string(const void *start, size_t len) { + VALUE str; + if (len < 4096) { + FIO_STR_INFO_TMP_VAR(buf, 4096); + fio_string_write_unescape(&buf, NULL, start, len); + str = rb_str_new(buf.buf, buf.len); + } else { + char *tmp = fio_bstr_write_unescape(NULL, start, len); + str = rb_str_new(tmp, fio_bstr_len(tmp)); + fio_bstr_free(tmp); + } + STORE.hold(str); + return (void *)str; +} +FIO_SFUNC void *iodine___json_on_string_simple(const void *start, size_t len) { + VALUE str = rb_str_new((const char *)start, len); + STORE.hold(str); + return (void *)str; +} +/** Dictionary was detected. Returns ctx to hash map or NULL on error. */ +FIO_SFUNC void *iodine___json_on_map(void *ctx, void *at) { + (void)ctx, (void)at; + VALUE map = rb_hash_new(); + STORE.hold(map); + return (void *)map; +} +/** Array was detected. Returns ctx to array or NULL on error. */ +FIO_SFUNC void *iodine___json_on_array(void *ctx, void *at) { + (void)ctx, (void)at; + VALUE ary = rb_ary_new(); + STORE.hold(ary); + return (void *)ary; +} +/** Array was detected. Returns non-zero on error. */ +FIO_SFUNC int iodine___json_map_push(void *ctx, void *key, void *val) { + rb_hash_aset((VALUE)ctx, (VALUE)key, (VALUE)val); + STORE.release((VALUE)val); + STORE.release((VALUE)key); + return 0; +} +/** Array was detected. Returns non-zero on error. */ +FIO_SFUNC int iodine___json_array_push(void *ctx, void *val) { + rb_ary_push((VALUE)ctx, (VALUE)val); + STORE.release((VALUE)val); + return 0; +} +/** Called for the `key` element in case of error or NULL value. */ +FIO_SFUNC void iodine___json_free_unused_object(void *ctx) { + STORE.release((VALUE)ctx); +} +/** the JSON parsing encountered an error - what to do with ctx? */ +FIO_SFUNC void *iodine___json_on_error(void *ctx) { + STORE.release((VALUE)ctx); + return (void *)Qnil; +} + +static fio_json_parser_callbacks_s IODINE_JSON_PARSER_CALLBACKS = { + .on_null = iodine___json_on_null, + .on_true = iodine___json_on_true, + .on_false = iodine___json_on_false, + .on_number = iodine___json_on_number, + .on_float = iodine___json_on_float, + .on_string = iodine___json_on_string, + .on_string_simple = iodine___json_on_string_simple, + .on_map = iodine___json_on_map, + .on_array = iodine___json_on_array, + .map_push = iodine___json_map_push, + .array_push = iodine___json_array_push, + .free_unused_object = iodine___json_free_unused_object, + .on_error = iodine___json_on_error, +}; + +/* ***************************************************************************** +API +***************************************************************************** */ + +/** Accepts a JSON String and returns a Ruby object. */ +static VALUE iodine_json_parse(VALUE self, VALUE rstr) { + rb_check_type(rstr, RUBY_T_STRING); + fio_json_result_s r = fio_json_parse(&IODINE_JSON_PARSER_CALLBACKS, + RSTRING_PTR(rstr), + RSTRING_LEN(rstr)); + STORE.release((VALUE)r.ctx); + return (VALUE)r.ctx; +} + +/** Accepts a Ruby object and returns a JSON String. */ +static VALUE iodine_json_stringify(VALUE self, VALUE object) { + VALUE r = Qnil; + char *str = fio_bstr_reserve(NULL, ((size_t)1 << 12) - 64); + str = iodine_json_stringify2bstr(str, object); + r = rb_str_new(str, (long)fio_bstr_len(str)); + fio_bstr_free(str); + return r; +} + +/** Initialize Iodine::JSON */ // clang-format off +/** +Iodine::JSON exposes the {Iodine::Connection#write} fallback behavior when called with non-String objects. + +The fallback behavior is similar to (though faster than) calling: + + client.write(Iodine::JSON.stringify(data)) + +If you want to work with JSON, consider using the `oj` gem. + +This API is mostly to test for Iodine JSON input/output errors and reflects what the C layer sees. + +## Performance + +Performance... could be better. + +Converting Ruby objects into a JSON String (Strinfigying) should be fast even though the String data is copied twice, once to C and then to Ruby. + +However, Converting a JSON object into Ruby Objects is currently slow and it is better to use the `oj` gem or even the Ruby builtin parser. + +The reason is simple - the implementation is designed to create C objects (C Hash Maps, C Arrays, etc'), not Ruby objects. When converting from a String to Ruby Objevts, the data is copied twice, once to C and then to Ruby. + +This especially effects parsing, where more objects are allocated, +whereas {Iodine::JSON.stringify} only (re)copies the String data which is a single continuous block of memory. + +That's why {Iodine::JSON.stringify} is significantly faster than the Ruby `object.to_json` approach, +yet slower than `JSON.parse(json_string)`. + + require 'oj' rescue nil + require 'benchmark/ips' + require 'json' + require 'iodine' + + def benchmark_json + # make a big data store with nothings + data_1000 = [] + 1000.times do + tmp = {f: rand() }; + tmp[:i] = (tmp[:f] * 1000000).to_i + tmp[:str] = tmp[:i].to_s + tmp[:sym] = tmp[:str].to_sym + tmp[:ary] = [] + tmp[:ary_empty] = [] + tmp[:hash_empty] = Hash.new + 100.times {|i| tmp[:ary] << i } + data_1000 << tmp + end + 3.times do + json_string = data_1000.to_json + puts "-----" + puts "Benchmark #{data_1000.length} item tree, and #{json_string.length} bytes of JSON" + # benchmark stringification + Benchmark.ips do |x| + x.report(" Ruby obj.to_json") do |times| + data_1000.to_json + end + x.report("Iodine::JSON.stringify") do |times| + Iodine::JSON.stringify(data_1000) + end + if(defined?(Oj)) + x.report(" Oj.dump") do |times| + Oj.dump(data_1000) + end + end + x.compare! + end ; nil + # benchmark parsing + Benchmark.ips do |x| + x.report(" Ruby JSON.parse") do |times| + JSON.parse(json_string) + end + x.report("Iodine::JSON.parse") do |times| + Iodine::JSON.parse(json_string) + end + if(defined?(Oj)) + x.report(" Oj.load") do |times| + Oj.load(json_string) + end + end + x.compare! + end + data_1000 = data_1000.slice(0, (data_1000.length / 10)) + nil + end + end + + benchmark_json + +*/ +static void Init_Iodine_JSON(void) { + VALUE m = rb_define_module_under(iodine_rb_IODINE, "JSON"); // clang-format on + rb_define_singleton_method(m, "parse", iodine_json_parse, 1); + rb_define_singleton_method(m, "stringify", iodine_json_stringify, 1); + rb_define_singleton_method(m, "dump", iodine_json_stringify, 1); +} +#endif /* H___IODINE_JSON___H */ diff --git a/ext/iodine/iodine_minimap.h b/ext/iodine/iodine_minimap.h new file mode 100644 index 00000000..b1fa55e5 --- /dev/null +++ b/ext/iodine/iodine_minimap.h @@ -0,0 +1,409 @@ +#ifndef H___IODINE_MINIMAP___H +#define H___IODINE_MINIMAP___H +#include "iodine.h" + +/* ***************************************************************************** +Mini Ruby Hash Map +***************************************************************************** */ + +static uint64_t iodinde_hmap_hash(VALUE k) { + size_t t = (size_t)rb_type(k); + switch (t) { + case RUBY_T_STRING: { /* may be an unfrozen String */ + fio_buf_info_s buf = (fio_buf_info_s)IODINE_RSTR_INFO(k); + return fio_risky_hash(buf.buf, buf.len, (uint64_t)&iodinde_hmap_hash); + } + case RUBY_T_SYMBOL: /* fall through */ + case RUBY_T_FIXNUM: /* fall through */ + case RUBY_T_TRUE: /* fall through */ + case RUBY_T_FALSE: /* fall through */ + case RUBY_T_NIL: /* these are always frozen */ + return fio_risky_num((uint64_t)k, (uint64_t)&iodinde_hmap_hash); + } + rb_raise(rb_eTypeError, "key MUST be either a String or a Symbol."); + return 0; +} + +static _Bool iodinde_hmap_object_cmp(VALUE a, VALUE b) { + if (((a ^ b) & RUBY_T_MASK)) + return 0; + if (!RB_TYPE_P(a, RUBY_T_STRING)) + return a == b; + fio_buf_info_s aa = IODINE_RSTR_INFO(a); + fio_buf_info_s bb = IODINE_RSTR_INFO(b); + return FIO_BUF_INFO_IS_EQ(aa, bb); +} + +#define FIO_MAP_NAME iodine_hmap +#define FIO_MAP_KEY VALUE +#define FIO_MAP_VALUE VALUE +#define FIO_MAP_HASH_FN iodinde_hmap_hash +#define FIO_MAP_KEY_CMP(a, b) iodinde_hmap_object_cmp(a, b) +#define FIO_MAP_KEY_COPY(dest, src) ((dest) = (src)) +#include FIO_INCLUDE_FILE + +typedef struct { + iodine_hmap_s map; + VALUE tmp; +} iodine_minimap_s; + +static int iodine_minimap_gc_mark_task(iodine_hmap_each_s *e) { + rb_gc_mark(e->key); + rb_gc_mark(e->value); + return 0; +} + +FIO_IFUNC void iodine_minimap_gc_mark(void *m_) { + iodine_minimap_s *m = (iodine_minimap_s *)m_; + if (m->tmp) + rb_gc_mark(m->tmp); + iodine_hmap_each(&m->map, iodine_minimap_gc_mark_task, NULL, 0); +} + +static VALUE iodine_minimap_store(iodine_minimap_s *m, VALUE key, VALUE value) { + if (value == Qnil) + goto on_delete; + if (RB_TYPE_P(key, T_STRING) && !rb_obj_frozen_p(key)) { + m->tmp = value; + key = rb_str_dup_frozen(key); + m->tmp = 0; + } + return iodine_hmap_set(&m->map, key, value, NULL); +on_delete: + iodine_hmap_remove(&m->map, key, &value); + return value; +} + +FIO_IFUNC VALUE iodine_minimap_rb_get(iodine_minimap_s *m, VALUE key) { + return iodine_hmap_get(&m->map, key); +} + +typedef struct iodine_hmap_rb_each_task_s { + size_t r; + iodine_hmap_s *m; +} iodine_hmap_rb_each_task_s; + +static int iodine_minimap_c_each_task(iodine_hmap_each_s *e) { + iodine_hmap_rb_each_task_s *info = (iodine_hmap_rb_each_task_s *)e->udata; + VALUE args[] = {e->key, e->value}; + info->r++; + rb_yield_values2(2, args); + return 0; +} +static VALUE iodine_minimap_rb_each_task(VALUE info_) { + iodine_hmap_rb_each_task_s *info = (iodine_hmap_rb_each_task_s *)info_; + iodine_hmap_each(info->m, iodine_minimap_c_each_task, info, 0); + return Qnil; +} + +static VALUE iodine_minimap_rb_each(iodine_minimap_s *m) { + rb_need_block(); + if (!iodine_hmap_count(&m->map)) + return ULL2NUM(0); + iodine_hmap_rb_each_task_s i = {.m = &m->map}; + VALUE exc; + int e = 0; + rb_protect(iodine_minimap_rb_each_task, (VALUE)&i, &e); + if (e) + goto error; + // iodine_hmap_each(m, iodine_hmap_c_each_task, &i, 0); + return ULL2NUM(i.r); +error: + exc = rb_errinfo(); + if (!rb_obj_is_instance_of(exc, rb_eLocalJumpError)) + iodine_handle_exception(NULL); + rb_set_errinfo(Qnil); + return ULL2NUM(i.r); +} + +/* ***************************************************************************** +Ruby Object. +***************************************************************************** */ + +static void iodine_minimap_free(void *p) { + iodine_minimap_s *m = (iodine_minimap_s *)p; + iodine_hmap_destroy(&m->map); + ruby_xfree(m); + FIO_LEAK_COUNTER_ON_FREE(iodine_minimap); +} + +static size_t iodine_minimap_size(const void *p) { + iodine_minimap_s *m = (iodine_minimap_s *)p; + return sizeof(*m) + (iodine_hmap_capa(&m->map) * sizeof(m->map.map[0])); +} + +static const rb_data_type_t IODINE_MINIMAP_DATA_TYPE = { + .wrap_struct_name = "IodineMiniMap", + .function = + { + .dmark = iodine_minimap_gc_mark, + .dfree = iodine_minimap_free, + .dsize = iodine_minimap_size, + }, + .data = NULL, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static iodine_minimap_s *iodine_minimap_ptr(VALUE self) { + iodine_minimap_s *m; + TypedData_Get_Struct(self, iodine_minimap_s, &IODINE_MINIMAP_DATA_TYPE, m); + return m; +} + +static VALUE iodine_minimap_alloc(VALUE klass) { + + FIO_LEAK_COUNTER_ON_ALLOC(iodine_minimap); + iodine_minimap_s *m; + VALUE self = TypedData_Make_Struct(klass, + iodine_minimap_s, + &IODINE_MINIMAP_DATA_TYPE, + m); + *m = (iodine_minimap_s)FIO_MAP_INIT; + return self; + // return TypedData_Wrap_Struct(klass, &IODINE_MINIMAP_DATA_TYPE, m); +} + +/* ***************************************************************************** +API +***************************************************************************** */ + +static VALUE iodine_minimap_get(VALUE o, VALUE key) { + VALUE r = iodine_minimap_rb_get(iodine_minimap_ptr(o), key); + if (!r) + r = Qnil; + return r; +} +static VALUE iodine_minimap_set(VALUE o, VALUE key, VALUE value) { + return iodine_minimap_store(iodine_minimap_ptr(o), key, value); +} +static VALUE iodine_minimap_each(VALUE o) { + return iodine_minimap_rb_each(iodine_minimap_ptr(o)); +} +static VALUE iodine_minimap_count(VALUE o) { + return ULL2NUM( + (unsigned long long)iodine_hmap_count(&iodine_minimap_ptr(o)->map)); +} +static VALUE iodine_minimap_capa(VALUE o) { + return ULL2NUM( + (unsigned long long)iodine_hmap_capa(&iodine_minimap_ptr(o)->map)); +} +static VALUE iodine_minimap_reserve(VALUE o, VALUE s_) { + rb_check_type(s_, T_FIXNUM); + size_t s = (size_t)NUM2ULL(s_); + if (s > 0x0FFFFFFFULL) + rb_raise( + rb_eRangeError, + "cannot reserve negative values or values higher than 268,435,455."); + iodine_hmap_s *m = &iodine_minimap_ptr(o)->map; + iodine_hmap_reserve(m, s); + return o; +} + +/* ***************************************************************************** +Benchmark C world performance +***************************************************************************** */ + +static int each_task(void *_i) { + FIO_COMPILER_GUARD; + return 0; + (void)_i; +} + +#define FIO_MAP_NAME iodine_mini_map +#define FIO_MAP_KEY size_t +#define FIO_MAP_KEY_CMP(a, b) ((a) == (b)) +#define FIO_MAP_VALUE size_t +#define FIO_MAP_HASH_FN(k) (fio_risky_num(k, 0)) +#include FIO_INCLUDE_FILE + +typedef VALUE ruby_hash_s; +FIO_IFUNC void ruby_hash_destroy(VALUE *m) { + if (!*m) + return; + STORE.release(*m); + *m = 0; +} +FIO_IFUNC size_t ruby_hash_reserve(VALUE *m, size_t capa) { + *m = rb_hash_new(); + STORE.hold(*m); + (void)capa; + return capa; +} + +FIO_IFUNC size_t ruby_hash_count(VALUE *m) { return RHASH_SIZE(*m); } +FIO_IFUNC size_t ruby_hash_capa(VALUE *m) { + return rb_st_memsize(RHASH_TBL(*m)) / (sizeof(VALUE) * 3); +} +FIO_IFUNC VALUE ruby_hash_set(VALUE *m, size_t k, size_t v, size_t *old) { + rb_hash_aset(*m, k, v); + return v; + (void)old; +} +FIO_IFUNC size_t ruby_hash_get(VALUE *m, size_t o) { + VALUE tmp = rb_hash_aref(*m, o); + if (tmp == Qnil) + return 0; + return tmp; +} + +FIO_SFUNC int ruby_hash_each___task(VALUE m, VALUE k, VALUE v) { + FIO_COMPILER_GUARD; + return ST_CONTINUE; + (void)m, (void)k, (void)v; +} +typedef struct { + int i; +} ruby_hash_each_s; +FIO_IFUNC size_t ruby_hash_each(VALUE *m, + int (*each_task)(ruby_hash_each_s *), + void *udata, + ssize_t start_at) { + rb_hash_foreach(*m, ruby_hash_each___task, (VALUE)udata); + return RHASH_SIZE((*m)); + (void)start_at, (void)each_task; +} + +static VALUE iodine_minimap_benchmark_c(VALUE klass, VALUE object_count) { + (void)klass; + if (object_count == Qnil) + object_count = ULONG2NUM(30UL); + if (NUM2ULL(object_count) > 10000000UL) + rb_raise(rb_eRangeError, "object count is too high."); + const size_t ORDERED_OBJECTS = 0U; + const size_t RANDOM_OBJECTS = NUM2ULL(object_count); + const size_t OBJECTS = ORDERED_OBJECTS + RANDOM_OBJECTS; + const size_t CYCLES = 10000000U / OBJECTS; + const size_t TESTS = 3U; + const size_t FIND_CYCLES = CYCLES; + const size_t EACH_CYCLES = CYCLES; + const size_t MISSING_OBJECTS = OBJECTS; + size_t *numbers = malloc(sizeof(*numbers) * (OBJECTS + MISSING_OBJECTS)); + { /* make sure all objects are unique */ + iodine_mini_map_s existing = {0}; + for (size_t i = 1; i < ORDERED_OBJECTS + 1; ++i) { + numbers[i - 1] = i; + iodine_mini_map_set(&existing, i - i, i, NULL); + } + for (size_t i = ORDERED_OBJECTS; i < (OBJECTS + MISSING_OBJECTS); ++i) { + do { + numbers[i] = fio_rand64(); + numbers[i] >>= 32; + numbers[i] = ULL2NUM(numbers[i]); + } while (iodine_mini_map_get_ptr(&existing, numbers[i])); + numbers[i] += !numbers[i]; + iodine_mini_map_set(&existing, numbers[i], numbers[i], NULL); + } + iodine_mini_map_destroy(&existing); + } +#define PERFORM_TEST(klass) \ + do { \ + klass##_s m = {0}; \ + uint64_t insert, overwrite, find, loop, find_missing, start; \ + start = fio_time_micro(); \ + start = fio_time_micro(); \ + for (size_t j = 0; j < CYCLES; ++j) { \ + klass##_destroy(&m); \ + if (1) \ + klass##_reserve(&m, 8 | ((OBJECTS) >> 31)); \ + for (size_t i = 0; i < OBJECTS; ++i) { \ + klass##_set(&m, numbers[i], numbers[i], NULL); \ + } \ + } \ + insert = fio_time_micro() - start; \ + if (klass##_count(&m) != OBJECTS) \ + FIO_LOG_ERROR("map counter error (%zu != %zu)!", \ + klass##_count(&m), \ + OBJECTS); \ + if (klass##_capa(&m) < OBJECTS) \ + FIO_LOG_ERROR("map capacity error (%zu < %zu)!", \ + klass##_capa(&m), \ + OBJECTS); \ + start = fio_time_micro(); \ + for (size_t j = 0; j < CYCLES; ++j) { \ + for (size_t i = 0; i < OBJECTS; ++i) \ + klass##_set(&m, numbers[i], numbers[i], NULL); \ + } \ + overwrite = fio_time_micro() - start; \ + if (klass##_count(&m) != OBJECTS) \ + FIO_LOG_ERROR("map counter error (%zu != %zu)!", \ + klass##_count(&m), \ + OBJECTS); \ + if (klass##_capa(&m) < OBJECTS) \ + FIO_LOG_ERROR("map capacity error (%zu < %zu)!", \ + klass##_capa(&m), \ + OBJECTS); \ + start = fio_time_micro(); \ + for (size_t sc = 0; sc < FIND_CYCLES; ++sc) \ + for (size_t i = 0; i < OBJECTS; ++i) { \ + if (klass##_get(&m, numbers[i]) != numbers[i]) \ + FIO_LOG_ERROR(FIO_MACRO2STR(klass) "_get error ([%zu] %zu != %zu)!", \ + i, \ + numbers[i], \ + klass##_get(&m, i)); \ + } \ + find = fio_time_micro() - start; \ + start = fio_time_micro(); \ + for (size_t sc = 0; sc < FIND_CYCLES; ++sc) \ + for (size_t i = OBJECTS; i < (OBJECTS + MISSING_OBJECTS); ++i) { \ + if (klass##_get(&m, numbers[i])) \ + FIO_LOG_ERROR( \ + FIO_MACRO2STR(klass) "_get error" \ + "([%zu] %zu shouldn't exist but == %zu)!", \ + i, \ + numbers[i], \ + klass##_get(&m, numbers[i])); \ + } \ + find_missing = fio_time_micro() - start; \ + start = fio_time_micro(); \ + for (size_t i = 0; i < EACH_CYCLES; ++i) \ + klass##_each(&m, (int (*)(klass##_each_s *))each_task, NULL, 0); \ + loop = fio_time_micro() - start; \ + FIO_LOG_INFO(" %-16s" \ + "\tcapa: %zu/%-6zu" \ + "\tinsert: %-6zu" \ + "\toverwrite: %-6zu" \ + "\tfind: %-6zu" \ + "\tfind missing: %-6zu" \ + "\tloop: %-6zu", \ + FIO_MACRO2STR(klass), \ + (size_t)klass##_count(&m), \ + (size_t)klass##_capa(&m), \ + insert, \ + overwrite, \ + find, \ + find_missing, \ + loop); \ + klass##_destroy(&m); \ + } while (0); + for (size_t tst = 0; tst < TESTS; ++tst) { + PERFORM_TEST(iodine_mini_map) + PERFORM_TEST(ruby_hash) + } + free(numbers); + return Qnil; +} + +/* ***************************************************************************** +Initialize + +Benchmark with: + +require 'iodine/benchmark' +Iodine::Benchmark.minimap(100) + +m = Iodine::Base::MiniMap.new +10.times {|i| m[i] = i } +m.each {|k,v| puts "#{k.to_s} => #{v.to_s}"} +***************************************************************************** */ +static void Init_Iodine_MiniMap(void) { + VALUE m = rb_define_class_under(iodine_rb_IODINE_BASE, "MiniMap", rb_cObject); + rb_define_alloc_func(m, iodine_minimap_alloc); + rb_define_method(m, "[]", iodine_minimap_get, 1); + rb_define_method(m, "[]=", iodine_minimap_set, 2); + rb_define_method(m, "count", iodine_minimap_count, 0); + rb_define_method(m, "capa", iodine_minimap_capa, 0); + rb_define_method(m, "each", iodine_minimap_each, 0); + rb_define_method(m, "reserve", iodine_minimap_reserve, 1); + rb_define_singleton_method(m, "cbench", iodine_minimap_benchmark_c, 1); +} +#endif /* H___IODINE_MINIMAP___H */ diff --git a/ext/iodine/iodine_mustache.c b/ext/iodine/iodine_mustache.c deleted file mode 100644 index 9df9470e..00000000 --- a/ext/iodine/iodine_mustache.c +++ /dev/null @@ -1,567 +0,0 @@ -#include -#define INCLUDE_MUSTACHE_IMPLEMENTATION 1 -#include "mustache_parser.h" - -#include "iodine.h" - -#define FIO_INCLUDE_STR -#include - -static ID call_func_id; -static VALUE filename_id; -static VALUE data_id; -static VALUE template_id; -/* ***************************************************************************** -C <=> Ruby Data allocation -***************************************************************************** */ - -static size_t iodine_mustache_data_size(const void *c_) { - return sizeof(mustache_s *) + - (((mustache_s **)c_)[0] - ? (((mustache_s **)c_)[0]->u.read_only.data_length + - (((mustache_s **)c_)[0]->u.read_only.intruction_count * - sizeof(struct mustache__instruction_s))) - : 0); - (void)c_; -} - -static void iodine_mustache_data_free(void *c_) { - mustache_free(((mustache_s **)c_)[0]); - FIO_LOG_DEBUG("deallocated mustache data at: %p", ((void **)c_)[0]); - free((void *)c_); - FIO_LOG_DEBUG("deallocated mustache pointer at: %p", c_); - (void)c_; -} - -static const rb_data_type_t iodine_mustache_data_type = { - .wrap_struct_name = "IodineMustacheData", - .function = - { - .dmark = NULL, - .dfree = iodine_mustache_data_free, - .dsize = iodine_mustache_data_size, - }, - .data = NULL, - // .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -/* Iodine::PubSub::Engine.allocate */ -static VALUE iodine_mustache_data_alloc_c(VALUE self) { - void *m = malloc(sizeof(mustache_s *)); - ((mustache_s **)m)[0] = NULL; - FIO_LOG_DEBUG("allocated mustache pointer at: %p", m); - return TypedData_Wrap_Struct(self, &iodine_mustache_data_type, m); -} - -/* ***************************************************************************** -Parser Callbacks -***************************************************************************** */ - -static inline VALUE fiobj_mustache_find_obj_absolute(VALUE udata, - const char *name, - uint32_t name_len) { - VALUE tmp; - if (!RB_TYPE_P(udata, T_HASH)) { - if (name_len == 1 && name[0] == '.') - return udata; - /* search by method */ - ID name_id = rb_intern2(name, name_len); - if (rb_respond_to(udata, name_id)) { - return IodineCaller.call(udata, name_id); - } - return Qnil; - } - /* search by Symbol */ - ID name_id = rb_intern2(name, name_len); - VALUE key = ID2SYM(name_id); - tmp = rb_hash_lookup2(udata, key, Qundef); - if (tmp != Qundef) - return tmp; - /* search by String */ - key = rb_sym2str(key); - tmp = rb_hash_lookup2(udata, key, Qundef); - rb_str_free(key); - if (tmp != Qundef) - return tmp; - /* search by method */ - tmp = Qnil; - if (rb_respond_to(udata, name_id)) { - tmp = IodineCaller.call(udata, name_id); - } - - return tmp; -} - -static inline VALUE fiobj_mustache_find_obj_tree(mustache_section_s *section, - const char *name, - uint32_t name_len) { - do { - VALUE tmp = fiobj_mustache_find_obj_absolute((VALUE)section->udata2, name, - name_len); - if (tmp != Qnil) { - return tmp; - } - } while ((section = mustache_section_parent(section))); - return Qnil; -} - -static inline VALUE fiobj_mustache_find_obj(mustache_section_s *section, - const char *name, - uint32_t name_len) { - VALUE tmp = fiobj_mustache_find_obj_tree(section, name, name_len); - if (tmp != Qnil) - return tmp; - /* interpolate sections... */ - uint32_t dot = 0; - while (dot < name_len && name[dot] != '.') - ++dot; - if (dot == name_len) - return Qnil; - tmp = fiobj_mustache_find_obj_tree(section, name, dot); - if (!tmp) { - return Qnil; - } - ++dot; - for (;;) { - VALUE obj = - fiobj_mustache_find_obj_absolute(tmp, name + dot, name_len - dot); - if (obj != Qnil) - return obj; - name += dot; - name_len -= dot; - dot = 0; - while (dot < name_len && name[dot] != '.') - ++dot; - if (dot == name_len) { - return Qnil; - } - tmp = fiobj_mustache_find_obj_absolute(tmp, name, dot); - if (tmp == Qnil) - return Qnil; - ++dot; - } -} -/** - * Called when an argument name was detected in the current section. - * - * A conforming implementation will search for the named argument both in the - * existing section and all of it's parents (walking backwards towards the root) - * until a value is detected. - * - * A missing value should be treated the same as an empty string. - * - * A conforming implementation will output the named argument's value (either - * HTML escaped or not, depending on the `escape` flag) as a string. - */ -static int mustache_on_arg(mustache_section_s *section, const char *name, - uint32_t name_len, unsigned char escape) { - VALUE o = fiobj_mustache_find_obj(section, name, name_len); - switch (o) { - case Qnil: - case Qfalse: - return 0; - case Qtrue: - fio_str_write(section->udata1, "true", 4); - break; - } - if (!RB_TYPE_P(o, T_STRING)) { - if (rb_respond_to(o, call_func_id)) - o = IodineCaller.call(o, call_func_id); - if (!RB_TYPE_P(o, T_STRING)) - o = IodineCaller.call(o, iodine_to_s_id); - } - if (!RB_TYPE_P(o, T_STRING) || !RSTRING_LEN(o)) - return 0; - return mustache_write_text(section, RSTRING_PTR(o), RSTRING_LEN(o), escape); -} - -/** - * Called when simple template text (string) is detected. - * - * A conforming implementation will output data as a string (no escaping). - */ -static int mustache_on_text(mustache_section_s *section, const char *data, - uint32_t data_len) { - fio_str_write(section->udata1, data, data_len); - return 0; -} - -/** - * Called for nested sections, must return the number of objects in the new - * subsection (depending on the argument's name). - * - * Arrays should return the number of objects in the array. - * - * `true` values should return 1. - * - * `false` values should return 0. - * - * A return value of -1 will stop processing with an error. - * - * Please note, this will handle both normal and inverted sections. - */ -static int32_t mustache_on_section_test(mustache_section_s *section, - const char *name, uint32_t name_len, - uint8_t callable) { - VALUE o = fiobj_mustache_find_obj(section, name, name_len); - if (o == Qnil || o == Qfalse) { - return 0; - } - if (RB_TYPE_P(o, T_ARRAY)) { - return RARRAY_LEN(o); - } - if (callable && rb_respond_to(o, call_func_id)) { - size_t len; - const char *txt = mustache_section_text(section, &len); - VALUE str = Qnil; - if (txt && len) { - str = rb_str_new(txt, len); - } - o = IodineCaller.call2(o, call_func_id, 1, &str); - if (!RB_TYPE_P(o, T_STRING)) - o = rb_funcall2(o, iodine_to_s_id, 0, NULL); - if (RB_TYPE_P(o, T_STRING) && RSTRING_LEN(o)) - mustache_write_text(section, RSTRING_PTR(o), RSTRING_LEN(o), 0); - return 0; - } - return 1; -} - -/** - * Called when entering a nested section. - * - * `index` is a zero based index indicating the number of repetitions that - * occurred so far (same as the array index for arrays). - * - * A return value of -1 will stop processing with an error. - * - * Note: this is a good time to update the subsection's `udata` with the value - * of the array index. The `udata` will always contain the value or the parent's - * `udata`. - */ -static int mustache_on_section_start(mustache_section_s *section, - char const *name, uint32_t name_len, - uint32_t index) { - VALUE o = fiobj_mustache_find_obj(section, name, name_len); - if (RB_TYPE_P(o, T_ARRAY)) - section->udata2 = (void *)rb_ary_entry(o, index); - else if (RB_TYPE_P(o, T_HASH)) - section->udata2 = (void *)o; - return 0; -} - -/** - * Called for cleanup in case of error. - */ -static void mustache_on_formatting_error(void *udata1, void *udata2) { - (void)udata1; - (void)udata2; -} - -/* ***************************************************************************** -Loading the template -***************************************************************************** */ - -/** -Loads the mustache template found in `:filename`. If `:template` is provided it -will be used instead of reading the file's content. - - Iodine::Mustache.new(filename, template = nil) - -When template data is provided, filename (if any) will only be used for partial -template path resolution and the template data will be used for the template's -content. This allows, for example, for front matter to be extracted before -parsing the template. - -Once a template was loaded, it could be rendered using {render}. - -Accepts named arguments as well: - - Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}") - -*/ -static VALUE iodine_mustache_new(int argc, VALUE *argv, VALUE self) { - VALUE filename = Qnil, template = Qnil; - if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) { - /* named arguments */ - filename = rb_hash_aref(argv[0], filename_id); - template = rb_hash_aref(argv[0], template_id); - } else { - /* regular arguments */ - if (argc == 0 || argc > 2) - rb_raise(rb_eArgError, "expecting 1..2 arguments or named arguments."); - filename = argv[0]; - if (argc > 1) { - template = argv[1]; - } - } - if (filename == Qnil && template == Qnil) - rb_raise(rb_eArgError, "need either template contents or file name."); - - if (template != Qnil) - Check_Type(template, T_STRING); - if (filename != Qnil) - Check_Type(filename, T_STRING); - - mustache_s **m = NULL; - TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m); - if (!m) { - rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error."); - } - - mustache_error_en err; - *m = mustache_load(.filename = - (filename == Qnil ? NULL : RSTRING_PTR(filename)), - .filename_len = - (filename == Qnil ? 0 : RSTRING_LEN(filename)), - .data = (template == Qnil ? NULL : RSTRING_PTR(template)), - .data_len = (template == Qnil ? 0 : RSTRING_LEN(template)), - .err = &err); - if (!*m) - goto error; - - FIO_LOG_DEBUG("allocated / loaded mustache data at: %p", (void *)*m); - - return self; -error: - switch (err) { - case MUSTACHE_OK: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template ok, unknown error."); - break; - case MUSTACHE_ERR_TOO_DEEP: - rb_raise(rb_eRuntimeError, "Iodine::Mustache element nesting too deep."); - break; - case MUSTACHE_ERR_CLOSURE_MISMATCH: - rb_raise(rb_eRuntimeError, - "Iodine::Mustache template error, closure mismatch."); - break; - case MUSTACHE_ERR_FILE_NOT_FOUND: - rb_raise(rb_eLoadError, "Iodine::Mustache template not found."); - break; - case MUSTACHE_ERR_FILE_TOO_BIG: - rb_raise(rb_eLoadError, "Iodine::Mustache template too big."); - break; - case MUSTACHE_ERR_FILE_NAME_TOO_LONG: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template name too long."); - break; - case MUSTACHE_ERR_EMPTY_TEMPLATE: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template is empty."); - break; - case MUSTACHE_ERR_UNKNOWN: - rb_raise(rb_eRuntimeError, "Iodine::Mustache unknown error."); - break; - case MUSTACHE_ERR_USER_ERROR: - rb_raise(rb_eRuntimeError, "Iodine::Mustache internal error."); - break; - case MUSTACHE_ERR_FILE_NAME_TOO_SHORT: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template file name too long."); - - break; - case MUSTACHE_ERR_DELIMITER_TOO_LONG: - rb_raise(rb_eRuntimeError, "Iodine::Mustache new delimiter is too long."); - - break; - case MUSTACHE_ERR_NAME_TOO_LONG: - rb_raise(rb_eRuntimeError, - "Iodine::Mustache section name in template is too long."); - default: - break; - } - return self; -} - -/* ***************************************************************************** -Rendering -***************************************************************************** */ - -/** -Renders the mustache template using the data provided in the `data` argument. - -Returns a String with the rendered template. - -Raises an exception on error. - -NOTE: - -As one might notice, no binding is provided. Instead, a `data` Hash is assumed. -Iodine will search the Hash for any data while protecting against code -execution. -*/ -static VALUE iodine_mustache_render(VALUE self, VALUE data) { - fio_str_s str = FIO_STR_INIT; - mustache_s **m = NULL; - TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m); - if (!m) { - rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error."); - } - if (mustache_build(*m, .udata1 = &str, .udata2 = (void *)data)) - goto error; - fio_str_info_s i = fio_str_info(&str); - VALUE ret = rb_str_new(i.data, i.len); - fio_str_free(&str); - return ret; - -error: - fio_str_free(&str); - rb_raise(rb_eRuntimeError, "Couldn't build template frome data."); -} - -/** -Renders the mustache template found in `filename`, using the data provided in -the `data` argument. If `template` is provided it will be used instead of -reading the file's content. - - Iodine::Mustache.render(filename, data, template = nil) - -Returns a String with the rendered template. - -Raises an exception on error. - - template = "

      {{title}}

      " - filename = "templates/index" - data = {title: "Home"} - result = Iodine::Mustache.render(filename, data) - - # filename will be used to resolve the path to any partials: - result = Iodine::Mustache.render(filename, data, template) - - # OR, if we don't need partial template path resolution - result = Iodine::Mustache.render(template: template, data: data) - -NOTE 1: - -This function doesn't cache the template data. - -The more complext the template the higher the cost of the template parsing -stage. - -Consider creating a persistent template object using a new object and using the -instance {#render} method. - -NOTE 2: - -As one might notice, no binding is provided. Instead, a `data` Hash is assumed. -Iodine will search the Hash for any data while protecting against code -execution. -*/ -static VALUE iodine_mustache_render_klass(int argc, VALUE *argv, VALUE self) { - VALUE filename = Qnil, data = Qnil, template = Qnil; - if (argc == 1) { - /* named arguments */ - Check_Type(argv[0], T_HASH); - filename = rb_hash_aref(argv[0], filename_id); - data = rb_hash_aref(argv[0], data_id); - template = rb_hash_aref(argv[0], template_id); - } else { - /* regular arguments */ - if (argc < 2 || argc > 3) - rb_raise(rb_eArgError, "expecting 2..3 arguments or named arguments."); - filename = argv[0]; - data = argv[1]; - if (argc > 2) { - template = argv[2]; - } - } - if (filename == Qnil && template == Qnil) - rb_raise(rb_eArgError, "need either template contents or file name."); - - if (template != Qnil) - Check_Type(template, T_STRING); - if (filename != Qnil) - Check_Type(filename, T_STRING); - - fio_str_s str = FIO_STR_INIT; - - mustache_s *m = NULL; - mustache_error_en err; - m = mustache_load(.filename = - (filename == Qnil ? NULL : RSTRING_PTR(filename)), - .filename_len = - (filename == Qnil ? 0 : RSTRING_LEN(filename)), - .data = (template == Qnil ? NULL : RSTRING_PTR(template)), - .data_len = (template == Qnil ? 0 : RSTRING_LEN(template)), - .err = &err); - if (!m) - goto error; - int e = mustache_build(m, .udata1 = &str, .udata2 = (void *)data); - mustache_free(m); - if (e) - goto render_error; - fio_str_info_s i = fio_str_info(&str); - VALUE ret = rb_str_new(i.data, i.len); - fio_str_free(&str); - return ret; - -error: - switch (err) { - case MUSTACHE_OK: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template ok, unknown error."); - break; - case MUSTACHE_ERR_TOO_DEEP: - rb_raise(rb_eRuntimeError, "Iodine::Mustache element nesting too deep."); - break; - case MUSTACHE_ERR_CLOSURE_MISMATCH: - rb_raise(rb_eRuntimeError, - "Iodine::Mustache template error, closure mismatch."); - break; - case MUSTACHE_ERR_FILE_NOT_FOUND: - rb_raise(rb_eLoadError, "Iodine::Mustache template not found."); - break; - case MUSTACHE_ERR_FILE_TOO_BIG: - rb_raise(rb_eLoadError, "Iodine::Mustache template too big."); - break; - case MUSTACHE_ERR_FILE_NAME_TOO_LONG: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template name too long."); - break; - case MUSTACHE_ERR_EMPTY_TEMPLATE: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template is empty."); - break; - case MUSTACHE_ERR_UNKNOWN: - rb_raise(rb_eRuntimeError, "Iodine::Mustache unknown error."); - break; - case MUSTACHE_ERR_USER_ERROR: - rb_raise(rb_eRuntimeError, - "Iodine::Mustache internal error or unexpected data structure."); - break; - case MUSTACHE_ERR_FILE_NAME_TOO_SHORT: - rb_raise(rb_eRuntimeError, "Iodine::Mustache template file name too long."); - - break; - case MUSTACHE_ERR_DELIMITER_TOO_LONG: - rb_raise(rb_eRuntimeError, "Iodine::Mustache new delimiter is too long."); - - break; - case MUSTACHE_ERR_NAME_TOO_LONG: - rb_raise(rb_eRuntimeError, - "Iodine::Mustache section name in template is too long."); - - break; - default: - break; - } - return Qnil; - -render_error: - fio_str_free(&str); - rb_raise(rb_eRuntimeError, "Couldn't build template frome data."); -} - -/* ***************************************************************************** -Initialize Iodine::Mustache -***************************************************************************** */ - -void iodine_init_mustache(void) { - call_func_id = rb_intern2("call", 4); - filename_id = rb_id2sym(rb_intern2("filename", 8)); - data_id = rb_id2sym(rb_intern2("data", 4)); - template_id = rb_id2sym(rb_intern2("template", 8)); - rb_global_variable(&filename_id); - rb_global_variable(&data_id); - rb_global_variable(&template_id); - VALUE tmp = rb_define_class_under(IodineModule, "Mustache", rb_cObject); - rb_define_alloc_func(tmp, iodine_mustache_data_alloc_c); - rb_define_method(tmp, "initialize", iodine_mustache_new, -1); - rb_define_method(tmp, "render", iodine_mustache_render, 1); - rb_define_singleton_method(tmp, "render", iodine_mustache_render_klass, -1); - // rb_define_module_function(tmp, "render", iodine_mustache_render_klass, 2); -} diff --git a/ext/iodine/iodine_mustache.h b/ext/iodine/iodine_mustache.h index fa917f9f..9212f45e 100644 --- a/ext/iodine/iodine_mustache.h +++ b/ext/iodine/iodine_mustache.h @@ -1,6 +1,415 @@ -#ifndef H_IODINE_MUSTACHE_H -#define H_IODINE_MUSTACHE_H +#ifndef H___IODINE_MUSTA___H +#define H___IODINE_MUSTA___H +#include "iodine.h" -void iodine_init_mustache(void); +/* ***************************************************************************** +Mustache Callbacks +***************************************************************************** */ -#endif +static void *mus_get_var(void *ctx, fio_buf_info_s name) { + VALUE r = Qnil; + VALUE c = (VALUE)ctx; + ID to_hash; + if (TYPE(c) != RUBY_T_HASH) + goto not_a_hash; + r = rb_hash_aref(c, rb_id2sym(rb_intern2(name.buf, name.len))); + if (FIO_LIKELY(r != Qnil)) + return (void *)r; + r = rb_hash_aref(c, rb_str_new_static(name.buf, name.len)); + if (r == Qnil) + r = (VALUE)NULL; + STORE.hold(r); + return (void *)r; +not_a_hash: + to_hash = rb_intern2("to_hash", 7); + if (c && TYPE(c) == RUBY_T_OBJECT && rb_respond_to(c, to_hash)) + return mus_get_var((void *)rb_funcallv(c, to_hash, 0, &c), name); + return NULL; +} +static size_t mus_get_array_len(void *ctx) { + VALUE c = (VALUE)ctx; + if (TYPE(c) != RUBY_T_ARRAY) + return 0; + return RARRAY_LEN(c); +} + +static void *mus_get_var_index(void *ctx, size_t index) { + VALUE c = (VALUE)ctx; + if (TYPE(c) != RUBY_T_ARRAY) + return NULL; + c = rb_ary_entry(c, index); + STORE.hold(c); + return (void *)c; +} +static fio_buf_info_s mus_var2str(void *var) { + if ((VALUE)var == Qnil || !var) + return FIO_BUF_INFO2(NULL, 0); + VALUE v = (VALUE)var; + switch (TYPE(v)) { + case RUBY_T_TRUE: return FIO_BUF_INFO2((char *)"true", 4); + case RUBY_T_FALSE: return FIO_BUF_INFO2((char *)"false", 5); + case RUBY_T_SYMBOL: v = rb_sym2str(v); + case RUBY_T_STRING: + return FIO_BUF_INFO2(RSTRING_PTR(v), (size_t)RSTRING_LEN(v)); + case RUBY_T_FIXNUM: /* fall through */ + case RUBY_T_BIGNUM: /* fall through */ + case RUBY_T_FLOAT: + v = rb_funcallv(v, IODINE_TO_S_ID, 0, &v); + return FIO_BUF_INFO2(RSTRING_PTR(v), (size_t)RSTRING_LEN(v)); + case RUBY_T_ARRAY: /* fall through */ + case RUBY_T_HASH: /* fall through */ + default: + if (rb_respond_to(v, rb_intern("call"))) { + return mus_var2str((void *)rb_proc_call(v, rb_ary_new())); + } + return FIO_BUF_INFO2(NULL, 0); + } +} +static int mus_var_is_truthful(void *ctx) { + return ctx && ((VALUE)ctx) != Qnil && ((VALUE)ctx) != Qfalse && + (TYPE(((VALUE)ctx)) != RUBY_T_ARRAY || rb_array_len(((VALUE)ctx))); +} + +static void mus_release_var(void *ctx) { STORE.release((VALUE)ctx); } + +static int mus_is_lambda(void **udata, void *ctx, fio_buf_info_s raw) { + VALUE c = (VALUE)ctx; + if (!rb_respond_to(c, rb_intern("call"))) + return 0; + VALUE tmp = rb_ary_new(); + if (raw.len) + rb_ary_push(tmp, rb_str_new(raw.buf, raw.len)); + tmp = rb_proc_call(c, tmp); + fio_buf_info_s txt = mus_var2str((void *)tmp); + if (txt.len) + *udata = (void *)fio_bstr_write((char *)(*udata), txt.buf, txt.len); + return 1; +} + +static void mus_on_yaml_front_matter(fio_buf_info_s yaml_front_matter, + void *udata) { + VALUE block = (VALUE)udata; + VALUE yaml_data = rb_ary_new(); + rb_ary_push(yaml_data, + rb_str_new(yaml_front_matter.buf, yaml_front_matter.len)); + rb_proc_call(block, yaml_data); +} + +/* ***************************************************************************** +Ruby Object. +***************************************************************************** */ + +static size_t fio_mustache_wrapper_size(const void *ptr_) { + return fio_bstr_len(*(const char **)ptr_) + 8; +} + +static void fio_mustache_wrapper_free(void *p) { + fio_mustache_s **pm = (fio_mustache_s **)p; + fio_mustache_free(*pm); + FIO_LEAK_COUNTER_ON_FREE(iodine_mustache); + ruby_xfree(pm); +} + +static const rb_data_type_t IODINE_MUSTACHE_DATA_TYPE = { + .wrap_struct_name = "IodineMustache", + .function = + { + .dfree = fio_mustache_wrapper_free, + .dsize = fio_mustache_wrapper_size, + }, + .data = NULL, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE fio_mustache_wrapper_alloc(VALUE klass) { + fio_mustache_s **mp; + VALUE o = TypedData_Make_Struct(klass, + fio_mustache_s *, + &IODINE_MUSTACHE_DATA_TYPE, + mp); + *mp = NULL; + FIO_LEAK_COUNTER_ON_ALLOC(iodine_mustache); + return o; +} + +static fio_mustache_s **fio_mustache_wrapper_get(VALUE self) { + fio_mustache_s **mp; + return TypedData_Get_Struct(self, + fio_mustache_s *, + &IODINE_MUSTACHE_DATA_TYPE, + mp); +} + +/* ***************************************************************************** +API +***************************************************************************** */ +// clang-format off +/** + * Loads a template file and compiles it into a flattened instruction tree. + * + * Iodine::Mustache.new(file = nil, data = nil, on_yaml = nil, &block = nil) + * + * @param file [String=nil] a file name for the mustache template. + * + * @param template [String=nil] the content of the mustache template. + * + * @param on_yaml [Proc=nil] (optional) accepts a YAML front-matter String. + * + * @param &block to be used as an implicit \p on_yaml (if missing). + * + * @return [Iodine::Mustache] returns an Iodine::Mustache object with the + * provided template ready for rendering. + * + * **Note**: Either the file or template argument (or both) must be provided. + */ +static VALUE mus_load_template(int argc, VALUE *argv, VALUE self) { // clang-format on + VALUE on_yaml_block = Qnil; + fio_buf_info_s fname = FIO_BUF_INFO0; + fio_buf_info_s data = FIO_BUF_INFO0; + + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(fname, 0, "file", 0), + IODINE_ARG_BUF(data, 0, "template", 0), + IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 0)); + + if (!fname.buf && !data.buf) + rb_raise(rb_eArgError, + "either template `file` or `template` should be provided."); + if ((data.buf && !data.len) || (fname.buf && !fname.len)) + rb_raise(rb_eArgError, + "neither template `file` nor `template` can be empty."); + + fio_mustache_s *m = + fio_mustache_load(.data = data, + .filename = fname, + .on_yaml_front_matter = + (on_yaml_block == Qnil ? NULL + : mus_on_yaml_front_matter), + .udata = (void *)on_yaml_block); + if (!m) + rb_raise(rb_eStandardError, + "template couldn't be found or empty, nothing to build."); + fio_mustache_s **mp = fio_mustache_wrapper_get(self); + *mp = m; + return self; +} + +/** + * Renders the template given at initialization with the provided context. + * + * m.render(ctx) + * + * @param ctx the top level context for the template data. + * + * @return [String] returns a String containing the rendered template. + */ +static VALUE mus_render(VALUE self, VALUE ctx) { + fio_mustache_s **mp = fio_mustache_wrapper_get(self); + if (!*mp) + rb_raise(rb_eStandardError, "mustache template is empty, couldn't render."); + char *result = + (char *)fio_mustache_build(*mp, + .get_var = mus_get_var, + .array_length = mus_get_array_len, + .get_var_index = mus_get_var_index, + .var2str = mus_var2str, + .var_is_truthful = mus_var_is_truthful, + .release_var = mus_release_var, + .is_lambda = mus_is_lambda, + .ctx = (void *)ctx); + if (!result) + return Qnil; + VALUE str = rb_utf8_str_new(result, fio_bstr_len(result)); + fio_bstr_free(result); + return str; +} +// clang-format off +/** + * Loads a template file and renders it into a String. + * + * Iodine::Mustache.render(file = nil, data = nil, ctx = nil, on_yaml = nil) + * + * @param file [String=nil] a file name for the mustache template. + * + * @param template [String=nil] the content of the mustache template. + * + * @param ctx [String=nil] the top level context for the template data. + * + * @param on_yaml [Proc=nil] (optional) accepts a YAML front-matter String. + * + * @param &block to be used as an implicit \p on_yaml (if missing). + * + * @return [String] returns a String containing the rendered template. + * + * **Note**: Either the file or template argument (or both) must be provided. + */ +static VALUE mus_build_and_render(int argc, VALUE *argv, VALUE klass) { // clang-format on + VALUE ctx = Qnil; + VALUE on_yaml_block = Qnil; + fio_buf_info_s fname = FIO_BUF_INFO2(NULL, 0); + fio_buf_info_s data = FIO_BUF_INFO2(NULL, 0); + + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(fname, 0, "file", 0), + IODINE_ARG_BUF(data, 0, "template", 0), + IODINE_ARG_RB(ctx, 0, "ctx", 0), + IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 0)); + + if (!fname.buf && !data.buf) + rb_raise(rb_eArgError, + "either template `file` or `template` should be provided."); + if ((data.buf && !data.len) || (fname.buf && !fname.len)) + rb_raise(rb_eArgError, + "neither template `file` nor `template` can be empty."); + + fio_mustache_s *m = + fio_mustache_load(.data = data, + .filename = fname, + .on_yaml_front_matter = + (on_yaml_block == Qnil ? NULL + : mus_on_yaml_front_matter), + .udata = (void *)on_yaml_block); + if (!m) + rb_raise(rb_eStandardError, + "template couldn't be found or empty, nothing to build."); + + char *result = (char *)fio_mustache_build(m, + .get_var = mus_get_var, + .array_length = mus_get_array_len, + .get_var_index = mus_get_var_index, + .var2str = mus_var2str, + .release_var = mus_release_var, + .is_lambda = mus_is_lambda, + .ctx = (void *)ctx); + fio_mustache_free(m); + if (!result) + return Qnil; + VALUE str = rb_utf8_str_new(result, fio_bstr_len(result)); + fio_bstr_free(result); + return str; +} + +/** Initialize Iodine::Mustache */ // clang-format off +/** +Iodine::Mustache is a lighter implementation of the mustache template rendering gem, with a focus on a few minor security details: + +1. HTML escaping is more aggressive, increasing XSS protection. Read why at: [wonko.com/post/html-escaping](https://wonko.com/post/html-escaping). + +2. Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `user`, `name.first` and than as each `user`, `name` , `first`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts. + +3. Less logic: i.e., lambdas / procs do not automatically invoke a re-rendering... I'd remove them completely as unsafe, but for now there's that. + +4. Improved Protection against Endless Recursion: i.e., Partial templates reference themselves when recursively nested (instead of being recursively re-loaded); and Partial's context is limited to their starting point's context (cannot access parent context). + +It wasn't designed specifically for speed or performance... but it ended up being significantly faster. + +## Usage + +This approach to Mustache templates may require more forethought when designing either the template or the context's data format, however it should force implementations to be more secure and performance aware. + +Approach: + + require 'iodine' + # One-off rendering of (possibly dynamic) template: + result = Iodine::Mustache.render(template: "{{foo}}", ctx: {foo: "bar"}) # => "bar" + # caching of parsed template data for multiple render operations: + view = Iodine::Mustache.new(file: "./views/foo.mustache", template: "{{foo}}") + results = Array.new(100) {|i| view.render(foo: "bar#{i}") } # => ["bar0", "bar1", ...] + +## Performance + +Performance may differ according to architecture and compiler used. Please measure: + + require 'benchmark/ips' + require 'mustache' + require 'iodine' + + # Benchmark code was copied, in part, from: + # https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb + # The test is, sadly, biased and doesn't test for missing elements, proc/method resolution or template partials. + def benchmark_mustache + template = """ + {{#products}} +
      + +
      + {{/products}} + """ + + # fill Hash objects with values for template rendering + data_1000 = { + products: [] + } + data_1000_escaped = { + products: [] + } + + 1000.times do + data_1000[:products] << { + :external_index=>"product", + :url=>"/products/7", + :image=>"products/product.jpg" + } + data_1000_escaped[:products] << { + :external_index=>"This should've been \"properly\" escaped.", + :url=>"/products/7", + :image=>"products/product.jpg" + } + end + + # prepare Iodine::Mustache reduced Mustache template engine + mus_view = Iodine::Mustache.new(template: template) + + # prepare official Mustache template engine + view = Mustache.new + view.template = template + view.render # Call render once so the template will be compiled + + # benchmark different use cases + Benchmark.ips do |x| + x.report("Ruby Mustache render list of 1000") do |times| + view.render(data_1000) + end + x.report("Iodine::Mustache render list of 1000") do |times| + mus_view.render(data_1000) + end + + x.report("Ruby Mustache render list of 1000 with escaped data") do |times| + view.render(data_1000_escaped) + end + x.report("Iodine::Mustache render list of 1000 with escaped data") do |times| + mus_view.render(data_1000_escaped) + end + + x.report("Ruby Mustache - no caching - render list of 1000") do |times| + Mustache.render(template, data_1000) + end + x.report("Iodine::Mustache - no caching - render list of 1000") do |times| + Iodine::Mustache.render(nil, template, data_1000) + end + x.compare! + end ; nil + end + + benchmark_mustache + +*/ +static void Init_Iodine_Mustache(void) { + VALUE m = rb_define_class_under(iodine_rb_IODINE, "Mustache", rb_cObject); // clang-format on + rb_define_alloc_func(m, fio_mustache_wrapper_alloc); + rb_define_method(m, "initialize", mus_load_template, -1); + rb_define_method(m, "render", mus_render, 1); + rb_define_singleton_method(m, "render", mus_build_and_render, -1); +} +#endif /* H___IODINE_MUSTA___H */ diff --git a/ext/iodine/iodine_pubsub.c b/ext/iodine/iodine_pubsub.c deleted file mode 100644 index 31c17c29..00000000 --- a/ext/iodine/iodine_pubsub.c +++ /dev/null @@ -1,580 +0,0 @@ -#include "iodine_pubsub.h" -#include "iodine_fiobj2rb.h" - -#include "redis_engine.h" - -/* -NOTE: - -This file defines Pub/Sub management and settings, not Pub/Sub usage. - -This file doen't include the `Iodine.subscribe`, `Iodine.unsubscribe` and -`Iodine.publish` methods. - -These methods are all defined in the Connection module (iodine_connection.h). -*/ - -/* ***************************************************************************** -static consts -***************************************************************************** */ - -static ID subscribe_id; -static ID unsubscribe_id; -static ID publish_id; -static ID default_id; -static ID redis_id; -static ID call_id; - -/** -The {Iodine::PubSub::Engine} class is the parent for all engines to inherit -from. - -Engines should inherit this class and override the `subscribe`, `unsubscribe` -and `publish` callbacks (which shall be called by {Iodine}). - -After creation, Engines should attach themselves to Iodine using -{Iodine::PubSub.attach} or their callbacks will never get called. - -Engines can also set themselves to be the default engine using -{Iodine::PubSub.default=}. -*/ -static VALUE EngineClass; - -/* ***************************************************************************** -Ruby <=> C Callbacks -***************************************************************************** */ - -typedef struct { - iodine_pubsub_s *eng; - fio_str_info_s ch; - fio_str_info_s msg; - fio_match_fn pattern; -} iodine_pubsub_task_s; - -#define iodine_engine(eng) ((iodine_pubsub_s *)(eng)) - -/* calls an engine's `subscribe` callback within the GVL */ -static void *iodine_pubsub_GIL_subscribe(void *tsk_) { - iodine_pubsub_task_s *task = tsk_; - VALUE args[2]; - args[0] = rb_str_new(task->ch.data, task->ch.len); - args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis - IodineCaller.call2(task->eng->handler, subscribe_id, 2, args); - return NULL; -} - -/** Must subscribe channel. Failures are ignored. */ -static void iodine_pubsub_on_subscribe(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, - fio_match_fn match) { - if (iodine_engine(eng)->handler == Qnil) { - return; - } - iodine_pubsub_task_s task = { - .eng = iodine_engine(eng), .ch = channel, .pattern = match}; - IodineCaller.enterGVL(iodine_pubsub_GIL_subscribe, &task); -} - -/* calls an engine's `unsubscribe` callback within the GVL */ -static void *iodine_pubsub_GIL_unsubscribe(void *tsk_) { - iodine_pubsub_task_s *task = tsk_; - VALUE args[2]; - args[0] = rb_str_new(task->ch.data, task->ch.len); - args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis - IodineCaller.call2(task->eng->handler, unsubscribe_id, 2, args); - return NULL; -} - -/** Must unsubscribe channel. Failures are ignored. */ -static void iodine_pubsub_on_unsubscribe(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, - fio_match_fn match) { - if (iodine_engine(eng)->handler == Qnil) { - return; - } - iodine_pubsub_task_s task = { - .eng = iodine_engine(eng), .ch = channel, .pattern = match}; - IodineCaller.enterGVL(iodine_pubsub_GIL_unsubscribe, &task); -} - -/* calls an engine's `unsubscribe` callback within the GVL */ -static void *iodine_pubsub_GIL_publish(void *tsk_) { - iodine_pubsub_task_s *task = tsk_; - VALUE args[2]; - args[0] = rb_str_new(task->ch.data, task->ch.len); - args[1] = rb_str_new(task->msg.data, task->msg.len); - IodineCaller.call2(task->eng->handler, publish_id, 2, args); - return NULL; -} - -/** Should return 0 on success and -1 on failure. */ -static void iodine_pubsub_on_publish(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, fio_str_info_s msg, - uint8_t is_json) { - if (iodine_engine(eng)->handler == Qnil) { - return; - } - iodine_pubsub_task_s task = { - .eng = iodine_engine(eng), .ch = channel, .msg = msg}; - IodineCaller.enterGVL(iodine_pubsub_GIL_publish, &task); - (void)is_json; -} - -/* ***************************************************************************** -Ruby methods -***************************************************************************** */ - -/** -OVERRIDE this callback - it will be called by {Iodine} whenever the process -CLUSTER (not just this process) subscribes to a stream / channel. -*/ -static VALUE iodine_pubsub_subscribe(VALUE self, VALUE to, VALUE match) { - return Qnil; - (void)self; - (void)to; - (void)match; -} - -/** -OVERRIDE this callback - it will be called by {Iodine} whenever the whole -process CLUSTER (not just this process) unsubscribes from a stream / channel. -*/ -static VALUE iodine_pubsub_unsubscribe(VALUE self, VALUE to, VALUE match) { - return Qnil; - (void)self; - (void)to; - (void)match; -} - -/** -OVERRIDE this callback - it will be called by {Iodine} whenever the -{Iodine.publish} (or {Iodine::Connection#publish}) is called for this engine. - -If this {Engine} is set as the default {Engine}, then any call to -{Iodine.publish} (or {Iodine::Connection#publish} will invoke this callback -(unless another {Engine} was specified). - -NOTE: this callback is called per process event (not per cluster event) and the -{Engine} is responsible for message propagation. -*/ -static VALUE iodine_pubsub_publish(VALUE self, VALUE to, VALUE message) { - iodine_pubsub_s *e = iodine_pubsub_CData(self); - if (!e || e->engine == &e->do_not_touch) { - /* this is a Ruby engine, nothing to do. */ - return Qnil; - } - fio_publish(.engine = e->engine, .channel = IODINE_RSTRINFO(to), - .message = IODINE_RSTRINFO(message)); - return self; -} - -/* ***************************************************************************** -Ruby <=> C Data Type -***************************************************************************** */ - -/* a callback for the GC (marking active objects) */ -static void iodine_pubsub_data_mark(void *c_) { - iodine_pubsub_s *c = c_; - if (c->handler != Qnil) { - rb_gc_mark(c->handler); - } -} -/* a callback for the GC (marking active objects) */ -static void iodine_pubsub_data_free(void *c_) { - FIO_LOG_DEBUG("iodine destroying engine"); - iodine_pubsub_s *data = c_; - fio_pubsub_detach(data->engine); - IodineStore.remove(data->handler); /* redundant except during exit */ - if (data->dealloc) { - data->dealloc(data->engine); - } - free(data); -} - -static size_t iodine_pubsub_data_size(const void *c_) { - return sizeof(iodine_pubsub_s); - (void)c_; -} - -const rb_data_type_t iodine_pubsub_data_type = { - .wrap_struct_name = "IodinePubSubData", - .function = - { - .dmark = iodine_pubsub_data_mark, - .dfree = iodine_pubsub_data_free, - .dsize = iodine_pubsub_data_size, - }, - .data = NULL, - // .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -/* Iodine::PubSub::Engine.allocate */ -static VALUE iodine_pubsub_data_alloc_c(VALUE self) { - iodine_pubsub_s *c = malloc(sizeof(*c)); - *c = (iodine_pubsub_s){ - .do_not_touch = - { - .subscribe = iodine_pubsub_on_subscribe, - .unsubscribe = iodine_pubsub_on_unsubscribe, - .publish = iodine_pubsub_on_publish, - }, - .handler = Qnil, - .engine = &c->do_not_touch, - }; - return TypedData_Wrap_Struct(self, &iodine_pubsub_data_type, c); -} - -/* ***************************************************************************** -C engines -***************************************************************************** */ - -static VALUE iodine_pubsub_make_C_engine(const fio_pubsub_engine_s *e) { - VALUE engine = IodineCaller.call(EngineClass, rb_intern2("new", 3)); - if (engine == Qnil) { - return Qnil; - } - iodine_pubsub_CData(engine)->engine = (fio_pubsub_engine_s *)e; - return engine; -} - -/* ***************************************************************************** -PubSub module methods -***************************************************************************** */ - -/** Sets the default {Iodine::PubSub::Engine} for pub/sub methods. */ -static VALUE iodine_pubsub_default_set(VALUE self, VALUE engine) { - if (engine == Qnil) { - engine = rb_const_get(self, rb_intern2("CLUSTER", 7)); - } - iodine_pubsub_s *e = iodine_pubsub_CData(engine); - if (!e) { - rb_raise(rb_eTypeError, "not a valid engine"); - return Qnil; - } - if (e->handler == Qnil) { - e->handler = engine; - } - FIO_PUBSUB_DEFAULT = e->engine; - rb_ivar_set(self, rb_intern2("default_engine", 14), engine); - return engine; -} - -/** Returns the default {Iodine::PubSub::Engine} for pub/sub methods. */ -static VALUE iodine_pubsub_default_get(VALUE self) { - VALUE def = rb_ivar_get(self, rb_intern2("default_engine", 14)); - if (def == Qnil) { - def = rb_const_get(self, rb_intern2("CLUSTER", 7)); - iodine_pubsub_default_set(self, def); - } - return def; -} - -/** - * Attaches an {Iodine::PubSub::Engine} to the pub/sub system (more than a - * single engine can be attached at the same time). - * - * After an engine was attached, it's callbacks will be called - * ({Iodine::PubSub::Engine#subscribe} and {Iodine::PubSub::Engine#unsubscribe}) - * in response to Pub/Sub events. - */ -static VALUE iodine_pubsub_attach(VALUE self, VALUE engine) { - iodine_pubsub_s *e = iodine_pubsub_CData(engine); - if (!e) { - rb_raise(rb_eTypeError, "not a valid engine"); - return Qnil; - } - if (e->handler == Qnil) { - e->handler = engine; - } - IodineStore.add(engine); - fio_pubsub_attach(e->engine); - return engine; - (void)self; -} - -/** - * Removes an {Iodine::PubSub::Engine} from the pub/sub system. - * - * After an {Iodine::PubSub::Engine} was detached, Iodine will no longer call - * the {Iodine::PubSub::Engine}'s callbacks ({Iodine::PubSub::Engine#subscribe} - * and {Iodine::PubSub::Engine#unsubscribe}) - */ -static VALUE iodine_pubsub_detach(VALUE self, VALUE engine) { - iodine_pubsub_s *e = iodine_pubsub_CData(engine); - if (!e) { - rb_raise(rb_eTypeError, "not a valid engine"); - return Qnil; - } - if (e->handler == Qnil) { - e->handler = engine; - } - IodineStore.remove(engine); - fio_pubsub_detach(e->engine); - return engine; - (void)self; -} - -/** - * Forces {Iodine} to call the {Iodine::PubSub::Engine#subscribe} callback for - * all existing subscriptions (i.e., when reconnecting to a Pub/Sub backend such - * as Redis). - */ -static VALUE iodine_pubsub_reset(VALUE self, VALUE engine) { - iodine_pubsub_s *e = iodine_pubsub_CData(engine); - if (!e) { - rb_raise(rb_eTypeError, "not a valid engine"); - return Qnil; - } - if (e->handler == Qnil) { - e->handler = engine; - } - fio_pubsub_reattach(e->engine); - return engine; - (void)self; -} - -/* ***************************************************************************** -Redis Engine -***************************************************************************** */ - -/** -Initializes a new {Iodine::PubSub::Redis} engine. - - Iodine::PubSub::Redis.new(url, opt = {}) - -use: - - REDIS_URL = "redis://localhost:6379/" - Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds - -To use Redis authentication, add the password to the URL. i.e.: - - REDIS_URL = "redis://redis:password@localhost:6379/" - Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds - -The options hash accepts: - -:ping:: the PING interval up to 255 seconds. Default: 0 (~5 minutes). -*/ -static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) { - if (!argc) { - rb_raise(rb_eArgError, "Iodine::PubSub::Redis.new(address, opt={}) " - "requires at least 1 argument."); - } - VALUE url = argv[0]; - Check_Type(url, T_STRING); - if (RSTRING_LEN(url) > 4096) { - rb_raise(rb_eArgError, "Redis URL too long."); - } - uint8_t ping = 0; - - iodine_pubsub_s *e = iodine_pubsub_CData(self); - if (!e) { - rb_raise(rb_eTypeError, "not a valid engine"); - return Qnil; - } - - /* extract options */ - if (argc == 2) { - Check_Type(argv[1], T_HASH); - VALUE tmp = rb_hash_aref(argv[1], rb_id2sym(rb_intern2("ping", 4))); - if (tmp != Qnil) { - Check_Type(tmp, T_FIXNUM); - if (NUM2SIZET(tmp) > 255) { - rb_raise(rb_eArgError, - ":ping must be a non-negative integer under 255 seconds."); - } - ping = (uint8_t)NUM2SIZET(tmp); - } - } - - /* parse URL assume redis://redis:password@localhost:6379 */ - fio_url_s info = fio_url_parse(RSTRING_PTR(url), RSTRING_LEN(url)); - - FIO_LOG_INFO("Initializing Redis engine for address: %.*s", - (int)RSTRING_LEN(url), RSTRING_PTR(url)); - /* create engine */ - e->engine = redis_engine_create(.address = info.host, .port = info.port, - .auth = info.password, .ping_interval = ping); - if (!e->engine) { - e->engine = &e->do_not_touch; - } else { - e->dealloc = redis_engine_destroy; - } - - if (e->engine == &e->do_not_touch) { - rb_raise(rb_eArgError, - "Error initializing the Redis engine - malformed URL?"); - } - return self; - (void)self; - (void)argc; - (void)argv; -} - -struct redis_callback_data { - FIOBJ response; - VALUE block; -}; - -/** A callback for Redis commands. */ -static void *iodine_pubsub_redis_callback_in_gil(void *data_) { - struct redis_callback_data *d = data_; - VALUE rb = Qnil; - if (!FIOBJ_IS_NULL(d->response)) { - rb = fiobj2rb_deep(d->response, 0); - } - IodineCaller.call2(d->block, call_id, 1, &rb); - IodineStore.remove(rb); - return NULL; -} - -/** A callback for Redis commands. */ -static void iodine_pubsub_redis_callback(fio_pubsub_engine_s *e, FIOBJ response, - void *udata) { - struct redis_callback_data d = {.response = response, .block = (VALUE)udata}; - if (d.block == Qnil) { - return; - } - IodineCaller.enterGVL(iodine_pubsub_redis_callback_in_gil, &d); - IodineStore.remove(d.block); - (void)e; -} - -// clang-format off -/** -Sends a Redis command. Accepts an optional block that will recieve the response. - -i.e.: - - REDIS_URL = "redis://redis:password@localhost:6379/" - redis = Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds - Iodine::PubSub.default = redis - redis.cmd("KEYS", "*") {|result| p result -} - - -*/ -static VALUE iodine_pubsub_redis_cmd(int argc, VALUE *argv, VALUE self) { - // clang-format on - if (argc <= 0) { - rb_raise(rb_eArgError, "Iodine::PubSub::Redis#cmd(command, ...) is missing " - "the required command argument."); - } - iodine_pubsub_s *e = iodine_pubsub_CData(self); - if (!e || !e->engine || e->engine == &e->do_not_touch) { - rb_raise(rb_eTypeError, - "Iodine::PubSub::Redis internal error - obsolete object?"); - } - VALUE block = Qnil; - if (rb_block_given_p()) { - block = IodineStore.add(rb_block_proc()); - } - FIOBJ data = fiobj_ary_new2((size_t)argc); - for (int i = 0; i < argc; ++i) { - switch (TYPE(argv[i])) { - case T_SYMBOL: - argv[i] = rb_sym2str(argv[i]); - /* overflow */ - case T_STRING: - fiobj_ary_push(data, - fiobj_str_new(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i]))); - break; - case T_FIXNUM: - fiobj_ary_push(data, fiobj_num_new(NUM2SSIZET(argv[i]))); - break; - case T_FLOAT: - fiobj_ary_push(data, fiobj_float_new(rb_float_value(argv[i]))); - break; - case T_NIL: - fiobj_ary_push(data, fiobj_null()); - break; - case T_TRUE: - fiobj_ary_push(data, fiobj_true()); - break; - case T_FALSE: - fiobj_ary_push(data, fiobj_false()); - break; - default: - goto wrong_type; - } - } - if (redis_engine_send(e->engine, data, iodine_pubsub_redis_callback, - (void *)block)) { - iodine_pubsub_redis_callback(e->engine, fiobj_null(), (void *)block); - } - fiobj_free(data); - return self; - -wrong_type: - fiobj_free(data); - rb_raise(rb_eArgError, - "only String, Number (with limits), Symbol, true, false and nil " - "arguments can be used."); -} - -/* ***************************************************************************** -Module initialization -***************************************************************************** */ - -/** Initializes the Connection Ruby class. */ -void iodine_pubsub_init(void) { - subscribe_id = rb_intern2("subscribe", 9); - unsubscribe_id = rb_intern2("unsubscribe", 11); - publish_id = rb_intern2("publish", 7); - default_id = rb_intern2("default_engine", 14); - redis_id = rb_intern2("redis", 5); - call_id = rb_intern2("call", 4); - - /* Define the PubSub module and it's methods */ - - VALUE PubSubModule = rb_define_module_under(IodineModule, "PubSub"); - rb_define_module_function(PubSubModule, "default=", iodine_pubsub_default_set, - 1); - rb_define_module_function(PubSubModule, "default", iodine_pubsub_default_get, - 0); - rb_define_module_function(PubSubModule, "attach", iodine_pubsub_attach, 1); - rb_define_module_function(PubSubModule, "detach", iodine_pubsub_detach, 1); - rb_define_module_function(PubSubModule, "reset", iodine_pubsub_reset, 1); - - /* Define the Engine class and it's methods */ - - /** - The {Iodine::PubSub::Engine} class is the parent for all engines to inherit - from. - - Engines should inherit this class and override the `subscribe`, `unsubscribe` - and `publish` callbacks (which shall be called by {Iodine}). - - After creation, Engines should attach themselves to Iodine using - {Iodine::PubSub.attach} or their callbacks will never get called. - - Engines can also set themselves to be the default engine using - {Iodine::PubSub.default=}. - */ - EngineClass = rb_define_class_under(PubSubModule, "Engine", rb_cObject); - rb_define_alloc_func(EngineClass, iodine_pubsub_data_alloc_c); - rb_define_method(EngineClass, "subscribe", iodine_pubsub_subscribe, 2); - rb_define_method(EngineClass, "unsubscribe", iodine_pubsub_unsubscribe, 2); - rb_define_method(EngineClass, "publish", iodine_pubsub_publish, 2); - - /* Define the CLUSTER and PROCESS engines */ - - /* CLUSTER publishes data to all the subscribers in the process cluster. */ - rb_define_const(PubSubModule, "CLUSTER", - iodine_pubsub_make_C_engine(FIO_PUBSUB_CLUSTER)); - /* PROCESS publishes data to all the subscribers in a single process. */ - rb_define_const(PubSubModule, "PROCESS", - iodine_pubsub_make_C_engine(FIO_PUBSUB_PROCESS)); - /* SIBLINGS publishes data to all the subscribers in the *other* processes - * process. */ - rb_define_const(PubSubModule, "SIBLINGS", - iodine_pubsub_make_C_engine(FIO_PUBSUB_SIBLINGS)); - /* PUBLISH2ROOT publishes data only to the root / master process. */ - rb_define_const(PubSubModule, "PUBLISH2ROOT", - iodine_pubsub_make_C_engine(FIO_PUBSUB_ROOT)); - - VALUE RedisClass = rb_define_class_under(PubSubModule, "Redis", EngineClass); - rb_define_method(RedisClass, "initialize", iodine_pubsub_redis_new, -1); - rb_define_method(RedisClass, "cmd", iodine_pubsub_redis_cmd, -1); -} diff --git a/ext/iodine/iodine_pubsub.h b/ext/iodine/iodine_pubsub.h deleted file mode 100644 index d4c40cd5..00000000 --- a/ext/iodine/iodine_pubsub.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef H_IODINE_PUBSUB_H -#define H_IODINE_PUBSUB_H - -#include "iodine.h" - -#include "fio.h" - -/** Initializes the PubSub::Engine Ruby class. */ -void iodine_pubsub_init(void); - -extern const rb_data_type_t iodine_pubsub_data_type; - -typedef struct { - fio_pubsub_engine_s do_not_touch; - VALUE handler; - fio_pubsub_engine_s *engine; - void (*dealloc)(fio_pubsub_engine_s *engine); -} iodine_pubsub_s; - -static inline iodine_pubsub_s *iodine_pubsub_CData(VALUE obj) { - iodine_pubsub_s *c = NULL; - TypedData_Get_Struct(obj, iodine_pubsub_s, &iodine_pubsub_data_type, c); - return c; -} - -#endif diff --git a/ext/iodine/iodine_pubsub_eng.h b/ext/iodine/iodine_pubsub_eng.h new file mode 100644 index 00000000..c2b46c3e --- /dev/null +++ b/ext/iodine/iodine_pubsub_eng.h @@ -0,0 +1,290 @@ +#ifndef H___IODINE_PUBSUB_ENG___H +#define H___IODINE_PUBSUB_ENG___H +#include "iodine.h" + +/* ***************************************************************************** +Ruby PubSub Engine Type +***************************************************************************** */ + +typedef struct iodine_pubsub_eng_s { + fio_pubsub_engine_s engine; + fio_pubsub_engine_s *ptr; + VALUE handler; +} iodine_pubsub_eng_s; + +/* ***************************************************************************** +Ruby PubSub Engine Bridge +***************************************************************************** */ + +typedef struct iodine_pubsub_eng___args_s { + iodine_pubsub_eng_s *eng; + fio_msg_s *msg; + fio_buf_info_s channel; + int16_t filter; +} iodine_pubsub_eng___args_s; + +/** Called after the engine was detached, may be used for cleanup. */ +static void iodine_pubsub_eng___detached(const fio_pubsub_engine_s *eng) { + iodine_pubsub_eng_s *e = (iodine_pubsub_eng_s *)eng; + iodine_ruby_call_outside(e->handler, rb_intern("on_cleanup")); +} + +static void *iodine_pubsub_eng___subscribe__in_GC(void *a_) { + iodine_pubsub_eng___args_s *args = (iodine_pubsub_eng___args_s *)a_; + VALUE ch = rb_str_new(args->channel.buf, args->channel.len); + STORE.hold(ch); + iodine_ruby_call_inside(args->eng->handler, rb_intern("subscribe"), 1, &ch); + STORE.release(ch); + return NULL; +} +/** Subscribes to a channel. Called ONLY in the Root (master) process. */ +static void iodine_pubsub_eng___subscribe(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + iodine_pubsub_eng___args_s args = { + .eng = (iodine_pubsub_eng_s *)eng, + .channel = channel, + .filter = filter, + }; + rb_thread_call_with_gvl(iodine_pubsub_eng___subscribe__in_GC, &args); +} + +static void *iodine_pubsub_eng___psubscribe__in_GC(void *a_) { + iodine_pubsub_eng___args_s *args = (iodine_pubsub_eng___args_s *)a_; + VALUE ch = rb_str_new(args->channel.buf, args->channel.len); + STORE.hold(ch); + iodine_ruby_call_inside(args->eng->handler, rb_intern("psubscribe"), 1, &ch); + STORE.release(ch); + return NULL; +} +/** Subscribes to a pattern. Called ONLY in the Root (master) process. */ +static void iodine_pubsub_eng___psubscribe(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + iodine_pubsub_eng___args_s args = { + .eng = (iodine_pubsub_eng_s *)eng, + .channel = channel, + .filter = filter, + }; + rb_thread_call_with_gvl(iodine_pubsub_eng___psubscribe__in_GC, &args); +} + +static void *iodine_pubsub_eng___unsubscribe__in_GC(void *a_) { + iodine_pubsub_eng___args_s *args = (iodine_pubsub_eng___args_s *)a_; + VALUE ch = rb_str_new(args->channel.buf, args->channel.len); + STORE.hold(ch); + iodine_ruby_call_inside(args->eng->handler, rb_intern("unsubscribe"), 1, &ch); + STORE.release(ch); + return NULL; +} +/** Unsubscribes to a channel. Called ONLY in the Root (master) process. */ +static void iodine_pubsub_eng___unsubscribe(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + iodine_pubsub_eng___args_s args = { + .eng = (iodine_pubsub_eng_s *)eng, + .channel = channel, + .filter = filter, + }; + rb_thread_call_with_gvl(iodine_pubsub_eng___unsubscribe__in_GC, &args); +} + +static void *iodine_pubsub_eng___punsubscribe__in_GC(void *a_) { + iodine_pubsub_eng___args_s *args = (iodine_pubsub_eng___args_s *)a_; + VALUE ch = rb_str_new(args->channel.buf, args->channel.len); + STORE.hold(ch); + iodine_ruby_call_inside(args->eng->handler, + rb_intern("punsubscribe"), + 1, + &ch); + STORE.release(ch); + return NULL; +} +/** Unsubscribe to a pattern. Called ONLY in the Root (master) process. */ +static void iodine_pubsub_eng___punsubscribe(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + iodine_pubsub_eng___args_s args = { + .eng = (iodine_pubsub_eng_s *)eng, + .channel = channel, + .filter = filter, + }; + rb_thread_call_with_gvl(iodine_pubsub_eng___punsubscribe__in_GC, &args); +} + +static void *iodine_pubsub_eng___publish__in_GC(void *a_) { + iodine_pubsub_eng___args_s *args = (iodine_pubsub_eng___args_s *)a_; + VALUE msg = iodine_pubsub_msg_create(args->msg); + iodine_ruby_call_inside(args->eng->handler, rb_intern("publish"), 1, &msg); + STORE.release(msg); + return NULL; +} + +/** Publishes a message through the engine. Called by any worker / thread. */ +static void iodine_pubsub_eng___publish(const fio_pubsub_engine_s *eng, + fio_msg_s *msg) { + iodine_pubsub_eng___args_s args = { + .eng = (iodine_pubsub_eng_s *)eng, + .msg = msg, + }; + rb_thread_call_with_gvl(iodine_pubsub_eng___publish__in_GC, &args); +} + +static fio_pubsub_engine_s iodine_pubsub___engine_validate(VALUE obj) { + fio_pubsub_engine_s r = { + /** Called after the engine was detached, may be used for cleanup. */ + .detached = (rb_respond_to(obj, rb_intern("on_cleanup"))) + ? iodine_pubsub_eng___detached + : NULL, + /** Subscribes to a channel. Called ONLY in the Root (master) process. */ + .subscribe = (rb_respond_to(obj, rb_intern("subscribe"))) + ? iodine_pubsub_eng___subscribe + : NULL, + /** Subscribes to a pattern. Called ONLY in the Root (master) process. */ + .psubscribe = (rb_respond_to(obj, rb_intern("psubscribe"))) + ? iodine_pubsub_eng___psubscribe + : NULL, + /** Unsubscribes to a channel. Called ONLY in the Root (master) process. + */ + .unsubscribe = (rb_respond_to(obj, rb_intern("unsubscribe"))) + ? iodine_pubsub_eng___unsubscribe + : NULL, + /** Unsubscribe to a pattern. Called ONLY in the Root (master) process. */ + .punsubscribe = (rb_respond_to(obj, rb_intern("punsubscribe"))) + ? iodine_pubsub_eng___punsubscribe + : NULL, + /** Publishes a message through the engine. Called by any worker / thread. + */ + .publish = (rb_respond_to(obj, rb_intern("publish"))) + ? iodine_pubsub_eng___publish + : NULL, + }; + return r; +} +/* ***************************************************************************** +Ruby PubSub Engine Object +***************************************************************************** */ + +static size_t iodine_pubsub_eng_data_size(const void *ptr_) { + iodine_pubsub_eng_s *m = (iodine_pubsub_eng_s *)ptr_; + return sizeof(*m); +} + +static void iodine_pubsub_eng_free(void *ptr_) { + iodine_pubsub_eng_s *e = (iodine_pubsub_eng_s *)ptr_; + if (FIO_PUBSUB_DEFAULT == e->ptr) + FIO_PUBSUB_DEFAULT = NULL; + fio_free(e); + FIO_LEAK_COUNTER_ON_FREE(iodine_pubsub_eng); +} + +static const rb_data_type_t IODINE_PUBSUB_ENG_DATA_TYPE = { + .wrap_struct_name = "IodinePSEngine", + .function = + { + .dfree = iodine_pubsub_eng_free, + .dsize = iodine_pubsub_eng_data_size, + }, + .data = NULL, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE iodine_pubsub_eng_alloc(VALUE klass) { + iodine_pubsub_eng_s *m = (iodine_pubsub_eng_s *)fio_malloc(sizeof(*m)); + if (!m) + goto no_memory; + *m = (iodine_pubsub_eng_s){0}; + m->ptr = &m->engine; + FIO_LEAK_COUNTER_ON_ALLOC(iodine_pubsub_eng); + m->handler = TypedData_Wrap_Struct(klass, &IODINE_PUBSUB_ENG_DATA_TYPE, m); + m->engine = iodine_pubsub___engine_validate(m->handler); + return m->handler; + +no_memory: + FIO_LOG_FATAL("Memory allocation failed"); + fio_io_stop(); + return Qnil; +} + +static iodine_pubsub_eng_s *iodine_pubsub_eng_get(VALUE self) { + iodine_pubsub_eng_s *m; + TypedData_Get_Struct(self, + iodine_pubsub_eng_s, + &IODINE_PUBSUB_ENG_DATA_TYPE, + m); + return m; +} + +/* ***************************************************************************** +Ruby Methods +***************************************************************************** */ + +static VALUE iodine_pubsub_eng_initialize(VALUE self) { + iodine_pubsub_eng_s *m = iodine_pubsub_eng_get(self); + fio_pubsub_attach(m->ptr); + return self; +} + +#define IODINE_PUBSUB_DEFAULT_NM "PUBSUB____DEFAULT" + +static VALUE iodine_pubsub_eng_default_set(VALUE klass, VALUE eng) { + fio_pubsub_engine_s *e = FIO_PUBSUB_CLUSTER; + if (!IODINE_STORE_IS_SKIP(eng)) + e = iodine_pubsub_eng_get(eng)->ptr; + FIO_PUBSUB_DEFAULT = e; + ID name = rb_intern(IODINE_PUBSUB_DEFAULT_NM); + rb_const_remove(iodine_rb_IODINE_BASE, name); + rb_const_set(iodine_rb_IODINE_BASE, name, eng); + return eng; + (void)klass; +} + +static VALUE iodine_pubsub_eng_default_get(VALUE klass) { + return rb_const_get(iodine_rb_IODINE_BASE, + rb_intern(IODINE_PUBSUB_DEFAULT_NM)); + (void)klass; +} + +/** + * Iodine::PubSub::Engine class instances are passed to subscription callbacks. + */ +static void Init_Iodine_PubSub_Engine(void) { + /** Initialize Iodine::PubSub::Engine */ + rb_define_module_function(iodine_rb_IODINE_PUBSUB, + "default=", + iodine_pubsub_eng_default_set, + 1); + rb_define_module_function(iodine_rb_IODINE_PUBSUB, + "default", + iodine_pubsub_eng_default_get, + 0); + + iodine_rb_IODINE_PUBSUB_ENG = + rb_define_class_under(iodine_rb_IODINE_PUBSUB, "Engine", rb_cObject); + STORE.hold(iodine_rb_IODINE_PUBSUB_ENG); + rb_define_alloc_func(iodine_rb_IODINE_PUBSUB_ENG, iodine_pubsub_eng_alloc); + +#define IODINE_PUBSUB_ENG_INTERNAL(name) \ + do { \ + VALUE tmp = rb_obj_alloc(iodine_rb_IODINE_PUBSUB_ENG); \ + iodine_pubsub_eng_get(tmp)->ptr = FIO_PUBSUB_##name; \ + rb_define_const(iodine_rb_IODINE_PUBSUB, #name, tmp); \ + } while (0) + IODINE_PUBSUB_ENG_INTERNAL(ROOT); + IODINE_PUBSUB_ENG_INTERNAL(PROCESS); + IODINE_PUBSUB_ENG_INTERNAL(SIBLINGS); + IODINE_PUBSUB_ENG_INTERNAL(LOCAL); + IODINE_PUBSUB_ENG_INTERNAL(CLUSTER); +#undef IODINE_PUBSUB_ENG_INTERNAL + + rb_define_const(iodine_rb_IODINE_BASE, + IODINE_PUBSUB_DEFAULT_NM, + rb_const_get(iodine_rb_IODINE_PUBSUB, rb_intern("CLUSTER"))); + + rb_define_method(iodine_rb_IODINE_PUBSUB_ENG, + "initialize", + iodine_pubsub_eng_initialize, + 0); +} + +#endif /* H___IODINE_PUBSUB_ENG___H */ diff --git a/ext/iodine/iodine_pubsub_msg.h b/ext/iodine/iodine_pubsub_msg.h new file mode 100644 index 00000000..f52b1dfa --- /dev/null +++ b/ext/iodine/iodine_pubsub_msg.h @@ -0,0 +1,153 @@ +#ifndef H___IODINE_PUBSUB_MSG___H +#define H___IODINE_PUBSUB_MSG___H +#include "iodine.h" + +/* ***************************************************************************** +Ruby PubSub Message Object +***************************************************************************** */ + +typedef enum { + IODINE_PUBSUB_MSG_STORE_id, + IODINE_PUBSUB_MSG_STORE_channel, + IODINE_PUBSUB_MSG_STORE_filter, + IODINE_PUBSUB_MSG_STORE_message, + IODINE_PUBSUB_MSG_STORE_published, + IODINE_PUBSUB_MSG_STORE_FINISH, +} iodine_pubsub_msg_store_e; + +typedef struct iodine_pubsub_msg_s { + fio_msg_s *msg; + VALUE store[IODINE_PUBSUB_MSG_STORE_FINISH]; +} iodine_pubsub_msg_s; + +static size_t iodine_pubsub_msg_data_size(const void *ptr_) { + iodine_pubsub_msg_s *m = (iodine_pubsub_msg_s *)ptr_; + return sizeof(*m) + (m->msg ? (sizeof(m->msg[0]) + m->msg->message.len + + m->msg->channel.len) + : 0); +} + +static void iodine_pubsub_msg_mark(void *m_) { + iodine_pubsub_msg_s *m = (iodine_pubsub_msg_s *)m_; + for (size_t i = 0; i < IODINE_PUBSUB_MSG_STORE_FINISH; ++i) + if (!IODINE_STORE_IS_SKIP(m->store[i])) + rb_gc_mark(m->store[i]); +} + +static void iodine_pubsub_msg_free(void *ptr_) { + iodine_pubsub_msg_s *c = (iodine_pubsub_msg_s *)ptr_; + fio_free(c); + FIO_LEAK_COUNTER_ON_FREE(iodine_pubsub_msg); +} + +static const rb_data_type_t IODINE_PUBSUB_MSG_DATA_TYPE = { + .wrap_struct_name = "IodinePSMessage", + .function = + { + .dmark = iodine_pubsub_msg_mark, + .dfree = iodine_pubsub_msg_free, + .dsize = iodine_pubsub_msg_data_size, + }, + .data = NULL, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE iodine_pubsub_msg_alloc(VALUE klass) { + iodine_pubsub_msg_s *m = (iodine_pubsub_msg_s *)fio_malloc(sizeof(*m)); + if (!m) + goto no_memory; + *m = (iodine_pubsub_msg_s){0}; + for (size_t i = 0; i < IODINE_PUBSUB_MSG_STORE_FINISH; ++i) + m->store[i] = Qnil; + FIO_LEAK_COUNTER_ON_ALLOC(iodine_pubsub_msg); + return TypedData_Wrap_Struct(klass, &IODINE_PUBSUB_MSG_DATA_TYPE, m); +no_memory: + FIO_LOG_FATAL("Memory allocation failed"); + fio_io_stop(); + return Qnil; +} + +static iodine_pubsub_msg_s *iodine_pubsub_msg_get(VALUE self) { + iodine_pubsub_msg_s *m; + TypedData_Get_Struct(self, + iodine_pubsub_msg_s, + &IODINE_PUBSUB_MSG_DATA_TYPE, + m); + return m; +} + +static VALUE iodine_pubsub_msg_create(fio_msg_s *msg) { + VALUE m = rb_obj_alloc(iodine_rb_IODINE_PUBSUB_MSG); + STORE.hold(m); + iodine_pubsub_msg_s *c = iodine_pubsub_msg_get(m); + c->store[IODINE_PUBSUB_MSG_STORE_id] = ULL2NUM(msg->id); + c->store[IODINE_PUBSUB_MSG_STORE_channel] = + (msg->channel.len ? rb_usascii_str_new(msg->channel.buf, msg->channel.len) + : Qnil); + ; + c->store[IODINE_PUBSUB_MSG_STORE_filter] = + (msg->filter ? INT2NUM(((int16_t)(msg->filter))) : Qnil); + c->store[IODINE_PUBSUB_MSG_STORE_message] = + (msg->message.len ? rb_usascii_str_new(msg->message.buf, msg->message.len) + : Qnil); + c->store[IODINE_PUBSUB_MSG_STORE_published] = + (msg->published ? ULL2NUM(msg->published) : Qnil); + return m; +} + +#define IODINE_DEF_GET_SET_FUNC(val_name, ...) \ + /** Returns the message's val_name */ \ + static VALUE iodine_pubsub_msg_##val_name##_get(VALUE self) { \ + iodine_pubsub_msg_s *c = iodine_pubsub_msg_get(self); \ + if (!c) \ + return Qnil; \ + return c->store[IODINE_PUBSUB_MSG_STORE_##val_name]; \ + } \ + /** Sets the message's val_name */ \ + static VALUE iodine_pubsub_msg_##val_name##_set(VALUE self, VALUE val) { \ + iodine_pubsub_msg_s *c = iodine_pubsub_msg_get(self); \ + if (!c) \ + return Qnil; \ + return (c->store[IODINE_PUBSUB_MSG_STORE_##val_name] = val); \ + } + +IODINE_DEF_GET_SET_FUNC(id); +IODINE_DEF_GET_SET_FUNC(channel); +IODINE_DEF_GET_SET_FUNC(filter); +IODINE_DEF_GET_SET_FUNC(message); +IODINE_DEF_GET_SET_FUNC(published); + +#undef IODINE_DEF_GET_SET_FUNC + +/** + * Iodine::PubSub::Message class instances are passed to subscription callbacks. + */ +static void Init_Iodine_PubSub_Message(void) { + /** Initialize Iodine::PubSub::Message */ // clang-format off + + iodine_rb_IODINE_PUBSUB_MSG = rb_define_class_under(iodine_rb_IODINE_PUBSUB, "Message", rb_cObject); + STORE.hold(iodine_rb_IODINE_PUBSUB_MSG); + rb_define_alloc_func(iodine_rb_IODINE_PUBSUB_MSG, iodine_pubsub_msg_alloc); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "id", iodine_pubsub_msg_id_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "channel", iodine_pubsub_msg_channel_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "event", iodine_pubsub_msg_channel_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "filter", iodine_pubsub_msg_filter_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "message", iodine_pubsub_msg_message_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "msg", iodine_pubsub_msg_message_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "data", iodine_pubsub_msg_message_get, 0); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "published", iodine_pubsub_msg_published_get, 0); + + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "to_s", iodine_pubsub_msg_message_get, 0); + + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "id=", iodine_pubsub_msg_id_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "channel=", iodine_pubsub_msg_channel_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "event=", iodine_pubsub_msg_channel_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "filter=", iodine_pubsub_msg_filter_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "message=", iodine_pubsub_msg_message_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "msg=", iodine_pubsub_msg_message_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "data=", iodine_pubsub_msg_message_set, 1); + rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "published=", iodine_pubsub_msg_published_set, 1); +} // clang-format off + + +#endif /* H___IODINE_PUBSUB_MSG___H */ diff --git a/ext/iodine/iodine_rack_io.c b/ext/iodine/iodine_rack_io.c deleted file mode 100644 index f3189444..00000000 --- a/ext/iodine/iodine_rack_io.c +++ /dev/null @@ -1,273 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#include "iodine_rack_io.h" - -#include "iodine.h" - -#include -#include -#include - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -/* IodineRackIO manages a minimal interface to act as an IO wrapper according to -these Rack specifications: - -The input stream is an IO-like object which contains the raw HTTP POST data. -When applicable, its external encoding must be “ASCII-8BIT” and it must be -opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond -to gets, each, read and rewind. - -gets must be called without arguments and return a string, or nil on EOF. - -read behaves like IO#read. Its signature is read([length, [buffer]]). If given, -length must be a non-negative Integer (>= 0) or nil, and buffer must be a String -and may not be nil. If length is given and not nil, then this method reads at -most length bytes from the input stream. If length is not given or nil, then -this method reads all data until EOF. When EOF is reached, this method returns -nil if length is given and not nil, or “” if length is not given or is nil. If -buffer is given, then the read data will be placed into buffer instead of a -newly created String object. - -each must be called without arguments and only yield Strings. - -rewind must be called without arguments. It rewinds the input stream back to the -beginning. It must not raise Errno::ESPIPE: that is, it may not be a pipe or a -socket. Therefore, handler developers must buffer the input data into some -rewindable object if the underlying input stream is not rewindable. - -close must never be called on the input stream. - -*/ - -/* ***************************************************************************** -Core data / helpers -*/ - -static VALUE rRackIO; - -static ID env_id; -static ID io_id; - -static VALUE R_INPUT; /* rack.input */ -static VALUE hijack_func_sym; -static VALUE TCPSOCKET_CLASS; -static ID for_fd_id; -static ID iodine_fd_var_id; -static ID iodine_new_func_id; -#ifdef __MINGW32__ -static ID iodine_osffd_id; -#endif -static rb_encoding *IodineUTF8Encoding; -static rb_encoding *IodineBinaryEncoding; - -#define set_handle(object, handle) \ - rb_ivar_set((object), iodine_fd_var_id, ULL2NUM((uintptr_t)handle)) - -inline static http_s *get_handle(VALUE obj) { - VALUE i = rb_ivar_get(obj, iodine_fd_var_id); - return (http_s *)NUM2ULL(i); -} - -/* ***************************************************************************** -IO API -*/ - -static inline FIOBJ get_data(VALUE self) { - VALUE i = rb_ivar_get(self, io_id); - return (FIOBJ)NUM2ULL(i); -} - -static VALUE rio_rewind(VALUE self) { - FIOBJ io = get_data(self); - if (!FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) - return Qnil; - fiobj_data_seek(io, 0); - return INT2NUM(0); -} -/** -Gets returns a line. this is okay for small lines, -but shouldn't really be used. - -Limited to ~ 1Mb of a line length. -*/ -static VALUE rio_gets(VALUE self) { - FIOBJ io = get_data(self); - if (!FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) - return Qnil; - fio_str_info_s line = fiobj_data_gets(io); - if (line.len) { - VALUE buffer = rb_str_new(line.data, line.len); - // make sure the buffer is binary encoded. - rb_enc_associate(buffer, IodineBinaryEncoding); - return buffer; - } - return Qnil; -} - -// Reads data from the IO, according to the Rack specifications for `#read`. -static VALUE rio_read(int argc, VALUE *argv, VALUE self) { - FIOBJ io = get_data(self); - VALUE buffer = Qnil; - uint8_t ret_nil = 0; - ssize_t len = 0; - if (!FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { - return (argc > 0 && argv[0] != Qnil) ? Qnil : rb_str_buf_new(0); - } - - // get the buffer object if given - if (argc == 2) { - Check_Type(argv[1], T_STRING); - buffer = argv[1]; - } - // get the length object, if given - if (argc > 0 && argv[0] != Qnil) { - Check_Type(argv[0], T_FIXNUM); - len = FIX2LONG(argv[0]); - if (len < 0) - rb_raise(rb_eRangeError, "length should be bigger then 0."); - if (len == 0) - return rb_str_buf_new(0); - ret_nil = 1; - } - // return if we're at the EOF. - fio_str_info_s buf = fiobj_data_read(io, len); - if (buf.len) { - // create the buffer if we don't have one. - if (buffer == Qnil) { - // make sure the buffer is binary encoded. - buffer = rb_enc_str_new(buf.data, buf.len, IodineBinaryEncoding); - } else { - // make sure the buffer is binary encoded. - rb_enc_associate(buffer, IodineBinaryEncoding); - if (rb_str_capacity(buffer) < (size_t)buf.len) - rb_str_resize(buffer, buf.len); - memcpy(RSTRING_PTR(buffer), buf.data, buf.len); - rb_str_set_len(buffer, buf.len); - } - return buffer; - } - return ret_nil ? Qnil : rb_str_buf_new(0); -} - -// Does nothing - this is controlled by the server. -static VALUE rio_close(VALUE self) { - // FIOBJ io = get_data(self); - // fiobj_free(io); // we don't call fiobj_dup, do we? - rb_ivar_set(self, io_id, INT2NUM(0)); - (void)self; - return Qnil; -} - -// Passes each line of the input to the block. This should be avoided. -static VALUE rio_each(VALUE self) { - rb_need_block(); - rio_rewind(self); - VALUE str = Qnil; - while ((str = rio_gets(self)) != Qnil) { - rb_yield(str); - } - return self; -} - -/* ***************************************************************************** -Hijacking -*/ - -// defined by iodine_http -extern VALUE IODINE_R_HIJACK; // for Rack: rack.hijack -extern VALUE IODINE_R_HIJACK_CB; // for Rack: rack.hijack -extern VALUE IODINE_R_HIJACK_IO; // for Rack: rack.hijack_io - -static VALUE rio_get_io(int argc, VALUE *argv, VALUE self) { - if (TCPSOCKET_CLASS == Qnil) - return Qfalse; - VALUE env = rb_ivar_get(self, env_id); - http_s *h = get_handle(self); - if (h == NULL) { - /* we're repeating ourselves, aren't we? */ - VALUE io = rb_hash_aref(env, IODINE_R_HIJACK_IO); - return io; - } - // mark update - set_handle(self, NULL); - // hijack the IO object - intptr_t uuid = http_hijack(h, NULL); -#ifdef __MINGW32__ - int osffd = fio_osffd4fd(fio_uuid2fd(uuid)); - if (osffd == -1) - return Qfalse; - VALUE fd = INT2FIX(osffd); -#else - VALUE fd = INT2FIX(fio_uuid2fd(uuid)); -#endif - // VALUE new_io = how the fuck do we create a new IO from the fd? - VALUE new_io = IodineCaller.call2(TCPSOCKET_CLASS, for_fd_id, 1, - &fd); // TCPSocket.for_fd(fd) ... cool... - rb_hash_aset(env, IODINE_R_HIJACK_IO, new_io); - if (argc) - rb_hash_aset(env, IODINE_R_HIJACK_CB, *argv); - return new_io; -} - -/* ***************************************************************************** -C land API -*/ - -// new object -static VALUE new_rack_io(http_s *h, VALUE env) { - VALUE rack_io = rb_funcall2(rRackIO, iodine_new_func_id, 0, NULL); - rb_ivar_set(rack_io, io_id, ULL2NUM(h->body)); - set_handle(rack_io, h); - rb_ivar_set(rack_io, env_id, env); - rb_hash_aset(env, IODINE_R_INPUT, rack_io); - rb_hash_aset(env, IODINE_R_HIJACK, rb_obj_method(rack_io, hijack_func_sym)); - return rack_io; -} - -static void close_rack_io(VALUE rack_io) { - // rio_close(rack_io); - rb_ivar_set(rack_io, io_id, INT2NUM(0)); - set_handle(rack_io, NULL); /* this disables hijacking. */ -} - -// initialize library -static void init_rack_io(void) { - IodineUTF8Encoding = rb_enc_find("UTF-8"); - IodineBinaryEncoding = rb_enc_find("binary"); - rRackIO = rb_define_class_under(IodineBaseModule, "RackIO", rb_cObject); - - io_id = rb_intern("rack_io"); - env_id = rb_intern("env"); - for_fd_id = rb_intern("for_fd"); - iodine_fd_var_id = rb_intern("fd"); - iodine_new_func_id = rb_intern("new"); -#ifdef __MINGW32__ - iodine_osffd_id = rb_intern("osffd"); -#endif - hijack_func_sym = ID2SYM(rb_intern("_hijack")); - - TCPSOCKET_CLASS = rb_const_get(rb_cObject, rb_intern("TCPSocket")); - // IO methods - - rb_define_method(rRackIO, "rewind", rio_rewind, 0); - rb_define_method(rRackIO, "gets", rio_gets, 0); - rb_define_method(rRackIO, "read", rio_read, -1); - rb_define_method(rRackIO, "close", rio_close, 0); - rb_define_method(rRackIO, "each", rio_each, 0); - rb_define_method(rRackIO, "_hijack", rio_get_io, -1); -} - -//////////////////////////////////////////////////////////////////////////// -// the API interface -struct IodineRackIO IodineRackIO = { - .create = new_rack_io, - .close = close_rack_io, - .init = init_rack_io, -}; diff --git a/ext/iodine/iodine_rack_io.h b/ext/iodine/iodine_rack_io.h deleted file mode 100644 index 36aeefa8..00000000 --- a/ext/iodine/iodine_rack_io.h +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2017 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef RUBY_RACK_IO_H -#define RUBY_RACK_IO_H - -#include - -#include "http.h" - -extern struct IodineRackIO { - VALUE (*create)(http_s *h, VALUE env); - void (*close)(VALUE rack_io); - void (*init)(void); -} IodineRackIO; - -#endif /* RUBY_RACK_IO_H */ diff --git a/ext/iodine/iodine_store.c b/ext/iodine/iodine_store.c deleted file mode 100644 index 495e08fb..00000000 --- a/ext/iodine/iodine_store.c +++ /dev/null @@ -1,142 +0,0 @@ -#include "iodine.h" - -#include "iodine_store.h" - -#include -#include - -#define FIO_SET_NAME fio_store -#define FIO_SET_OBJ_TYPE uintptr_t -#include - -static fio_lock_i iodine_storage_lock = FIO_LOCK_INIT; -static fio_store_s iodine_storage = FIO_SET_INIT; -static size_t iodine_storage_count_max = 0; - -#ifndef IODINE_DEBUG -#define IODINE_DEBUG 0 -#endif - -/* ***************************************************************************** -API -***************************************************************************** */ - -/** Adds an object to the storage (or increases it's reference count). */ -static VALUE storage_add(VALUE obj) { - if (!obj || obj == Qnil || obj == Qtrue || obj == Qfalse) - return obj; - uintptr_t old = 0; - fio_lock(&iodine_storage_lock); - fio_store_overwrite(&iodine_storage, obj, 1, &old); - if (old) - fio_store_overwrite(&iodine_storage, obj, old + 1, NULL); - if (iodine_storage_count_max < fio_store_count(&iodine_storage)) - iodine_storage_count_max = fio_store_count(&iodine_storage); - fio_unlock(&iodine_storage_lock); - return obj; -} -/** Removes an object from the storage (or decreases it's reference count). */ -static VALUE storage_remove(VALUE obj) { - if (!obj || obj == Qnil || obj == Qtrue || obj == Qfalse || - iodine_storage.count == 0) - return obj; - fio_lock(&iodine_storage_lock); - uintptr_t old = 0; - fio_store_remove(&iodine_storage, obj, 0, &old); - if (old > 1) - fio_store_overwrite(&iodine_storage, obj, old - 1, NULL); - fio_unlock(&iodine_storage_lock); - return obj; -} -/** Should be called after forking to reset locks */ -static void storage_after_fork(void) { iodine_storage_lock = FIO_LOCK_INIT; } - -/** Prints debugging information to the console. */ -static void storage_print(void) { - FIO_LOG_DEBUG("Ruby <=> C Memory storage stats (pid: %d):\n", getpid()); - fio_lock(&iodine_storage_lock); - uintptr_t index = 0; - FIO_SET_FOR_LOOP(&iodine_storage, pos) { - if (pos->obj) { - fprintf(stderr, "[%" PRIuPTR "] => %" PRIuPTR " X obj %p type %d\n", - index++, pos->obj, (void *)pos->hash, TYPE(pos->hash)); - } - } - fprintf(stderr, "Total of %" PRIuPTR " objects protected form GC\n", index); - fprintf(stderr, - "Storage uses %" PRIuPTR " Hash bins for %" PRIuPTR " objects\n" - "The largest collection was %zu objects.\n", - iodine_storage.capa, iodine_storage.count, iodine_storage_count_max); - fio_unlock(&iodine_storage_lock); -} - -/** - * Used for debugging purposes (when testing iodine for Ruby object "leaks"). - */ -static VALUE storage_print_rb(VALUE self) { - storage_print(); - return Qnil; - (void)self; -} -/* ***************************************************************************** -GC protection -***************************************************************************** */ - -/* a callback for the GC (marking active objects) */ -static void storage_mark(void *ignore) { - (void)ignore; - if (FIO_LOG_LEVEL >= FIO_LOG_LEVEL_DEBUG) - storage_print(); - fio_lock(&iodine_storage_lock); - // fio_store_compact(&iodine_storage); - FIO_SET_FOR_LOOP(&iodine_storage, pos) { - if (pos->obj) { - rb_gc_mark((VALUE)pos->hash); - } - } - fio_unlock(&iodine_storage_lock); -} - -/* clear the registry (end of lifetime) */ -static void storage_clear(void *ignore) { - (void)ignore; - FIO_LOG_DEBUG("Ruby<=>C Storage cleared.\n"); - fio_lock(&iodine_storage_lock); - fio_store_free(&iodine_storage); - iodine_storage = (fio_store_s)FIO_SET_INIT; - fio_unlock(&iodine_storage_lock); -} - -/* -the data-type used to identify the registry -this sets the callbacks. -*/ -static const struct rb_data_type_struct storage_type_struct = { - .wrap_struct_name = "RubyReferencesIn_C_Land", - .function.dfree = (void (*)(void *))storage_clear, - .function.dmark = (void (*)(void *))storage_mark, -}; - -/* ***************************************************************************** -Initialization -***************************************************************************** */ - -struct IodineStorage_s IodineStore = { - .add = storage_add, - .remove = storage_remove, - .after_fork = storage_after_fork, - .print = storage_print, -}; - -/** Initializes the storage unit for first use. */ -void iodine_storage_init(void) { - fio_store_capa_require(&iodine_storage, 512); - VALUE tmp = - rb_define_class_under(rb_cObject, "IodineObjectStorage", rb_cObject); - VALUE storage_obj = - TypedData_Wrap_Struct(tmp, &storage_type_struct, &iodine_storage); - // rb_global_variable(&iodine_storage_obj); - rb_ivar_set(IodineModule, rb_intern2("storage", 7), storage_obj); - rb_define_module_function(IodineBaseModule, "db_print_protected_objects", - storage_print_rb, 0); -} diff --git a/ext/iodine/iodine_store.h b/ext/iodine/iodine_store.h index 0f83b554..37bcec62 100644 --- a/ext/iodine/iodine_store.h +++ b/ext/iodine/iodine_store.h @@ -1,20 +1,326 @@ -#ifndef H_IODINE_STORAGE_H -#define H_IODINE_STORAGE_H +#if !defined(H___IODINE_STORE___H) +#define H___IODINE_STORE___H +#if !defined(IODINE_STORE_SKIP_PRINT) +#include "iodine.h" +#endif +/* ***************************************************************************** +Ruby Object Storage (GC management) -#include "ruby.h" +Use: -extern struct IodineStorage_s { - /** Adds an object to the storage (or increases it's reference count). */ - VALUE (*add)(VALUE); - /** Removes an object from the storage (or decreases it's reference count). */ - VALUE (*remove)(VALUE); - /** Should be called after forking to reset locks */ - void (*after_fork)(void); - /** Prints debugging information to the console. */ - void (*print)(void); -} IodineStore; +// Adds a Ruby Object from the store, holding it against GC cleanup +STORE.hold(VALUE o); -/** Initializes the storage unit for first use. */ -void iodine_storage_init(void); +// Removed a Ruby Object from the store, releasing it's GC hold +STORE.release(VALUE o); -#endif +// Performs callback during every GC cycle +STORE.on_gc(void (*fn)(void *), void *arg); + +// Returns a frozen String object (from cache, if exists) +VALUE str = STORE.frozen_str(fio_buf_info_s cstr); + +// Returns a frozen Header String object (from cache, if exists) +VALUE str = STORE.header_name(fio_buf_info_s header_name); + + +***************************************************************************** */ + +/* ***************************************************************************** +Ruby Garbage Collection Protection Object +***************************************************************************** */ + +typedef struct { + void (*fn)(void *); + void *arg; +} store___task_s; + +#define FIO_ARRAY_NAME store___todo +#define FIO_ARRAY_TYPE store___task_s +#define FIO_ARRAY_TYPE_CMP(a, b) ((a).fn == (b).fn && (a).arg == (b).arg) +#define FIO_ARRAY_EXPONENTIAL 1 +#define FIO_MAP_NAME iodine_reference_store_map +#define FIO_MAP_KEY VALUE +#define FIO_MAP_VALUE size_t +#define FIO_MAP_HASH_FN(o) fio_risky_ptr((void *)(o)) +#define FIO_THREADS +#include FIO_INCLUDE_FILE + +#define FIO_MAP_KEY_KSTR +#define FIO_MAP_HASH_FN(o) \ + fio_risky_hash((o).buf, (o).len, (uint64_t)&iodine_rb_IODINE); +#define FIO_MAP_NAME iodine_reference_store_frzn +#define FIO_MAP_VALUE VALUE +#include FIO_INCLUDE_FILE + +FIO_SFUNC void iodine_store___hold(VALUE o); +FIO_SFUNC void iodine_store___release(VALUE o); +FIO_SFUNC void iodine_store___on_gc(void (*fn)(void *), void *arg); +FIO_SFUNC VALUE iodine_store___frozen_str(fio_str_info_s n); +FIO_SFUNC VALUE iodine_store___header_name(fio_str_info_s n); + +static struct value_reference_counter_store_s { + iodine_reference_store_map_s map; + iodine_reference_store_frzn_s frozen; + iodine_reference_store_frzn_s headers; + store___todo_s todo; + size_t limit; + fio_thread_mutex_t lock; + /** Adds a VALUE to the store, protecting it from the GC. */ + void (*hold)(VALUE); + /** Removed a VALUE to the store, if it's `hold` count drops to zero. */ + void (*release)(VALUE); + /** Adds a task to be performed during the next GC cycle. */ + void (*on_gc)(void (*fn)(void *), void *arg); + /** Returns a frozen String, possibly cached. */ + VALUE (*frozen_str)(fio_str_info_s); + /** Returns a frozen String header name (`HTTP_` + uppercase). */ + VALUE (*header_name)(fio_str_info_s); +} STORE = { + FIO_MAP_INIT, + FIO_MAP_INIT, + FIO_MAP_INIT, + FIO_ARRAY_INIT, + 228, + FIO_THREAD_MUTEX_INIT, + iodine_store___hold, + iodine_store___release, + iodine_store___on_gc, + iodine_store___frozen_str, + iodine_store___header_name, +}; + +/** + * Prints the number of object withheld from the GC (for debugging). + * + * Iodine::Base.print_debug + */ +FIO_SFUNC VALUE iodine_store___print_debug(VALUE self) { +#ifndef IODINE_STORE_SKIP_PRINT + fprintf(stderr, + "DEBUG:\tRuby Objects Held: %-4zu (%-4zu current capacity)\n" + " \tCached Frozen Strings: %-4zu/%-4zu (%-4zu capacity)\n" + " \tCached Rack Headers: %-4zu/%-4zu (%-4zu capacity)\n" + " \tTasks to do: %-4zu\n" + " \tIodine Objects Allocated:\n" + " \tConnections: %-4zu\tMiniMaps: %-4zu\n" + " \tMustache: %-4zu\tPubSubMessages: %-4zu\n" + " \tfacil.io Objects Allocated:\n" + " \tHTTP Handles: %-4zu\tfio_bstr_s: %-4zu\n" + " \tIO Objects: %-4zu\n", + (size_t)iodine_reference_store_map_count(&STORE.map), + (size_t)iodine_reference_store_map_capa(&STORE.map), + (size_t)iodine_reference_store_frzn_count(&STORE.frozen), + STORE.limit, + (size_t)iodine_reference_store_frzn_capa(&STORE.frozen), + (size_t)iodine_reference_store_frzn_count(&STORE.headers), + STORE.limit, + (size_t)iodine_reference_store_frzn_capa(&STORE.headers), + (size_t)store___todo_count(&STORE.todo), + FIO_LEAK_COUNTER_COUNT(iodine_connection), + FIO_LEAK_COUNTER_COUNT(iodine_minimap), + FIO_LEAK_COUNTER_COUNT(iodine_mustache), + FIO_LEAK_COUNTER_COUNT(iodine_pubsub_msg), + FIO_LEAK_COUNTER_COUNT(fio_http), + FIO_LEAK_COUNTER_COUNT(fio_bstr_s), + FIO_LEAK_COUNTER_COUNT(fio___io)); + // fio_state_callback_print_state(); +#endif /* IODINE_STORE_SKIP_PRINT */ + return self; +} + +/** Sets Iodine's cache limit for frozen strings, limited to 65,535 items. */ +FIO_SFUNC VALUE iodine_store___cache_limit_set(VALUE self, VALUE nlim) { + Check_Type(nlim, T_FIXNUM); + unsigned long long l = NUM2ULL(nlim); + if (l > 65536) + l = 65536; + STORE.limit = l; + return ULL2NUM(l); +} +/** Gets Iodine's cache limit for frozen strings. */ +FIO_SFUNC VALUE iodine_store___cache_limit_get(VALUE self) { + return ULL2NUM((unsigned long long)STORE.limit); +} + +FIO_IFUNC void store___todo_perform_tasks_unsafe( + struct value_reference_counter_store_s *s) { + store___task_s tsk = {NULL}; + while (!store___todo_pop(&s->todo, &tsk)) + tsk.fn(tsk.arg); +} + +FIO_IFUNC void iodine_store___hold_unsafe(VALUE o) { + iodine_reference_store_map_node_s *n = + iodine_reference_store_map_get_ptr(&STORE.map, o); + if (!n) { + iodine_reference_store_map_set(&STORE.map, o, 1, NULL); + } else { + fio_atomic_add(&n->value, 1); + } +} + +FIO_SFUNC void iodine_store___hold(VALUE o) { + if (IODINE_STORE_IS_SKIP(o)) + return; + fio_thread_mutex_lock(&STORE.lock); + iodine_store___hold_unsafe(o); + fio_thread_mutex_unlock(&STORE.lock); +} +FIO_SFUNC void iodine_store___release(VALUE o) { + if (IODINE_STORE_IS_SKIP(o) || !iodine_reference_store_map_count(&STORE.map)) + return; + fio_thread_mutex_lock(&STORE.lock); + iodine_reference_store_map_node_s *n = + iodine_reference_store_map_get_ptr(&STORE.map, o); + if (n && !fio_atomic_sub_fetch(&n->value, 1)) { + iodine_reference_store_map_remove(&STORE.map, o, NULL); + } + fio_thread_mutex_unlock(&STORE.lock); +} + +FIO_SFUNC VALUE iodine_store___frozen_str(fio_str_info_s n) { + VALUE r; + fio_thread_mutex_lock(&STORE.lock); + r = iodine_reference_store_frzn_get(&STORE.frozen, n); + fio_thread_mutex_unlock(&STORE.lock); + if (r) + return r; + + r = rb_str_new(n.buf, n.len); /* might invoke GC, can't be in a lock */ + rb_str_freeze(r); + if (iodine_reference_store_frzn_count(&STORE.frozen) < STORE.limit) { + fio_thread_mutex_lock(&STORE.lock); + iodine_reference_store_frzn_set(&STORE.frozen, n, r, NULL); + fio_thread_mutex_unlock(&STORE.lock); + } + return r; +} + +FIO_SFUNC VALUE iodine_store___header_name(fio_str_info_s n) { + VALUE r; + size_t offset; + if (!n.len || n.len > 1023) + return Qnil; + FIO_STR_INFO_TMP_VAR(buffer, 1152); + /* CONTENT_LENGTH and CONTENT_TYPE should be copied without HTTP_ */ + const uint64_t content_length1 = fio_buf2u64u("content-"); + const uint64_t content_length2 = fio_buf2u64u("t-length"); + const uint64_t content_type1 = fio_buf2u64u("content-"); + const uint32_t content_type2 = fio_buf2u32u("type"); + + fio_thread_mutex_lock(&STORE.lock); + r = iodine_reference_store_frzn_get(&STORE.headers, n); + fio_thread_mutex_unlock(&STORE.lock); + if (r) + return r; + + offset = 5 * !((n.len == 14 && fio_buf2u64u(n.buf) == content_length1 && + fio_buf2u64u(n.buf + 6) == content_length2) || + (n.len == 12 && fio_buf2u64u(n.buf) == content_type1 && + fio_buf2u32u(n.buf + 8) == content_type2)); + fio_string_write2(&buffer, + NULL, + FIO_STRING_WRITE_STR2("HTTP_", offset), + FIO_STRING_WRITE_STR_INFO(n)); + for (size_t i = offset; i < buffer.len; ++i) { + if (buffer.buf[i] >= 'a' && buffer.buf[i] <= 'z') + buffer.buf[i] = buffer.buf[i] ^ 32; + else if (buffer.buf[i] == '-') + buffer.buf[i] = '_'; + } + r = rb_str_new(buffer.buf, buffer.len); + rb_str_freeze(r); + if (iodine_reference_store_frzn_count(&STORE.headers) < STORE.limit) { + fio_thread_mutex_lock(&STORE.lock); + iodine_reference_store_frzn_set(&STORE.headers, n, r, NULL); + fio_thread_mutex_unlock(&STORE.lock); + } + return r; +} + +FIO_SFUNC void iodine_store___on_gc(void (*fn)(void *), void *arg) { + if (!fn && !arg) + return; + fio_thread_mutex_lock(&STORE.lock); + store___todo_push(&STORE.todo, (store___task_s){.fn = fn, .arg = arg}); + fio_thread_mutex_unlock(&STORE.lock); +} + +FIO_SFUNC int iodine_store___gc_mark__key( + iodine_reference_store_map_each_s *e) { + rb_gc_mark(e->key); + return 0; +} +FIO_SFUNC int iodine_store___gc_mark__val( + iodine_reference_store_frzn_each_s *e) { + rb_gc_mark(e->value); + return 0; +} + +FIO_SFUNC void iodine_store___gc_mark( + struct value_reference_counter_store_s *s) { + if (!iodine_reference_store_map_count(&s->map) && + !store___todo_count(&s->todo)) + return; + // we can skip the lock, as the GC freezes all other actions + // fio_thread_mutex_lock(&s->lock); + store___todo_perform_tasks_unsafe(s); + iodine_reference_store_map_each(&s->map, + iodine_store___gc_mark__key, + NULL, + 0); + iodine_reference_store_frzn_each(&s->frozen, + iodine_store___gc_mark__val, + NULL, + 0); + iodine_reference_store_frzn_each(&s->headers, + iodine_store___gc_mark__val, + NULL, + 0); + // we can skip the lock, as the GC freezes all other actions + // fio_thread_mutex_unlock(&s->lock); + if (FIO_LOG_LEVEL >= FIO_LOG_LEVEL_DEBUG) + iodine_store___print_debug(Qnil); +} +FIO_SFUNC void value_reference_counter_store_destroy( + struct value_reference_counter_store_s *s) { + fio_thread_mutex_destroy(&s->lock); + store___todo_perform_tasks_unsafe(s); + iodine_reference_store_map_destroy(&s->map); + iodine_reference_store_frzn_destroy(&s->frozen); + iodine_reference_store_frzn_destroy(&s->headers); + store___todo_destroy(&s->todo); +} + +static void iodine_setup_value_reference_counter(VALUE klass) { + static const struct rb_data_type_struct storage_type_struct = { + .wrap_struct_name = "CRubyReferenceStore", + .function.dfree = (void (*)(void *))value_reference_counter_store_destroy, + .function.dmark = (void (*)(void *))iodine_store___gc_mark, + }; + static VALUE keep_alive = 0; + if (keep_alive) + return; + /** Container Class for Ruby-C bridge (GC protected objects). */ + rb_define_singleton_method(klass, + "print_debug", + iodine_store___print_debug, + 0); + rb_define_singleton_method(klass, + "cache_limit=", + iodine_store___cache_limit_set, + 1); + rb_define_singleton_method(klass, + "cache_limit", + iodine_store___cache_limit_get, + 0); + keep_alive = TypedData_Wrap_Struct(klass, &storage_type_struct, &STORE); + rb_global_variable(&keep_alive); + fio_state_callback_add( + FIO_CALL_AT_EXIT, + (void (*)(void *))value_reference_counter_store_destroy, + (void *)&STORE); +} + +#endif /* H___IODINE_STORE___H */ diff --git a/ext/iodine/iodine_tcp.c b/ext/iodine/iodine_tcp.c deleted file mode 100644 index 49d2711c..00000000 --- a/ext/iodine/iodine_tcp.c +++ /dev/null @@ -1,346 +0,0 @@ -#include "iodine.h" -#include -#include - -#define FIO_INCLUDE_STR -#include "fio.h" - -/* ***************************************************************************** -Static stuff -***************************************************************************** */ -static ID call_id; -static ID on_closed_id; -static rb_encoding *IodineBinaryEncoding; - -static VALUE port_id; -static VALUE address_id; -static VALUE handler_id; -static VALUE timeout_id; - -/* ***************************************************************************** -Raw TCP/IP Protocol -***************************************************************************** */ - -#define IODINE_MAX_READ 8192 - -typedef struct { - fio_protocol_s p; - VALUE io; -} iodine_protocol_s; - -typedef struct { - VALUE io; - ssize_t len; - char buffer[IODINE_MAX_READ]; -} iodine_buffer_s; - -/** - * Converts an iodine_buffer_s pointer to a Ruby string. - */ -static void *iodine_tcp_on_data_in_GIL(void *b_) { - iodine_buffer_s *b = b_; - if (!b) { - FIO_LOG_FATAL("(iodine->tcp/ip->on_data->GIL) WTF?!\n"); - exit(-1); - } - VALUE data = IodineStore.add(rb_str_new(b->buffer, b->len)); - rb_enc_associate(data, IodineBinaryEncoding); - iodine_connection_fire_event(b->io, IODINE_CONNECTION_ON_MESSAGE, data); - IodineStore.remove(data); - return NULL; - // return (void *)IodineStore.add(rb_usascii_str_new((const char *)b->buffer, - // b->len)); -} - -/** Called when a data is available, but will not run concurrently */ -static void iodine_tcp_on_data(intptr_t uuid, fio_protocol_s *protocol) { - iodine_buffer_s buffer; - buffer.len = fio_read(uuid, buffer.buffer, IODINE_MAX_READ); - if (buffer.len <= 0) { - return; - } - buffer.io = ((iodine_protocol_s *)protocol)->io; - IodineCaller.enterGVL(iodine_tcp_on_data_in_GIL, &buffer); - if (buffer.len == IODINE_MAX_READ) { - fio_force_event(uuid, FIO_EVENT_ON_DATA); - } -} - -/** called when the socket is ready to be written to. */ -static void iodine_tcp_on_ready(intptr_t uuid, fio_protocol_s *protocol) { - iodine_protocol_s *p = (iodine_protocol_s *)protocol; - iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_DRAINED, Qnil); - (void)uuid; -} - -/** - * Called when the server is shutting down, immediately before closing the - * connection. - */ -static uint8_t iodine_tcp_on_shutdown(intptr_t uuid, fio_protocol_s *protocol) { - iodine_protocol_s *p = (iodine_protocol_s *)protocol; - iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_SHUTDOWN, Qnil); - return 0; - (void)uuid; -} - -/** Called when the connection was closed, but will not run concurrently */ - -static void iodine_tcp_on_close(intptr_t uuid, fio_protocol_s *protocol) { - iodine_protocol_s *p = (iodine_protocol_s *)protocol; - iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_CLOSE, Qnil); - free(p); - (void)uuid; -} - -/** called when a connection's timeout was reached */ -static void iodine_tcp_ping(intptr_t uuid, fio_protocol_s *protocol) { - iodine_protocol_s *p = (iodine_protocol_s *)protocol; - iodine_connection_fire_event(p->io, IODINE_CONNECTION_PING, Qnil); - (void)uuid; -} - -/** fio_listen callback, called when a connection opens */ -static void iodine_tcp_on_open(intptr_t uuid, void *udata) { - if (!fio_is_valid(uuid)) - return; - VALUE handler = IodineCaller.call((VALUE)udata, call_id); - IodineStore.add(handler); - iodine_tcp_attch_uuid(uuid, handler); - IodineStore.remove(handler); -} - -/** called when the listening socket is destroyed */ -static void iodine_tcp_on_finish(intptr_t uuid, void *udata) { - IodineStore.remove((VALUE)udata); - (void)uuid; -} - -/** - * The `on_connect` callback should either call `fio_attach` or close the - * connection. - */ -static void iodine_tcp_on_connect(intptr_t uuid, void *udata) { - iodine_tcp_attch_uuid(uuid, (VALUE)udata); - IodineStore.remove((VALUE)udata); -} - -/** - * The `on_fail` is called when a socket fails to connect. The old sock UUID - * is passed along. - */ -static void iodine_tcp_on_fail(intptr_t uuid, void *udata) { - IodineStore.remove((VALUE)udata); -} - -/* ***************************************************************************** -The Ruby API implementation -***************************************************************************** */ - -// clang-format off -/** -The {listen} method instructs iodine to listen to incoming connections using either TCP/IP or Unix sockets. - -The method accepts a single Hash argument with the following optional keys: - -:port :: The port to listen to, deafults to nil (using a Unix socket) -:address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant). -:handler :: An object that answers the `call` method (i.e., a Proc). - -The method also accepts an optional block. - -Either a block or the :handler key MUST be present. - -The handler Proc (or object) should return a connection callback object that supports the following callbacks (see also {Iodine::Connection}): - -on_open(client) :: called after a connection was established -on_message(client, data) :: called when incoming data is available. Data may be fragmented. -on_drained(client) :: called when all the pending `client.write` events have been processed (see {Iodine::Connection#pending}). -ping(client) :: called whenever a timeout has occured (see {Iodine::Connection#timeout=}). -on_shutdown(client) :: called if the server is shutting down. This is called before the connection is closed. -on_close(client) :: called when the connection with the client was closed. - -The `client` argument is an {Iodine::Connection} instance that represents the connection / the client. - -Here's a telnet based chat-room example: - - require 'iodine' - # define the protocol for our service - module ChatHandler - def self.on_open(client) - # Set a connection timeout - client.timeout = 10 - # subscribe to the chat channel. - client.subscribe :chat - # Write a welcome message - client.publish :chat, "new member entered the chat\r\n" - end - # this is called for incoming data - note data might be fragmented. - def self.on_message(client, data) - # publish the data we received - client.publish :chat, data - # close the connection when the time comes - client.close if data =~ /^bye[\n\r]/ - end - # called whenever timeout occurs. - def self.ping(client) - client.write "System: quite, isn't it...?\r\n" - end - # called if the connection is still open and the server is shutting down. - def self.on_shutdown(client) - # write the data we received - client.write "Chat server going away. Try again later.\r\n" - end - # returns the callback object (self). - def self.call - self - end - end - # we use can both the `handler` keyword or a block, anything that answers #call. - Iodine.listen(port: "3000", handler: ChatHandler) - # start the service - Iodine.threads = 1 - Iodine.start - - - -Returns the handler object used. -*/ -intptr_t iodine_tcp_listen(iodine_connection_args_s args) { - // clang-format on - IodineStore.add(args.handler); -#ifdef __MINGW32__ - return fio_listen(.port = args.port.data, .address = args.address.data, - .on_open = iodine_tcp_on_open, - .on_finish = iodine_tcp_on_finish, - .udata = (void *)args.handler); -#else - return fio_listen(.port = args.port.data, .address = args.address.data, - .on_open = iodine_tcp_on_open, - .on_finish = iodine_tcp_on_finish, .tls = args.tls, - .udata = (void *)args.handler); -#endif -} - -// clang-format off -/** -The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets. - -The method accepts a single Hash argument with the following optional keys: - -:port :: The port to listen to, deafults to 0 (using a Unix socket) -:address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant). -:handler :: A connection callback object that supports the following same callbacks listen in the {listen} method's documentation. -:timeout :: An integer timeout for connection establishment (doen't effect the new connection's timeout. Should be in the rand of 0..255. -:tls :: An {Iodine::TLS} object (optional) for secure connections. - -The method also accepts an optional block. - -Either a block or the :handler key MUST be present. - -If the connection fails, only the `on_close` callback will be called (with a `nil` client). - -Returns the handler object used. -*/ -intptr_t iodine_tcp_connect(iodine_connection_args_s args){ - // clang-format on - IodineStore.add(args.handler); -#ifdef __MINGW32__ - return fio_connect(.port = args.port.data, .address = args.address.data, - .on_connect = iodine_tcp_on_connect, - .on_fail = iodine_tcp_on_fail, .timeout = args.ping, - .udata = (void *)args.handler); -#else - return fio_connect(.port = args.port.data, .address = args.address.data, - .on_connect = iodine_tcp_on_connect, .tls = args.tls, - .on_fail = iodine_tcp_on_fail, .timeout = args.ping, - .udata = (void *)args.handler); -#endif -} - -// clang-format off -/** -The {attach_fd} method instructs iodine to attach a socket to the server using it's numerical file descriptor. - -This is faster than attaching a Ruby IO object since it allows iodine to directly call the system's read/write methods. However, this doesn't support TLS/SSL connections. - -This method requires two objects, a file descriptor (`fd`) and a callback object. - -See {listen} for details about the callback object. - -Returns the callback object (handler) used. -*/ -static VALUE iodine_tcp_attach_fd(VALUE self, VALUE fd, VALUE handler) { - // clang-format on - Check_Type(fd, T_FIXNUM); - if (handler == Qnil || handler == Qfalse || handler == Qtrue) { - rb_raise(rb_eArgError, "A callback object must be provided."); - } - IodineStore.add(handler); - int other = dup(NUM2INT(fd)); - if (other == -1) { - rb_raise(rb_eIOError, "invalid fd."); - } - intptr_t uuid = fio_fd2uuid(other); - iodine_tcp_attch_uuid(uuid, handler); - IodineStore.remove(handler); - return handler; - (void)self; -} - -/* ***************************************************************************** -Add the Ruby API methods to the Iodine object -***************************************************************************** */ - -void iodine_init_tcp_connections(void) { - call_id = rb_intern2("call", 4); - port_id = IodineStore.add(rb_id2sym(rb_intern("port"))); - address_id = IodineStore.add(rb_id2sym(rb_intern("address"))); - handler_id = IodineStore.add(rb_id2sym(rb_intern("handler"))); - timeout_id = IodineStore.add(rb_id2sym(rb_intern("timeout"))); - on_closed_id = rb_intern("on_closed"); - - IodineBinaryEncoding = rb_enc_find("binary"); - - rb_define_module_function(IodineModule, "attach_fd", iodine_tcp_attach_fd, 2); -} - -/* ***************************************************************************** -Allow uuid attachment -***************************************************************************** */ - -/** assigns a protocol and IO object to a handler */ -void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) { - FIO_LOG_DEBUG("Iodine attaching handler %p to uuid %p", (void *)handler, - (void *)uuid); - if (handler == Qnil || handler == Qfalse || handler == Qtrue) { - fio_close(uuid); - return; - } - /* temporary, in case `iodine_connection_new` invokes the GC */ - iodine_protocol_s *p = malloc(sizeof(*p)); - FIO_ASSERT_ALLOC(p); - *p = (iodine_protocol_s){ - .p = - { - .on_data = iodine_tcp_on_data, - .on_ready = NULL /* set only after the on_open callback */, - .on_shutdown = iodine_tcp_on_shutdown, - .on_close = iodine_tcp_on_close, - .ping = iodine_tcp_ping, - }, - .io = iodine_connection_new(.type = IODINE_CONNECTION_RAW, .uuid = uuid, - .arg = p, .handler = handler), - }; - /* clear away (remember the connection object manages these concerns) */ - fio_attach(uuid, &p->p); - if (fio_is_valid(uuid)) { - iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil); - p->p.on_ready = iodine_tcp_on_ready; - fio_force_event(uuid, FIO_EVENT_ON_READY); - } else { - FIO_LOG_DEBUG( - "Iodine couldn't attach handler %p to uuid %p - invalid uuid.", - (void *)handler, (void *)uuid); - } -} diff --git a/ext/iodine/iodine_tcp.h b/ext/iodine/iodine_tcp.h deleted file mode 100644 index 5c785f92..00000000 --- a/ext/iodine/iodine_tcp.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef H_IODINE_RAW_TCP_IP_H -#define H_IODINE_RAW_TCP_IP_H - -#include "ruby.h" - -#include "iodine.h" - -void iodine_init_tcp_connections(void); -void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler); -intptr_t iodine_tcp_listen(iodine_connection_args_s args); -intptr_t iodine_tcp_connect(iodine_connection_args_s args); - -#endif diff --git a/ext/iodine/iodine_threads.h b/ext/iodine/iodine_threads.h new file mode 100644 index 00000000..45affdd1 --- /dev/null +++ b/ext/iodine/iodine_threads.h @@ -0,0 +1,122 @@ +#ifndef H___IODINE_THREADS___H +#include "iodine.h" +/* ***************************************************************************** +API for forking processes +***************************************************************************** */ + +/** Should behave the same as the POSIX system call `fork`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_fork(void) { + iodine_caller_result_s r = + iodine_ruby_call_outside(rb_mProcess, rb_intern2("fork", 4), 0, NULL); + if (r.exception) + return -1; + if (r.result == Qnil) + return 0; + return NUM2INT(r.result); +} + +/** Should behave the same as the POSIX system call `getpid`. */ +FIO_IFUNC fio_thread_pid_t fio_thread_getpid(void) { + return (fio_thread_pid_t)getpid(); +} + +/** Should behave the same as the POSIX system call `kill`. */ +FIO_IFUNC int fio_thread_kill(fio_thread_pid_t i, int s) { + VALUE args[] = {INT2NUM(((int)i)), INT2NUM(s)}; + iodine_caller_result_s r = + iodine_ruby_call_outside(rb_mProcess, rb_intern2("kill", 4), 2, args); + if (r.exception) + return -1; + return 0; +} + +/** Should behave the same as the POSIX system call `waitpid`. */ +FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t i, int *s, int o) { +#if FIO_OS_POSIX + return waitpid((pid_t)i, s, o); +#else + return -1; +#endif +} + +/* ***************************************************************************** +API for spawning threads +***************************************************************************** */ + +typedef struct { + fio_lock_i lock; + fio_thread_t *t; + void *(*fn)(void *); + void *arg; +} iodine___thread_starter_s; + +static VALUE iodine___thread_start_in_gvl(void *args_) { + iodine___thread_starter_s *args = (iodine___thread_starter_s *)args_; + iodine___thread_starter_s cpy = *args; + fio_unlock(&args->lock); + return (VALUE)rb_thread_call_without_gvl(cpy.fn, cpy.arg, NULL, NULL); +} +static void *iodine___thread_create_in_gvl(void *args_) { + iodine___thread_starter_s *args = (iodine___thread_starter_s *)args_; + args->t[0] = rb_thread_create(iodine___thread_start_in_gvl, args_); + if (args->t[0] == Qnil) + fio_unlock(&args->lock); + else + STORE.hold(args->t[0]); + return NULL; +} +/** Starts a new thread, returns 0 on success and -1 on failure. */ +FIO_IFUNC int fio_thread_create(fio_thread_t *t, + void *(*fn)(void *), + void *arg) { + iodine___thread_starter_s starter = {.lock = FIO_LOCK_INIT, + .t = t, + .fn = fn, + .arg = arg}; + fio_lock(&starter.lock); + rb_thread_call_with_gvl(iodine___thread_create_in_gvl, &starter); + fio_lock(&starter.lock); /* wait for other thread to unlock */ + if (*starter.t == Qnil) + return -1; + return 0; +} + +/** Waits for the thread to finish. */ +FIO_IFUNC int fio_thread_join(fio_thread_t *t) { + iodine_caller_result_s r = + iodine_ruby_call_outside(t[0], rb_intern2("join", 4), 0, NULL); + STORE.release(t[0]); + if (r.exception) + return -1; + return 0; +} + +/** Detaches the thread, so thread resources are freed automatically. */ +FIO_IFUNC int fio_thread_detach(fio_thread_t *t) { + STORE.release(t[0]); + return 0; +} + +/** Ends the current running thread. */ +FIO_IFUNC void fio_thread_exit(void) { +#if FIO_OS_POSIX + pthread_exit(NULL); +#elif FIO_OS_WIN + _endthread(); +#else + rb_thread_kill(rb_thread_current()); +#endif +} + +/* Returns non-zero if both threads refer to the same thread. */ +FIO_IFUNC int fio_thread_equal(fio_thread_t *a, fio_thread_t *b) { + return *a == *b; +} + +/** Returns the current thread. */ +FIO_IFUNC fio_thread_t fio_thread_current(void) { return rb_thread_current(); } + +/** Yields thread execution. */ +FIO_IFUNC void fio_thread_yield(void) { rb_thread_schedule(); } + +#endif /* H___IODINE_THREADS___H */ diff --git a/ext/iodine/iodine_tls.c b/ext/iodine/iodine_tls.c deleted file mode 100644 index 0c793962..00000000 --- a/ext/iodine/iodine_tls.c +++ /dev/null @@ -1,261 +0,0 @@ -#ifdef __MINGW32__ -// make pedantic compiler happy -typedef struct { - int bogus; -} bogus_s; - -#else -#include - -#include -#include -#include - -static VALUE server_name_sym = Qnil, certificate_sym = Qnil, - private_key_sym = Qnil, password_sym = Qnil; -VALUE IodineTLSClass; -/* ***************************************************************************** -C <=> Ruby Data allocation -***************************************************************************** */ - -static size_t iodine_tls_data_size(const void *c_) { - return sizeof(fio_tls_s *); - (void)c_; -} - -static void iodine_tls_data_free(void *c_) { fio_tls_destroy(c_); } - -static const rb_data_type_t iodine_tls_data_type = { - .wrap_struct_name = "IodineTLSData", - .function = - { - .dmark = NULL, - .dfree = iodine_tls_data_free, - .dsize = iodine_tls_data_size, - }, - .data = NULL, - // .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -/* Iodine::PubSub::Engine.allocate */ -static VALUE iodine_tls_data_alloc_c(VALUE klass) { - fio_tls_s *tls = fio_tls_new(NULL, NULL, NULL, NULL); - FIO_ASSERT_ALLOC(tls); - return TypedData_Wrap_Struct(klass, &iodine_tls_data_type, tls); -} - -/* ***************************************************************************** -ALPN selection callback -***************************************************************************** */ - -FIO_FUNC void iodine_tls_alpn_cb(intptr_t uuid, void *udata, void *block_) { - if (!fio_is_valid(uuid)) { - FIO_LOG_DEBUG("ALPN callback called for invalid connetion. SSL/TLS error?"); - return; - } - VALUE new_handler = IodineCaller.call((VALUE)block_, iodine_call_id); - if (!new_handler || new_handler == Qnil || new_handler == Qtrue || - new_handler == Qfalse) { - fio_close(uuid); - return; - } - - iodine_tcp_attch_uuid(uuid, new_handler); - - (void)udata; /* we can't use `udata`, since it's different in HTTP vs. TCP */ -} - -/* ***************************************************************************** -C API -***************************************************************************** */ - -fio_tls_s *iodine_tls2c(VALUE self) { - fio_tls_s *c = NULL; - if (self == Qnil || self == Qfalse) - return NULL; - TypedData_Get_Struct(self, fio_tls_s, &iodine_tls_data_type, c); - if (!c) { - rb_raise(rb_eTypeError, "Iodine::TLS error - not an Iodine::TLS object?"); - } - return c; -} - -/* ***************************************************************************** -Ruby API -***************************************************************************** */ - -/** -Assigns the TLS context a public sertificate, allowing remote parties to -validate the connection's identity. - -A self signed certificate is automatically created if the `server_name` argument -is specified and either (or both) of the `certificate` or `private_ket` -arguments are missing. - -Some implementations allow servers to have more than a single certificate, which -will be selected using the SNI extension. I believe the existing OpenSSL -implementation supports this option (untested). - - Iodine::TLS#use_certificate(server_name, - certificate = nil, - private_key = nil, - password = nil) - -Certificates and keys should be String objects leading to a PEM file. - -This method also accepts named arguments. i.e.: - - tls = Iodine::TLS.new - tls.use_certificate server_name: "example.com" - tls.use_certificate certificate: "my_cert.pem", private_key: "my_key.pem" - -Since TLS setup is crucial for security, a missing file will result in Iodine -crashing with an error message. This is expected behavior. - -*/ -static VALUE iodine_tls_use_certificate(int argc, VALUE *argv, VALUE self) { - VALUE server_name = Qnil, certificate = Qnil, private_key = Qnil, - password = Qnil; - if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) { - /* named arguments */ - server_name = rb_hash_aref(argv[0], server_name_sym); - certificate = rb_hash_aref(argv[0], certificate_sym); - private_key = rb_hash_aref(argv[0], private_key_sym); - password = rb_hash_aref(argv[0], password_sym); - } else { - /* regular arguments */ - switch (argc) { - case 4: /* overflow */ - password = argv[3]; - Check_Type(password, T_STRING); - case 3: /* overflow */ - private_key = argv[1]; - Check_Type(private_key, T_STRING); - case 2: /* overflow */ - certificate = argv[2]; - Check_Type(certificate, T_STRING); - case 1: /* overflow */ - server_name = argv[0]; - Check_Type(server_name, T_STRING); - break; - default: - rb_raise(rb_eArgError, "expecting 1..4 arguments or named arguments."); - return self; - } - } - - fio_tls_s *t = iodine_tls2c(self); - char *srvname = (server_name == Qnil ? NULL : StringValueCStr(server_name)); - char *pubcert = (certificate == Qnil ? NULL : StringValueCStr(certificate)); - char *prvkey = (private_key == Qnil ? NULL : StringValueCStr(private_key)); - char *pass = (password == Qnil ? NULL : StringValueCStr(password)); - - fio_tls_cert_add(t, srvname, pubcert, prvkey, pass); - return self; -} - -/** -Adds a certificate PEM file to the list of trusted certificates and enforces -peer verification. - -This is extremely important when using {Iodine::TLS} for client connections, -since adding the target server's - -It is enough to add the Certificate Authority's (CA) certificate, there's no -need to add each client or server certificate. - -When {trust} is used on a server TLS, only trusted clients will be allowed to -connect. - -Since TLS setup is crucial for security, a missing file will result in Iodine -crashing with an error message. This is expected behavior. -*/ -static VALUE iodine_tls_trust(VALUE self, VALUE certificate) { - Check_Type(certificate, T_STRING); - fio_tls_s *t = iodine_tls2c(self); - char *pubcert = - (certificate == Qnil ? NULL : IODINE_RSTRINFO(certificate).data); - fio_tls_trust(t, pubcert); - return self; -} - -/** -Adds an ALPN protocol callback for the named protocol, the required block must -return the handler for that protocol. - -The first protocol added will be the default protocol in cases where ALPN -failed. - -i.e.: - - tls.on_protocol("http/1.1") { HTTPConnection.new } - -When implementing TLS clients, this identifies the protocol(s) that should be -requested by the client. - -When implementing TLS servers, this identifies the protocol(s) offered by the -server. - -More than a single protocol can be set, but iodine doesn't offer, at this -moment, a way to handle these changes or to detect which protocol was selected -except by assigning a different callback per protocol. - -This is implemented using the ALPN extension to TLS. -*/ -static VALUE iodine_tls_alpn(VALUE self, VALUE protocol_name) { - Check_Type(protocol_name, T_STRING); - rb_need_block(); - fio_tls_s *t = iodine_tls2c(self); - char *prname = - (protocol_name == Qnil ? NULL : IODINE_RSTRINFO(protocol_name).data); - VALUE block = IodineStore.add(rb_block_proc()); - fio_tls_alpn_add(t, prname, iodine_tls_alpn_cb, (void *)block, - (void (*)(void *))IodineStore.remove); - return self; -} - -/** -Creates a new {Iodine::TLS} object and calles the {#use_certificate} method with -the supplied arguments. -*/ -static VALUE iodine_tls_new(int argc, VALUE *argv, VALUE self) { - if (argc) { - iodine_tls_use_certificate(argc, argv, self); - } - return self; -} - -/* ***************************************************************************** -Initialize Iodine::TLS -***************************************************************************** */ -#define IODINE_MAKE_SYM(name) \ - do { \ - name##_sym = rb_id2sym(rb_intern(#name)); \ - rb_global_variable(&name##_sym); \ - } while (0) - -void iodine_init_tls(void) { - - IODINE_MAKE_SYM(server_name); - IODINE_MAKE_SYM(certificate); - IODINE_MAKE_SYM(private_key); - IODINE_MAKE_SYM(password); - - IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cObject); - rb_define_alloc_func(IodineTLSClass, iodine_tls_data_alloc_c); - rb_define_method(IodineTLSClass, "initialize", iodine_tls_new, -1); - rb_define_method(IodineTLSClass, "use_certificate", - iodine_tls_use_certificate, -1); - rb_define_method(IodineTLSClass, "trust", iodine_tls_trust, 1); - rb_define_method(IodineTLSClass, "on_protocol", iodine_tls_alpn, 1); - -#if HAVE_OPENSSL - rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qtrue); -#elif HAVE_BEARSSL - rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse); -#else - rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse); -#endif -} -#undef IODINE_MAKE_SYM -#endif diff --git a/ext/iodine/iodine_tls.h b/ext/iodine/iodine_tls.h index 5fdfa06e..32493d00 100644 --- a/ext/iodine/iodine_tls.h +++ b/ext/iodine/iodine_tls.h @@ -1,13 +1,208 @@ -#ifndef H_IODINE_TLS_H -#define H_IODINE_TLS_H - +#ifndef H___IODINE_TLS___H +#define H___IODINE_TLS___H #include "iodine.h" +/* ***************************************************************************** +TLS Wrapper +***************************************************************************** */ + +static void iodine_tls_free(void *ptr_) { + fio_io_tls_s **p = (fio_io_tls_s **)ptr_; + fio_io_tls_free(*p); +} + +static VALUE iodine_tls_alloc(VALUE klass) { + fio_io_tls_s **tls; + VALUE o = Data_Make_Struct(klass, fio_io_tls_s *, NULL, iodine_tls_free, tls); + *tls = fio_io_tls_new(); + return o; +} + +static fio_io_tls_s *iodine_tls_get(VALUE self) { + fio_io_tls_s **p; + Data_Get_Struct(self, fio_io_tls_s *, p); + return *p; +} + +/* ***************************************************************************** +The Functions to be Wrapped +***************************************************************************** */ + +#if 0 +/** + * Adds a certificate a new SSL/TLS context / settings object (SNI support). + * + * fio_io_tls_cert_add(tls, "www.example.com", + * "public_key.pem", + * "private_key.pem", NULL ); + * + * NOTE: Except for the `tls` and `server_name` arguments, all arguments might + * be `NULL`, which a context builder (`fio_io_functions_s`) should treat as a + * request for a self-signed certificate. It may be silently ignored. + */ +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); + +/** + * Adds an ALPN protocol callback to the SSL/TLS context. + * + * The first protocol added will act as the default protocol to be selected. + * + * A `NULL` protocol name will be silently ignored. + * + * A `NULL` callback (`on_selected`) will be silently replaced with a no-op. + */ +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *tls, + const char *protocol_name, + void (*on_selected)(fio_s *)); -#include "fio_tls.h" +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *tls, + const char *protocol_name, + size_t name_length, + fio_s *); -void iodine_init_tls(void); -fio_tls_s *iodine_tls2c(VALUE self); +/** + * Adds a certificate to the "trust" list, which automatically adds a peer + * verification requirement. + * + * If `public_cert_file` is `NULL`, implementation is expected to add the + * system's default trust registry. + * + * Note: when the `fio_io_tls_s` object is used for server connections, this should + * limit connections to clients that connect using a trusted certificate. + * + * fio_io_tls_trust_add(tls, "google-ca.pem" ); + */ +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *, const char *public_cert_file); -extern VALUE IodineTLSClass; +/** + * Returns the number of `fio_io_tls_cert_add` instructions. + * + * This could be used when deciding if to add a NULL instruction (self-signed). + * + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls); +/** + * Returns the number of registered ALPN protocol names. + * + * This could be used when deciding if protocol selection should be delegated to + * the ALPN mechanism, or whether a protocol should be immediately assigned. + * + * If no ALPN protocols are registered, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls); + +/** + * Returns the number of `fio_io_tls_trust_add` instructions. + * + * This could be used when deciding if to disable peer verification or not. + * + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls); + +/** Arguments (and info) for `fio_io_tls_each`. */ +typedef struct fio_io_tls_each_s { + fio_io_tls_s *tls; + void *udata; + void *udata2; + int (*each_cert)(struct fio_io_tls_each_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); + int (*each_alpn)(struct fio_io_tls_each_s *, + const char *protocol_name, + void (*on_selected)(fio_s *)); + int (*each_trust)(struct fio_io_tls_each_s *, const char *public_cert_file); +} fio_io_tls_each_s; + +/** Calls callbacks for certificate, trust certificate and ALPN added. */ +SFUNC int fio_io_tls_each(fio_io_tls_each_s); + +/** `fio_io_tls_each` helper macro, see `fio_io_tls_each_s` for named arguments. */ +#define fio_io_tls_each(tls_, ...) \ + fio_io_tls_each(((fio_io_tls_each_s){.tls = tls_, __VA_ARGS__})) + +/** If `NULL` returns current default, otherwise sets it. */ +SFUNC fio_io_functions_s fio_io_tls_default_io_functions(fio_io_functions_s *); #endif + +/* ***************************************************************************** +Wrapper API +***************************************************************************** */ + +// clang-format off +/** +Assigns the TLS context a public certificate, allowing remote parties to +validate the connection's identity. + +A self signed certificate is automatically created if the `name` argument +is specified and either (or both) of the `cert` (public certificate) or `key` +(private key) arguments are missing. + +Some implementations allow servers to have more than a single certificate, which +will be selected using the SNI extension. I believe the existing OpenSSL +implementation supports this option (untested). + + Iodine::TLS#add_cert(name = nil, + cert = nil, + key = nil, + password = nil) + +Certificates and keys should be String objects leading to a PEM file. + +This method also accepts named arguments. i.e.: + + tls = Iodine::TLS.new + tls.add_cert name: "example.com" + tls.add_cert cert: "my_cert.pem", key: "my_key.pem" + tls.add_cert cert: "my_cert.pem", key: "my_key.pem", password: ENV['TLS_PASS'] + +Since TLS setup is crucial for security, an initialization error will result in +Iodine crashing with an error message. This is expected behavior. +*/ // clang-format on +static VALUE iodine_tls_cert_add(int argc, VALUE *argv, VALUE self) { + fio_io_tls_s *tls = iodine_tls_get(self); + fio_buf_info_s server_name = FIO_BUF_INFO1((char *)"localhost"); + fio_buf_info_s public_cert_file = FIO_BUF_INFO0; + fio_buf_info_s private_key_file = FIO_BUF_INFO0; + fio_buf_info_s pk_password = FIO_BUF_INFO0; + iodine_rb2c_arg(argc, + argv, + IODINE_ARG_BUF(server_name, 0, "name", 0), + IODINE_ARG_BUF(public_cert_file, 0, "cert", 0), + IODINE_ARG_BUF(private_key_file, 0, "key", 0), + IODINE_ARG_BUF(pk_password, 0, "password", 0)); + fio_io_tls_cert_add(tls, + server_name.buf, + public_cert_file.buf, + private_key_file.buf, + pk_password.buf); + return self; +} + +/** @deprecated use {Iodine::TLS.add_cert}. */ +static VALUE iodine_tls_cert_add_old_name(int argc, VALUE *argv, VALUE self) { + return iodine_tls_cert_add(argc, argv, self); +} + +static void Init_Iodine_TLS(void) { /** Initialize Iodine::TLS */ + /** Used to setup a TLS contexts for connections (incoming / outgoing). */ + VALUE m = rb_define_class_under(iodine_rb_IODINE, "TLS", rb_cObject); + rb_define_alloc_func(m, iodine_tls_alloc); + rb_define_method(m, "add_cert", iodine_tls_cert_add, -1); + rb_define_method(m, "use_certificate", iodine_tls_cert_add_old_name, -1); +#if HAVE_OPENSSL + rb_const_set(m, rb_intern("SUPPORTED"), Qtrue); +#else + rb_const_set(m, rb_intern("SUPPORTED"), Qfalse); +#endif +} + +#endif /* H___IODINE_TLS___H */ diff --git a/ext/iodine/iodine_utils.h b/ext/iodine/iodine_utils.h new file mode 100644 index 00000000..b681dd54 --- /dev/null +++ b/ext/iodine/iodine_utils.h @@ -0,0 +1,364 @@ +#ifndef H___IODINE_UTILS___H +#define H___IODINE_UTILS___H +#include "iodine.h" + +/* ***************************************************************************** +URL encoding Helpers +***************************************************************************** */ + +/** Decodes percent encoding, including the `%uxxxx` javascript extension and + * converting `+` to spaces. */ +FIO_IFUNC VALUE iodine_utils_encode_with_encoding( + int argc, + VALUE *argv, + VALUE self, + int (*writer)(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t len)) { + if (!argc || argc > 2) + rb_raise(rb_eArgError, "Wrong number of arguments (%d)", argc); + rb_check_type(argv[0], RUBY_T_STRING); + if (!RSTRING_LEN(argv[0])) + return argv[0]; + rb_encoding *enc = NULL; + if (argc == 2) + enc = (TYPE(argv[1]) == T_STRING) ? rb_enc_find(RSTRING_PTR(argv[1])) + : rb_enc_get(argv[1]); + if (!enc) + enc = IodineUTF8Encoding; + FIO_STR_INFO_TMP_VAR(tmp, 512); + char *org = tmp.buf; + writer(&tmp, + FIO_STRING_ALLOC_COPY, + RSTRING_PTR(argv[0]), + RSTRING_LEN(argv[0])); + self = rb_str_new(tmp.buf, tmp.len); + rb_enc_associate(self, enc); + if (org != tmp.buf) + FIO_STRING_FREE2(tmp); + return self; +} + +FIO_IFUNC VALUE +iodine_utils_encode_internal(VALUE mod, + VALUE arg, + int (*writer)(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t len)) { + rb_check_type(arg, RUBY_T_STRING); + if (!RSTRING_LEN(arg)) + return arg; + FIO_STR_INFO_TMP_VAR(tmp, 512); + const char *org = tmp.buf; + writer(&tmp, FIO_STRING_ALLOC_COPY, RSTRING_PTR(arg), RSTRING_LEN(arg)); + arg = rb_str_new(tmp.buf, tmp.len); + rb_enc_associate(arg, IodineUTF8Encoding); + if (org != tmp.buf) + FIO_STRING_FREE2(tmp); + return arg; +} +FIO_IFUNC VALUE +iodine_utils_encode1_internal(VALUE mod, + VALUE arg, + int (*writer)(fio_str_info_s *dest, + fio_string_realloc_fn reallocate, + const void *encoded, + size_t len)) { + rb_check_type(arg, RUBY_T_STRING); + if (!RSTRING_LEN(arg)) + return arg; + FIO_STR_INFO_TMP_VAR(tmp, 512); + const char *org = tmp.buf; + writer(&tmp, FIO_STRING_ALLOC_COPY, RSTRING_PTR(arg), RSTRING_LEN(arg)); + rb_str_set_len(arg, 0); + rb_str_cat(arg, tmp.buf, tmp.len); + rb_enc_associate(arg, IodineUTF8Encoding); + if (org != tmp.buf) + FIO_STRING_FREE2(tmp); + return arg; +} + +/** Encodes a String using percent encoding (i.e., URI encoding). */ +FIO_SFUNC VALUE iodine_utils_encode_url(VALUE mod, VALUE arg) { + return iodine_utils_encode_internal(mod, arg, fio_string_write_url_enc); +} + +/** Encodes a String using percent encoding (i.e., URI encoding). */ +FIO_SFUNC VALUE iodine_utils_encode_url1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, arg, fio_string_write_url_enc); +} + +/** Encodes a String using percent encoding (i.e., URI encoding). */ +FIO_SFUNC VALUE iodine_utils_encode_path(VALUE mod, VALUE arg) { + return iodine_utils_encode_internal(mod, arg, fio_string_write_url_enc); +} + +/** Encodes a String in place using percent encoding (i.e., URI encoding). */ +FIO_SFUNC VALUE iodine_utils_encode_path1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, arg, fio_string_write_url_enc); +} + +/** Decodes percent encoding, including the `%uxxxx` javascript extension and + * converting `+` to spaces. */ +FIO_SFUNC VALUE iodine_utils_decode_url(int argc, VALUE *argv, VALUE self) { + return iodine_utils_encode_with_encoding(argc, + argv, + self, + fio_string_write_url_dec); +} + +/** Decodes percent encoding in place, including the `%uxxxx` javascript + * extension and converting `+` to spaces. */ +FIO_SFUNC VALUE iodine_utils_decode_url1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, arg, fio_string_write_url_dec); +} + +/** Decodes percent encoding, including the `%uxxxx` javascript extension. */ +FIO_SFUNC VALUE iodine_utils_decode_path(int argc, VALUE *argv, VALUE self) { + return iodine_utils_encode_with_encoding(argc, + argv, + self, + fio_string_write_path_dec); +} + +/** Decodes percent encoding in place, including the `%uxxxx` javascript. */ +FIO_SFUNC VALUE iodine_utils_decode_path1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, arg, fio_string_write_path_dec); +} + +/** Escapes String using HTML escape encoding. */ +FIO_SFUNC VALUE iodine_utils_encode_html(VALUE mod, VALUE arg) { + return iodine_utils_encode_internal(mod, arg, fio_string_write_html_escape); +} + +/** + * Escapes String in place using HTML escape encoding. + * + * Note: this function significantly increases the number of escaped characters + * when compared to the native implementation. + */ +FIO_SFUNC VALUE iodine_utils_encode_html1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, arg, fio_string_write_html_escape); +} + +/** Decodes an HTML escaped String. */ +FIO_SFUNC VALUE iodine_utils_decode_html(int argc, VALUE *argv, VALUE self) { + return iodine_utils_encode_with_encoding(argc, + argv, + self, + fio_string_write_html_unescape); +} + +/** Decodes an HTML escaped String in place. */ +FIO_SFUNC VALUE iodine_utils_decode_html1(VALUE mod, VALUE arg) { + return iodine_utils_encode1_internal(mod, + arg, + fio_string_write_html_unescape); +} + +/* ***************************************************************************** +Time to String Helpers +***************************************************************************** */ + +FIO_IFUNC time_t iodine_utils_rb2time(VALUE rtm) { + rtm = rb_funcallv(rtm, rb_intern("to_i"), 0, NULL); + return FIX2LONG(rtm) ? FIX2LONG(rtm) : fio_time_real().tv_sec; +} + +/** Takes a Time object and returns a String conforming to RFC 2109. */ +FIO_SFUNC VALUE iodine_utils_rfc2109(VALUE mod, VALUE rtm) { + time_t time_requested = iodine_utils_rb2time(rtm); + VALUE str = rb_str_buf_new(34); + rb_str_set_len(str, fio_time2rfc2109(RSTRING_PTR(str), time_requested)); + rb_enc_associate(str, IodineUTF8Encoding); + return str; + (void)mod; +} +/** Takes a Time object and returns a String conforming to RFC 2822. */ +FIO_SFUNC VALUE iodine_utils_rfc2822(VALUE mod, VALUE rtm) { + time_t time_requested = iodine_utils_rb2time(rtm); + VALUE str = rb_str_buf_new(34); + rb_str_set_len(str, fio_time2rfc2822(RSTRING_PTR(str), time_requested)); + rb_enc_associate(str, IodineUTF8Encoding); + return str; + (void)mod; +} +/** Takes a Time object and returns a String conforming to RFC 7231. */ +FIO_SFUNC VALUE iodine_utils_rfc7231(VALUE mod, VALUE rtm) { + time_t time_requested = iodine_utils_rb2time(rtm); + VALUE str = rb_str_buf_new(34); + rb_str_set_len(str, fio_time2rfc7231(RSTRING_PTR(str), time_requested)); + rb_enc_associate(str, IodineUTF8Encoding); + return str; + (void)mod; +} + +/* ***************************************************************************** +String Secure Compare +***************************************************************************** */ +// clang-format off +/** +Securely compares two String objects to see if they are equal. + +Designed to be secure against timing attacks when both String objects are of the same length. + + require 'iodine' + require 'rack' + require 'benchmark' + def prove_secure_compare(name, mthd, length = 4096) + a = 0; + b = 0; + str1 = Array.new(length) { 'a' } .join; str2 = Array.new(length) { 'a' } .join; + bm = Benchmark.measure do + 1024.times do + tmp = Benchmark.measure {4096.times {mthd.call(str1, str2)}} + str1[0] = 'b' + tmp2 = Benchmark.measure {4096.times {mthd.call(str1, str2)}} + str1[0] = 'a' + tmp = tmp2.total - tmp.total + a += 1 if tmp >= 0 + b += 1 if tmp <= 0 + end + end + puts "#{name} timing ratio #{a}:#{b}\n#{bm.to_s}\n" + end + prove_secure_compare("String == (short string)", (Proc.new {|a,b| a == b } ), 47); + prove_secure_compare("Iodine::Utils.secure_compare (short string)", Iodine::Utils.method(:secure_compare), 47) + prove_secure_compare("Rack::Utils.secure_compare (short string)", Rack::Utils.method(:secure_compare), 47) + prove_secure_compare("String == (long string)", (Proc.new {|a,b| a == b } ), 1024) + prove_secure_compare("Iodine::Utils.secure_compare (long string)", Iodine::Utils.method(:secure_compare), 1024) + # prove_secure_compare("Rack::Utils.secure_compare (short string)", Rack::Utils.method(:secure_compare), 1024) # VERY slow + +*/ +FIO_SFUNC VALUE iodine_utils_is_eq(VALUE mod, VALUE a, VALUE b) { // clang-format on + rb_check_type(a, RUBY_T_STRING); + rb_check_type(b, RUBY_T_STRING); + if (RSTRING_LEN(a) != RSTRING_LEN(b)) + return RUBY_Qfalse; + return fio_ct_is_eq(RSTRING_PTR(a), RSTRING_PTR(b), RSTRING_LEN(a)) + ? RUBY_Qtrue + : RUBY_Qfalse; +} + +/* ***************************************************************************** +Create Methods in Module +***************************************************************************** */ + +FIO_SFUNC void iodine_utils_define_methods(VALUE m) { + rb_define_singleton_method(m, "escape_path", iodine_utils_encode_path, 1); + rb_define_singleton_method(m, "escape_path!", iodine_utils_encode_path1, 1); + rb_define_singleton_method(m, "unescape_path", iodine_utils_decode_path, -1); + rb_define_singleton_method(m, "unescape_path!", iodine_utils_decode_path1, 1); + rb_define_singleton_method(m, "escape", iodine_utils_encode_url, 1); + rb_define_singleton_method(m, "escape!", iodine_utils_encode_url1, 1); + rb_define_singleton_method(m, "unescape", iodine_utils_decode_url, -1); + rb_define_singleton_method(m, "unescape!", iodine_utils_decode_url1, 1); + rb_define_singleton_method(m, "escape_html", iodine_utils_encode_html, 1); + rb_define_singleton_method(m, "escape_html!", iodine_utils_encode_html1, 1); + rb_define_singleton_method(m, "unescape_html", iodine_utils_decode_html, -1); + rb_define_singleton_method(m, "unescape_html!", iodine_utils_decode_html1, 1); + rb_define_singleton_method(m, "rfc2109", iodine_utils_rfc2109, 1); + rb_define_singleton_method(m, "rfc2822", iodine_utils_rfc2822, 1); + rb_define_singleton_method(m, "time2str", iodine_utils_rfc7231, 1); + rb_define_singleton_method(m, "secure_compare", iodine_utils_is_eq, 2); +} + +/** + * Adds the `Iodine::Utils` methods to the modules passed as arguments. + * + * If no modules were passed to the `monkey_patch` method, `Rack::Utils` will be + * monkey patched. + * + */ +FIO_SFUNC VALUE iodine_utils_monkey_patch(int argc, VALUE *argv, VALUE self) { + VALUE default_module; + if (!argc) { + argc = 1; + argv = &default_module; + default_module = rb_define_module_under(rb_define_module("Rack"), "Utils"); + } + for (int i = 0; i < argc; ++i) { + rb_check_type(argv[i], T_MODULE); + iodine_utils_define_methods(argv[i]); + } + return self; +} + +/** Initialize Iodine::Utils */ // clang-format off +/** + +# Utility Helpers - Iodine's Helpers + +These are some unescaping / decoding helpers provided by Iodine. + +These **should** be faster then their common Ruby / Rack alternative. + +Performance may differ according to architecture and compiler used. Please measure: + + require 'iodine' + require 'rack' + require 'cgi' + require 'benchmark/ips' + encoded = '%E3 + %83 + %AB + %E3 + %83 + %93 + %E3 + %82 + %A4 + %E3 + %82 + %B9 + %E3 + %81 + %A8' # a String in need of decoding + decoded = Rack::Utils.unescape(encoded, "binary") + html_xss = "" + html_xss_safe = Rack::Utils.escape_html html_xss + short_str1 = Array.new(64) { 'a' } .join + short_str2 = Array.new(64) { 'a' } .join + long_str1 = Array.new(4094) { 'a' } .join + long_str2 = Array.new(4094) { 'a' } .join + now_preclaculated = Time.now + Benchmark.ips do |bm| + bm.report(" Iodine rfc2822") { Iodine::Utils.rfc2822(now_preclaculated) } + bm.report(" Rack rfc2822") { Rack::Utils.rfc2822(now_preclaculated) } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine unescape") { Iodine::Utils.unescape encoded } + bm.report(" Rack unescape") { Rack::Utils.unescape encoded } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine escape") { Iodine::Utils.escape decoded } + bm.report(" Rack escape") { Rack::Utils.escape decoded } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine escape HTML") { Iodine::Utils.escape_html html_xss } + bm.report(" Rack escape HTML") { Rack::Utils.escape_html html_xss } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine unescape HTML") { Iodine::Utils.unescape_html html_xss_safe } + bm.report(" CGI unescape HTML") { CGI.unescapeHTML html_xss_safe } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine secure compare (short)") { Iodine::Utils.secure_compare short_str1, short_str2 } + bm.report(" Rack secure compare (short)") { Rack::Utils.secure_compare short_str1, short_str2 } + bm.compare! + end; Benchmark.ips do |bm| + bm.report("Iodine secure compare (long)") { Iodine::Utils.secure_compare long_str1, long_str2 } + bm.report(" Rack secure compare (long)") { Rack::Utils.secure_compare long_str1, long_str2 } + bm.compare! + end && nil + + */ +static void Init_Iodine_Utils(void) { + VALUE m = rb_define_module_under(iodine_rb_IODINE, "Utils"); + rb_define_singleton_method(m, "escape_path", iodine_utils_encode_path, 1); + rb_define_singleton_method(m, "escape_path!", iodine_utils_encode_path1, 1); + rb_define_singleton_method(m, "unescape_path", iodine_utils_decode_path, -1); + rb_define_singleton_method(m, "unescape_path!", iodine_utils_decode_path1, 1); + rb_define_singleton_method(m, "escape", iodine_utils_encode_url, 1); + rb_define_singleton_method(m, "escape!", iodine_utils_encode_url1, 1); + rb_define_singleton_method(m, "unescape", iodine_utils_decode_url, -1); + rb_define_singleton_method(m, "unescape!", iodine_utils_decode_url1, 1); + rb_define_singleton_method(m, "escape_html", iodine_utils_encode_html, 1); + rb_define_singleton_method(m, "escape_html!", iodine_utils_encode_html1, 1); + rb_define_singleton_method(m, "unescape_html", iodine_utils_decode_html, -1); + rb_define_singleton_method(m, "unescape_html!", iodine_utils_decode_html1, 1); + rb_define_singleton_method(m, "rfc2109", iodine_utils_rfc2109, 1); + rb_define_singleton_method(m, "rfc2822", iodine_utils_rfc2822, 1); + rb_define_singleton_method(m, "time2str", iodine_utils_rfc7231, 1); + rb_define_singleton_method(m, "secure_compare", iodine_utils_is_eq, 2); + rb_define_singleton_method(m, "monkey_patch", iodine_utils_monkey_patch, -1); +} // clang-format on +#endif /* H___IODINE_UTILS___H */ diff --git a/ext/iodine/mustache_parser.h b/ext/iodine/mustache_parser.h deleted file mode 100644 index a05c0456..00000000 --- a/ext/iodine/mustache_parser.h +++ /dev/null @@ -1,1546 +0,0 @@ -/* -Copyright: Boaz Segev, 2018-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_MUSTACHE_LOADR_H -/** - * A mustache parser using a callback systems that allows this implementation to - * be framework agnostic (i.e., can be used with any JSON library). - */ -#define H_MUSTACHE_LOADR_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef __MINGW32__ -ssize_t pread(int, void*, size_t, off_t); -#endif - -#if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS) -#define __attribute__(...) -#define __has_include(...) 0 -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#elif !defined(__clang__) && !defined(__has_builtin) -#define __has_builtin(...) 0 -#define FIO_GNUC_BYPASS 1 -#endif - -#ifndef MUSTACHE_FUNC -#define MUSTACHE_FUNC static __attribute__((unused)) -#endif - -/* ***************************************************************************** -Compile Time Behavior Flags -***************************************************************************** */ - -#ifndef MUSTACHE_USE_DYNAMIC_PADDING -#define MUSTACHE_USE_DYNAMIC_PADDING 1 -#endif - -#ifndef MUSTACHE_FAIL_ON_MISSING_TEMPLATE -#define MUSTACHE_FAIL_ON_MISSING_TEMPLATE 1 -#endif - -#if !defined(MUSTACHE_NESTING_LIMIT) || !MUSTACHE_NESTING_LIMIT -#undef MUSTACHE_NESTING_LIMIT -#define MUSTACHE_NESTING_LIMIT 82 -#endif - -/* ***************************************************************************** -Mustache API Argument types -***************************************************************************** */ - -/** an opaque type for mustache template data (when caching). */ -typedef struct mustache_s mustache_s; - -/** Error reporting type. */ -typedef enum mustache_error_en { - MUSTACHE_OK, - MUSTACHE_ERR_TOO_DEEP, - MUSTACHE_ERR_CLOSURE_MISMATCH, - MUSTACHE_ERR_FILE_NOT_FOUND, - MUSTACHE_ERR_FILE_TOO_BIG, - MUSTACHE_ERR_FILE_NAME_TOO_LONG, - MUSTACHE_ERR_FILE_NAME_TOO_SHORT, - MUSTACHE_ERR_EMPTY_TEMPLATE, - MUSTACHE_ERR_DELIMITER_TOO_LONG, - MUSTACHE_ERR_NAME_TOO_LONG, - MUSTACHE_ERR_UNKNOWN, - MUSTACHE_ERR_USER_ERROR, -} mustache_error_en; - -/** Arguments for the `mustache_load` function, used by the mustache parser. */ -typedef struct { - /** The root template's file name. */ - char const *filename; - /** The file name's length. */ - size_t filename_len; - /** If data and data_len are set, they will be used as the file's contents. */ - char const *data; - /** If data and data_len are set, they will be used as the file's contents. */ - size_t data_len; - /** Parsing error reporting (can be NULL). */ - mustache_error_en *err; -} mustache_load_args_s; - -/* ***************************************************************************** -REQUIRED: Define INCLUDE_MUSTACHE_IMPLEMENTATION only in the implementation file -***************************************************************************** */ - -/** - * In non-implementation files, don't define the INCLUDE_MUSTACHE_IMPLEMENTATION - * macro. - * - * Before including the header within an implementation faile, define - * INCLUDE_MUSTACHE_IMPLEMENTATION as 1. - */ -#if INCLUDE_MUSTACHE_IMPLEMENTATION - -/* ***************************************************************************** -Mustache API Functions and Arguments -***************************************************************************** */ - -MUSTACHE_FUNC mustache_s *mustache_load(mustache_load_args_s args); - -#define mustache_load(...) mustache_load((mustache_load_args_s){__VA_ARGS__}) - -/** free the mustache template */ -inline MUSTACHE_FUNC void mustache_free(mustache_s *mustache) { - free(mustache); -} - -/** Arguments for the `mustache_build` function. */ -typedef struct { - /** The parsed template (an instruction collection). */ - mustache_s *mustache; - /** Opaque user data (recommended for input review) - children will inherit - * the parent's udata value. Updated values will propegate to child sections - * but won't effect parent sections. - */ - void *udata1; - /** Opaque user data (recommended for output handling)- children will inherit - * the parent's udata value. Updated values will propegate to child sections - * but won't effect parent sections. - */ - void *udata2; - /** Formatting error reporting (can be NULL). */ - mustache_error_en *err; -} mustache_build_args_s; -MUSTACHE_FUNC int mustache_build(mustache_build_args_s args); - -#define mustache_build(mustache_s_ptr, ...) \ - mustache_build( \ - (mustache_build_args_s){.mustache = (mustache_s_ptr), __VA_ARGS__}) - -/* ***************************************************************************** -Callbacks Types - types used by the template builder callbacks -***************************************************************************** */ - -/** - * A mustache section allows the callbacks to "walk" backwards towards the root - * in search of argument data. - * - * Note that every section is allowed a separate udata value. - */ -typedef struct mustache_section_s { - /** Opaque user data (recommended for input review) - children will inherit - * the parent's udata value. Updated values will propegate to child sections - * but won't effect parent sections. - */ - void *udata1; - /** Opaque user data (recommended for output handling)- children will inherit - * the parent's udata value. Updated values will propegate to child sections - * but won't effect parent sections. - */ - void *udata2; -} mustache_section_s; - -/* ***************************************************************************** -Callbacks Helpers - These functions can be called from within callbacks -***************************************************************************** */ - -/** - * Returns the section's parent for nested sections, or NULL (for root section). - * - * This allows the `udata` values in the parent to be accessed and can be used, - * for example, when seeking a value (argument / keyword) within a nested data - * structure such as a Hash. - */ -static inline mustache_section_s * -mustache_section_parent(mustache_section_s *section); - -/** - * This helper function should be used to write text to the output - * stream form within `mustache_on_arg` or `mustache_on_section_test`. - * - * This function will call the `mustache_on_text` callback for each slice of - * text that requires padding and for escaped data. - * - * `mustache_on_text` must NEVER call this function. - */ -static inline int mustache_write_text(mustache_section_s *section, char *text, - uint32_t len, uint8_t escape); - -/** - * Returns the section's unparsed content as a non-NUL terminated byte array. - * - * The length of the data will be placed in the `size_t` variable pointed to by - * `p_len`. Do NOT use `strlen`, since the data isn't NUL terminated. - * - * This allows text to be accessed when a section's content is, in fact, meant - * to be passed along to a function / lambda. - */ -static inline const char *mustache_section_text(mustache_section_s *section, - size_t *p_len); - -/* ***************************************************************************** -Client Callbacks - MUST be implemented by the including file -***************************************************************************** */ - -/** - * Called when an argument name was detected in the current section. - * - * A conforming implementation will search for the named argument both in the - * existing section and all of it's parents (walking backwards towards the root) - * until a value is detected. - * - * A missing value should be treated the same as an empty string. - * - * A conforming implementation will output the named argument's value (either - * HTML escaped or not, depending on the `escape` flag) as a string. - * - * A conforming implementation will test for dot notation in the name. - * - * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to - * determine the actual string length. - */ -static int mustache_on_arg(mustache_section_s *section, const char *name, - uint32_t name_len, unsigned char escape); - -/** - * Called when simple template text (string) is detected. - * - * A conforming implementation will output data as a string (no escaping). - * - * NOTE: the `data` is **not** NUL terminated. Use the `data_len` data to - * determine the actual data length. - */ -static int mustache_on_text(mustache_section_s *section, const char *data, - uint32_t data_len); - -/** - * Called for nested sections, must return the number of objects in the new - * subsection (depending on the argument's name). - * - * Arrays should return the number of objects in the array. - * - * `true` values should return 1. - * - * `false` values should return 0. - * - * A return value of -1 will stop processing with an error. - * - * Please note, this will handle both normal and inverted sections. - * - * The `callable` value is true if the section is allowed to be a function / - * callback. If the section object represent a function / callback (lambda), the - * lambda should be called and the `mustache_on_section_test` callback should - * return 0. - * - * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to - * determine the actual string length. - * - * A conforming implementation will test for dot notation in the name. - */ -static int32_t mustache_on_section_test(mustache_section_s *section, - const char *name, uint32_t name_len, - uint8_t callable); - -/** - * Called when entering a nested section. - * - * `index` is a zero based index indicating the number of repetitions that - * occurred so far (same as the array index for arrays). - * - * A return value of -1 will stop processing with an error. - * - * Note: this is a good time to update the subsection's `udata` with the value - * of the array index. The `udata` will always contain the value or the parent's - * `udata`. - * - * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to - * determine the actual string length. - * - * A conforming implementation will test for dot notation in the name. - */ -static int mustache_on_section_start(mustache_section_s *section, - char const *name, uint32_t name_len, - uint32_t index); - -/** - * Called for cleanup in case of error. - */ -static void mustache_on_formatting_error(void *udata1, void *udata2); - -/* ***************************************************************************** - -IMPLEMENTATION (beware: monolithic functions ahead) - -***************************************************************************** */ - -/* ***************************************************************************** -Internal types -***************************************************************************** */ - -struct mustache_s { - /* The number of instructions in the engine */ - union { - void *read_only_pt; /* ensure pointer wide padding */ - struct { - uint32_t intruction_count; - uint32_t data_length; - } read_only; - } u; -}; - -typedef struct mustache__instruction_s { - enum { - MUSTACHE_WRITE_TEXT, - MUSTACHE_WRITE_ARG, - MUSTACHE_WRITE_ARG_UNESCAPED, - MUSTACHE_SECTION_START, - MUSTACHE_SECTION_START_INV, - MUSTACHE_SECTION_END, - MUSTACHE_SECTION_GOTO, - MUSTACHE_PADDING_PUSH, - MUSTACHE_PADDING_POP, - MUSTACHE_PADDING_WRITE, - } instruction; - /** the data the instruction acts upon */ - struct { - /** The length instruction block in the instruction array (for sections). */ - uint32_t end; - /** The length of the (string) data. */ - uint32_t len; - /** The offset from the beginning of the data segment. */ - uint32_t name_pos; - /** The offset between the name (start) and content (for sections). */ - uint16_t name_len; - /** The offset between the name and the content (left / right by type). */ - uint16_t offset; - } data; -} mustache__instruction_s; - -typedef struct { - mustache_section_s sec; /* client visible section data */ - uint32_t start; /* section start instruction position */ - uint32_t end; /* instruction to jump to after completion */ - uint32_t index; /* zero based index for section loops */ - uint32_t count; /* the number of times the section should be performed */ - uint16_t frame; /* the stack frame's index (zero based) */ -} mustache__section_stack_frame_s; - -/* - * The stack memory is placed in a structure to allow stack unrolling and - * avoiding recursion with it's stack overflow risks. - */ -typedef struct { - mustache_s *data; /* the mustache template being built */ - uint32_t pos; /* the instruction postision index */ - uint32_t padding; /* padding instruction position */ - uint16_t index; /* the stack postision index */ - mustache__section_stack_frame_s stack[MUSTACHE_NESTING_LIMIT]; -} mustache__builder_stack_s; - -#define MUSTACHE_DELIMITER_LENGTH_LIMIT 5 - -/* - * The stack memory is placed in a structure to allow stack unrolling and - * avoiding recursion with it's stack overflow risks. - */ -typedef struct { - mustache_s *m; - mustache__instruction_s *i; - mustache_error_en *err; - char *data; - char *path; - uint32_t i_capa; - uint32_t data_len; - uint32_t padding; - uint16_t path_len; - uint16_t path_capa; - uint16_t index; /* stack index */ - struct { - uint32_t data_start; /* template starting position (with header) */ - uint32_t data_pos; /* data reading position (how much was consumed) */ - uint32_t data_end; /* data ending position (for this template) */ - uint16_t open_sections; /* counts open sections waiting for closure */ - char del_start[MUSTACHE_DELIMITER_LENGTH_LIMIT]; /* currunt instruction - start delimiter */ - char del_end[MUSTACHE_DELIMITER_LENGTH_LIMIT]; /* currunt instruction end - delimiter */ - uint8_t del_start_len; /* currunt start delimiter length */ - uint8_t del_end_len; /* currunt end delimiter length */ - } stack[MUSTACHE_NESTING_LIMIT]; -} mustache__loader_stack_s; - -/* ***************************************************************************** -Callbacks Helper Implementation -***************************************************************************** */ - -#define MUSTACH2INSTRUCTIONS(mustache) \ - ((mustache__instruction_s *)((mustache) + 1)) -#define MUSTACH2DATA(mustache) \ - (char *)(MUSTACH2INSTRUCTIONS((mustache)) + \ - (mustache)->u.read_only.intruction_count) -#define MUSTACHE_OBJECT_OFFSET(type, member, ptr) \ - ((type *)((uintptr_t)(ptr) - (uintptr_t)(&(((type *)0)->member)))) - -#define MUSTACHE_ASSERT(cond, msg) \ - if (!(cond)) { \ - perror("FATAL ERROR: " msg); \ - exit(errno); \ - } -#define MUSTACHE_IGNORE_WHITESPACE(str, step) \ - while (isspace(*(str))) { \ - (str) += (step); \ - } - -/* - * used internally by some of the helpers to find convert a section pointer to - * the full builder stack data. - */ -static inline mustache__builder_stack_s * -mustache___section2stack(mustache_section_s *section) { - mustache__section_stack_frame_s *f = - (mustache__section_stack_frame_s *)section; - return MUSTACHE_OBJECT_OFFSET(mustache__builder_stack_s, stack, - (f - f->frame)); -} - -/** - * Returns the section's parent for nested sections, or NULL (for root section). - * - * This allows the `udata` values in the parent to be accessed and can be used, - * for example, when seeking a value (argument / keyword) within a nested data - * structure such as a Hash. - */ -static inline mustache_section_s * -mustache_section_parent(mustache_section_s *section) { - mustache_section_s tmp = *section; - mustache__section_stack_frame_s *f = - (mustache__section_stack_frame_s *)section; - while (f->frame) { - --f; - if (tmp.udata1 != f->sec.udata1 || tmp.udata2 != f->sec.udata2) - return &f->sec; - } - return NULL; -} - -/** - * Returns the section's unparsed content as a non-NUL terminated byte array. - * - * The length of the data will be placed in the `size_t` variable pointed to by - * `p_len`. Do NOT use `strlen`, since the data isn't NUL terminated. - * - * This allows text to be accessed when a section's content is, in fact, meant - * to be passed along to a function / lambda. - */ -static inline const char *mustache_section_text(mustache_section_s *section, - size_t *p_len) { - if (!section || !p_len) - goto error; - mustache__builder_stack_s *s = mustache___section2stack(section); - mustache__instruction_s *inst = - MUSTACH2INSTRUCTIONS(s->data) + s->pos; /* current instruction*/ - if (inst->instruction != MUSTACHE_SECTION_START) - goto error; - const char *start = - MUSTACH2DATA(s->data) + inst->data.name_pos + inst->data.offset; - *p_len = inst->data.len; - return start; -error: - *p_len = 0; - return NULL; -} - -/** - * used internally to write escaped text rather than clear text. - */ -static inline int mustache__write_padding(mustache__builder_stack_s *s) { - mustache__instruction_s *const inst = MUSTACH2INSTRUCTIONS(s->data); - char *const data = MUSTACH2DATA(s->data); - for (uint32_t i = s->padding; i; i = inst[i].data.end) { - if (mustache_on_text(&s->stack[s->index].sec, data + inst[i].data.name_pos, - inst[i].data.name_len) == -1) - return -1; - } - return 0; -} - -/** - * used internally to write escaped text rather than clear text. - */ -static int mustache__write_escaped(mustache__builder_stack_s *s, char *text, - uint32_t len) { -#define MUSTACHE_ESCAPE_BUFFER_SIZE 4096 - /** HTML ecape table, created using the following Ruby Script: - - a = (0..255).to_a.map {|i| i.chr } - 100.times {|i| a[i] = "&\##{i.to_s(10)};"} - ('a'.ord..'z'.ord).each {|i| a[i] = i.chr } - ('A'.ord..'Z'.ord).each {|i| a[i] = i.chr } - ('0'.ord..'9'.ord).each {|i| a[i] = i.chr } - a['<'.ord] = "<" - a['>'.ord] = ">" - a['&'.ord] = "&" - a['"'.ord] = """ - a["\'".ord] = "'" - a['|'.ord] = "&\##{'|'.ord.to_s(10)};" - - b = a.map {|s| s.length } - puts "static char *html_escape_strs[] = {", - a.to_s.slice(1..-2) ,"};", - "static uint8_t html_escape_len[] = {", - b.to_s.slice(1..-2),"};" -*/ - static const char *html_escape_strs[] = { - "�", "", "", "", "", "", "", "", - "", " ", " ", " ", " ", " ", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - " ", "!", """, "#", "$", "%", "&", "'", - "(", ")", "*", "+", ",", "-", ".", "/", - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", ":", ";", "<", "=", ">", "?", - "@", "A", "B", "C", "D", "E", "F", "G", - "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", - "X", "Y", "Z", "[", "\", "]", "^", "_", - "`", "a", "b", "c", "d", "e", "f", "g", - "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", - "x", "y", "z", "{", "|", "}", "~", "\x7F", - "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87", - "\x88", "\x89", "\x8A", "\x8B", "\x8C", "\x8D", "\x8E", "\x8F", - "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97", - "\x98", "\x99", "\x9A", "\x9B", "\x9C", "\x9D", "\x9E", "\x9F", - "\xA0", "\xA1", "\xA2", "\xA3", "\xA4", "\xA5", "\xA6", "\xA7", - "\xA8", "\xA9", "\xAA", "\xAB", "\xAC", "\xAD", "\xAE", "\xAF", - "\xB0", "\xB1", "\xB2", "\xB3", "\xB4", "\xB5", "\xB6", "\xB7", - "\xB8", "\xB9", "\xBA", "\xBB", "\xBC", "\xBD", "\xBE", "\xBF", - "\xC0", "\xC1", "\xC2", "\xC3", "\xC4", "\xC5", "\xC6", "\xC7", - "\xC8", "\xC9", "\xCA", "\xCB", "\xCC", "\xCD", "\xCE", "\xCF", - "\xD0", "\xD1", "\xD2", "\xD3", "\xD4", "\xD5", "\xD6", "\xD7", - "\xD8", "\xD9", "\xDA", "\xDB", "\xDC", "\xDD", "\xDE", "\xDF", - "\xE0", "\xE1", "\xE2", "\xE3", "\xE4", "\xE5", "\xE6", "\xE7", - "\xE8", "\xE9", "\xEA", "\xEB", "\xEC", "\xED", "\xEE", "\xEF", - "\xF0", "\xF1", "\xF2", "\xF3", "\xF4", "\xF5", "\xF6", "\xF7", - "\xF8", "\xF9", "\xFA", "\xFB", "\xFC", "\xFD", "\xFE", "\xFF"}; - static const uint8_t html_escape_len[] = { - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 4, 5, 4, 5, 5, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, - 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - char buffer[MUSTACHE_ESCAPE_BUFFER_SIZE]; - size_t pos = 0; - const char *end = text + len; - while (text < end) { - if (MUSTACHE_USE_DYNAMIC_PADDING && *text == '\n' && s->padding) { - buffer[pos++] = '\n'; - buffer[pos] = 0; - if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) - return -1; - pos = 0; - if (mustache__write_padding(s) == -1) - return -1; - } else { - memcpy(buffer + pos, html_escape_strs[(uint8_t)text[0]], - html_escape_len[(uint8_t)text[0]]); - pos += html_escape_len[(uint8_t)text[0]]; - if (pos >= (MUSTACHE_ESCAPE_BUFFER_SIZE - 6)) { - buffer[pos] = 0; - if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) - return -1; - pos = 0; - } - } - ++text; - } - if (pos) { - buffer[pos] = 0; - if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) - return -1; - } - return 0; -#undef MUSTACHE_ESCAPE_BUFFER_SIZE -} -/** - * This helper function should be used to write text to the output - * stream (often used within the `mustache_on_arg` callback). - */ -static inline int mustache_write_text(mustache_section_s *section, char *text, - uint32_t len, uint8_t escape) { - mustache__builder_stack_s *s = mustache___section2stack(section); - if (escape) - return mustache__write_escaped(s, text, len); - /* TODO */ -#if MUSTACHE_USE_DYNAMIC_PADDING - char *end = memchr(text, '\n', len); - while (len && end) { - ++end; - const uint32_t slice_len = end - text; - if (mustache_on_text(&s->stack[s->index].sec, text, slice_len) == -1) - return -1; - text = end; - len -= slice_len; - end = memchr(text, '\n', len); - if (mustache__write_padding(s) == -1) - return -1; - } - if (len && mustache_on_text(&s->stack[s->index].sec, text, len) == -1) - return -1; -#else - if (mustache_on_text(&s->stack[s->index].sec, text, len) == -1) - return -1; - -#endif - return 0; -} - -/* ***************************************************************************** -Internal Helpers -***************************************************************************** */ - -/* the data segment's new length after loading the the template */ -/* The data segments includes a template header: */ -/* | 4 bytes template start instruction position | */ -/* | 2 bytes template name | 4 bytes next template position | */ -/* | template name (filename) | ...[template data]... */ -/* this allows template data to be reused when repeating a template */ - -/** a template file data segment header */ -typedef struct { - const char *filename; /* template file name */ - uint32_t inst_start; /* start position for instructions */ - uint32_t next; /* next template position (absolute) */ - uint16_t filename_len; /* file name length */ - uint16_t path_len; /* if the file is in a folder, this marks the '/' */ -} mustache__data_segment_s; - -/* data segment serialization, returns the number of bytes written. */ -static inline size_t -mustache__data_segment_write(uint8_t *dest, mustache__data_segment_s data) { - dest[0] = 0xFF & data.inst_start; - dest[1] = 0xFF & (data.inst_start >> 1); - dest[2] = 0xFF & (data.inst_start >> 2); - dest[3] = 0xFF & (data.inst_start >> 3); - dest[4] = 0xFF & data.next; - dest[5] = 0xFF & (data.next >> 1); - dest[6] = 0xFF & (data.next >> 2); - dest[7] = 0xFF & (data.next >> 3); - dest[8] = 0xFF & data.filename_len; - dest[9] = 0xFF & (data.filename_len >> 1); - dest[10] = 0xFF & data.path_len; - dest[11] = 0xFF & (data.path_len >> 1); - if (data.filename_len) - memcpy(dest + 12, data.filename, data.filename_len); - (dest + 12)[data.filename_len] = 0; - return 13 + data.filename_len; -} - -static inline size_t mustache__data_segment_length(size_t filename_len) { - return 13 + filename_len; -} - -/* data segment serialization, reads data from raw stream. */ -static inline mustache__data_segment_s -mustache__data_segment_read(uint8_t *data) { - mustache__data_segment_s s = { - .filename = (char *)(data + 12), - .inst_start = ((uint32_t)data[0] | ((uint32_t)data[1] << 1) | - ((uint32_t)data[2] << 2) | ((uint32_t)data[3] << 3)), - .next = ((uint32_t)data[4] | ((uint32_t)data[5] << 1) | - ((uint32_t)data[6] << 2) | ((uint32_t)data[7] << 3)), - .filename_len = ((uint16_t)data[8] | ((uint16_t)data[9] << 1)), - .path_len = ((uint16_t)data[10] | ((uint16_t)data[11] << 1)), - }; - return s; -} - -static inline void mustache__stand_alone_adjust(mustache__loader_stack_s *s, - uint32_t stand_alone) { - if (!stand_alone) - return; - /* adjust reading position */ - s->stack[s->index].data_pos += - 1 + (s->data[s->stack[s->index].data_pos] == '\r'); - /* remove any padding from the beginning of the line */ - if (s->m->u.read_only.intruction_count && - s->i[s->m->u.read_only.intruction_count - 1].instruction == - MUSTACHE_WRITE_TEXT) { - mustache__instruction_s *ins = - s->i + s->m->u.read_only.intruction_count - 1; - if (ins->data.name_len <= (stand_alone >> 1)) - --s->m->u.read_only.intruction_count; - else - ins->data.name_len -= (stand_alone >> 1); - } -} - -/* pushes an instruction to the instruction array */ -static inline int mustache__instruction_push(mustache__loader_stack_s *s, - mustache__instruction_s inst) { - if (s->m->u.read_only.intruction_count >= INT32_MAX) - goto instructions_too_long; - if (s->i_capa <= s->m->u.read_only.intruction_count) { - s->m = realloc(s->m, sizeof(*s->m) + (sizeof(*s->i) * (s->i_capa + 32))); - MUSTACHE_ASSERT(s->m, "Mustache memory allocation failed"); - s->i_capa += 32; - s->i = MUSTACH2INSTRUCTIONS(s->m); - } - s->i[s->m->u.read_only.intruction_count] = inst; - ++s->m->u.read_only.intruction_count; - return 0; -instructions_too_long: - *s->err = MUSTACHE_ERR_TOO_DEEP; - return -1; -} - -/* pushes text and padding instruction to the instruction array */ -static inline int mustache__push_text_instruction(mustache__loader_stack_s *s, - uint32_t pos, uint32_t len) { - /* always push padding instructions, in case or recursion with padding */ - // if (!s->padding) { - // /* no padding, push text, as is */ - // return mustache__instruction_push( - // s, (mustache__instruction_s){ - // .instruction = MUSTACHE_WRITE_TEXT, - // .data = {.name_pos = pos, .name_len = len}, - // }); - // } - /* insert padding instruction after each new line */ - for (;;) { - /* seek new line markers */ - char *start = s->data + pos; - char *end = memchr(s->data + pos, '\n', len); - if (!end) - break; - /* offset marks the line's length */ - const size_t offset = (end - start) + 1; - /* push text and padding instructions */ - if (mustache__instruction_push( - s, - (mustache__instruction_s){ - .instruction = MUSTACHE_WRITE_TEXT, - .data = {.name_pos = pos, .name_len = offset}, - }) == -1 || - mustache__instruction_push(s, (mustache__instruction_s){ - .instruction = MUSTACHE_PADDING_WRITE, - }) == -1) - return -1; - pos += offset; - len -= offset; - } - /* done? */ - if (!len) - return 0; - /* write any text that doesn't terminate in the EOL marker */ - return mustache__instruction_push( - s, (mustache__instruction_s){ - .instruction = MUSTACHE_WRITE_TEXT, - .data = {.name_pos = pos, .name_len = len}, - }); -} - -/* - * Returns the instruction's position if the template is already existing. - * - * Returns `(uint32_t)-1` if the template is missing (not loaded, yet). - */ -static inline uint32_t mustache__file_is_loaded(mustache__loader_stack_s *s, - char *name, size_t name_len) { - char *data = s->data; - const char *end = data + s->m->u.read_only.data_length; - while (data < end) { - mustache__data_segment_s seg = mustache__data_segment_read((uint8_t *)data); - if (seg.filename_len == name_len && !memcmp(seg.filename, name, name_len)) - return seg.inst_start; - data += seg.next; - } - return (uint32_t)-1; -} - -static inline ssize_t mustache__load_data(mustache__loader_stack_s *s, - const char *name, size_t name_len, - const char *data, size_t data_len) { - const size_t old_len = s->data_len; - if (old_len + data_len > UINT32_MAX) - goto too_long; - s->data = realloc(s->data, old_len + data_len + - mustache__data_segment_length(name_len) + 1); - MUSTACHE_ASSERT(s->data, - "failed to allocate memory for mustache template data"); - size_t path_len = name_len; - while (path_len) { - --path_len; - if (name[path_len] == '/' || name[path_len] == '\\') { - ++path_len; - break; - } - } - mustache__data_segment_write( - (uint8_t *)s->data + old_len, - (mustache__data_segment_s){ - .filename = name, - .filename_len = name_len, - .inst_start = s->m->u.read_only.intruction_count, - .next = - s->data_len + data_len + mustache__data_segment_length(name_len), - .path_len = path_len, - }); - if (data) { - memcpy(s->data + old_len + mustache__data_segment_length(name_len), data, - data_len); - } - s->data_len += data_len + mustache__data_segment_length(name_len); - s->data[s->data_len] = 0; - s->m->u.read_only.data_length = s->data_len; - - mustache__instruction_push( - s, (mustache__instruction_s){.instruction = MUSTACHE_SECTION_START}); - /* advance stack frame */ - ++s->index; - if (s->index >= MUSTACHE_NESTING_LIMIT) - goto too_long; - s->stack[s->index].data_pos = - old_len + mustache__data_segment_length(name_len); - s->stack[s->index].data_start = old_len; - s->stack[s->index].data_end = s->data_len; - /* reset delimiters */ - s->stack[s->index].del_start_len = 2; - s->stack[s->index].del_end_len = 2; - s->stack[s->index].del_start[0] = s->stack[s->index].del_start[1] = '{'; - s->stack[s->index].del_start[2] = 0; - s->stack[s->index].del_end[0] = s->stack[s->index].del_end[1] = '}'; - s->stack[s->index].del_end[2] = 0; - s->stack[s->index].open_sections = 0; - return data_len; -too_long: - *s->err = MUSTACHE_ERR_TOO_DEEP; - return -1; -} - -static inline ssize_t mustache__load_file(mustache__loader_stack_s *s, - const char *name, size_t name_len) { - struct stat f_data; - uint16_t i = s->index; - uint32_t old_path_len = 0; - if (!name_len) { - goto name_missing_error; - } - if (name_len >= 8192) - goto name_length_error; - /* test file names by walking the stack backwards and matching paths */ - do { - mustache__data_segment_s seg; - if (s->data) - seg = mustache__data_segment_read((uint8_t *)s->data + - s->stack[i].data_start); - else - seg = (mustache__data_segment_s){ - .path_len = 0, - }; - /* did we test this path for the file? */ - if (old_path_len && (old_path_len == seg.path_len && - !memcmp(s->path, seg.filename, old_path_len))) { - continue; - } - old_path_len = seg.path_len; - /* make sure s->path capacity is enough. */ - if (s->path_capa < seg.path_len + name_len + 10) { - s->path = realloc(s->path, seg.path_len + name_len + 10); - MUSTACHE_ASSERT(s->path, - "failed to allocate memory for mustache template data"); - s->path_capa = seg.path_len + name_len + 10; - } - /* if testing local folder, there's no need to keep looping */ - if (!seg.path_len) { - i = 1; - } else { - memcpy(s->path, seg.filename, seg.path_len); - } - /* copy name to path */ - memcpy(s->path + seg.path_len, name, name_len); - s->path[name_len + seg.path_len] = 0; - /* test if file exists */ - if (!stat(s->path, &f_data) && S_ISREG(f_data.st_mode)) { - old_path_len = name_len + seg.path_len; - goto file_found; - } - /* add default extension */ - memcpy(s->path + seg.path_len + name_len, ".mustache", 9); - s->path[name_len + seg.path_len + 9] = 0; - /* test if new filename file exists */ - if (!stat(s->path, &f_data) && S_ISREG(f_data.st_mode)) { - old_path_len = name_len + seg.path_len + 9; - goto file_found; - } - } while (--i); - - /* test if the file is "virtual" (only true for the first template loaded) */ - if (s->data) { - mustache__data_segment_s seg = - mustache__data_segment_read((uint8_t *)s->data); - if (seg.filename_len == name_len && !memcmp(seg.filename, name, name_len)) { - /* this name points to the original (root) template, and it's virtual */ - if (mustache__instruction_push( - s, (mustache__instruction_s){ - .instruction = MUSTACHE_SECTION_GOTO, - .data = - { - .len = 0, - .end = s->m->u.read_only.intruction_count, - }, - })) - goto unknown_error; - return 0; - } - } - -#if MUSTACHE_FAIL_ON_MISSING_TEMPLATE - *s->err = MUSTACHE_ERR_FILE_NOT_FOUND; - return -1; -#else - return 0; -#endif - -file_found: - if (f_data.st_size >= INT32_MAX) { - goto file_too_big; - } else if (f_data.st_size == 0) { - /* empty, do nothing */ - return 0; - } else { - /* test if the file was previously loaded */ - uint32_t pre_existing = mustache__file_is_loaded(s, s->path, old_path_len); - if (pre_existing != (uint32_t)-1) { - if (mustache__instruction_push( - s, (mustache__instruction_s){ - .instruction = MUSTACHE_SECTION_GOTO, - .data = - { - .len = pre_existing, - .end = s->m->u.read_only.intruction_count, - }, - })) { - goto unknown_error; - } - return 0; - } - } - if (mustache__load_data(s, s->path, old_path_len, NULL, f_data.st_size) == -1) - goto unknown_error; - int fd = open(s->path, O_RDONLY); - if (fd == -1) - goto file_err; - if (pread(fd, s->data + s->data_len - f_data.st_size, f_data.st_size, 0) != - f_data.st_size) - goto file_err; - close(fd); - return f_data.st_size; - -name_missing_error: - *s->err = MUSTACHE_ERR_FILE_NAME_TOO_SHORT; - return -1; - -name_length_error: - *s->err = MUSTACHE_ERR_FILE_NAME_TOO_LONG; - return -1; - -file_too_big: - *s->err = MUSTACHE_ERR_FILE_TOO_BIG; - return -1; - -file_err: - *s->err = MUSTACHE_ERR_UNKNOWN; - return -1; - -unknown_error: - return -1; -} - -/* ***************************************************************************** -Calling the instrustion list (using the template engine) -***************************************************************************** */ - -/* - * This function reviews the instructions listed at the end of the mustache_s - * and performs any callbacks necessary. - * - * The `mustache_s` data is looks like this: - * - * - header (the `mustache_s` struct): lists the length of the instruction - * array and data segments. - * - Instruction array: lists all the instructions extracted from the - * template(s) (an array of `mustache__instruction_s`). - * - Data segment: text and data related to the instructions. - * - * The instructions, much like machine code, might loop or jump. This is why the - * function keeps a stack of sorts. This allows the code to avoid recursion and - * minimize any risk of stack overflow caused by recursive templates. - */ -MUSTACHE_FUNC int(mustache_build)(mustache_build_args_s args) { - mustache_error_en err_if_missing; - if (!args.err) - args.err = &err_if_missing; - if (!args.mustache) { - goto user_error; - } - /* extract the instruction array and data segment from the mustache_s */ - mustache__instruction_s *instructions = MUSTACH2INSTRUCTIONS(args.mustache); - char *const data = MUSTACH2DATA(args.mustache); - mustache__builder_stack_s s; - s.data = args.mustache; - s.pos = 0; - s.index = 0; - s.padding = 0; - s.stack[0] = (mustache__section_stack_frame_s){ - .sec = - { - .udata1 = args.udata1, - .udata2 = args.udata2, - }, - .start = 0, - .end = instructions[0].data.end, - .index = 0, - .count = 0, - .frame = 0, - }; - while ((uintptr_t)(instructions + s.pos) < (uintptr_t)data) { - switch (instructions[s.pos].instruction) { - case MUSTACHE_WRITE_TEXT: - if (mustache_on_text(&s.stack[s.index].sec, - data + instructions[s.pos].data.name_pos, - instructions[s.pos].data.name_len)) - goto user_error; - break; - /* fallthrough */ - case MUSTACHE_WRITE_ARG: - if (mustache_on_arg(&s.stack[s.index].sec, - data + instructions[s.pos].data.name_pos, - instructions[s.pos].data.name_len, 1)) - goto user_error; - break; - case MUSTACHE_WRITE_ARG_UNESCAPED: - if (mustache_on_arg(&s.stack[s.index].sec, - data + instructions[s.pos].data.name_pos, - instructions[s.pos].data.name_len, 0)) - goto user_error; - break; - /* fallthrough */ - case MUSTACHE_SECTION_GOTO: - /* fallthrough */ - case MUSTACHE_SECTION_START: - case MUSTACHE_SECTION_START_INV: - /* advance stack*/ - if (s.index + 1 >= MUSTACHE_NESTING_LIMIT) { - if (args.err) - *args.err = MUSTACHE_ERR_TOO_DEEP; - goto error; - } - s.stack[s.index + 1].sec = s.stack[s.index].sec; - ++s.index; - s.stack[s.index].start = - (instructions[s.pos].instruction == MUSTACHE_SECTION_GOTO - ? instructions[s.pos].data.len - : s.pos); - s.stack[s.index].end = instructions[s.pos].data.end; - s.stack[s.index].frame = s.index; - s.stack[s.index].index = 0; - s.stack[s.index].count = 1; - - /* test section count */ - if (instructions[s.pos].data.name_pos) { - /* this is a named section, it should be tested against user data */ - int32_t val = mustache_on_section_test( - &s.stack[s.index].sec, data + instructions[s.pos].data.name_pos, - instructions[s.pos].data.name_len, - instructions[s.pos].instruction == MUSTACHE_SECTION_START); - if (val == -1) { - goto user_error; - } - if (instructions[s.pos].instruction == MUSTACHE_SECTION_START_INV) { - /* invert test */ - val = (val == 0); - } - s.stack[s.index].count = (uint32_t)val; - } - /* fallthrough */ - case MUSTACHE_SECTION_END: - /* loop section or continue */ - if (s.stack[s.index].index < s.stack[s.index].count) { - /* repeat / start section */ - s.pos = s.stack[s.index].start; - s.stack[s.index].sec = s.stack[s.index - 1].sec; - /* review user callback (if it's a named section) */ - if (instructions[s.pos].data.name_pos && - mustache_on_section_start(&s.stack[s.index].sec, - data + instructions[s.pos].data.name_pos, - instructions[s.pos].data.name_len, - s.stack[s.index].index) == -1) - goto user_error; - /* skip padding instructions in GOTO tags (recursive partials) */ - if (instructions[s.pos].instruction == MUSTACHE_SECTION_GOTO) - ++s.pos; - ++s.stack[s.index].index; - break; - } - s.pos = s.stack[s.index].end; - --s.index; - break; - case MUSTACHE_PADDING_PUSH: - s.padding = s.pos; - break; - case MUSTACHE_PADDING_POP: - s.padding = instructions[s.padding].data.end; - break; - case MUSTACHE_PADDING_WRITE: - for (uint32_t i = s.padding; i; i = instructions[i].data.end) { - if (mustache_on_text(&s.stack[s.index].sec, - data + instructions[i].data.name_pos, - instructions[i].data.name_len)) - goto user_error; - } - break; - default: - /* not a valid engine */ - fprintf(stderr, "ERROR: invalid mustache instruction set detected (wrong " - "`mustache_s`?)\n"); - if (args.err) { - *args.err = MUSTACHE_ERR_UNKNOWN; - } - goto error; - } - ++s.pos; - } - *args.err = MUSTACHE_OK; - return 0; -user_error: - *args.err = MUSTACHE_ERR_USER_ERROR; -error: - mustache_on_formatting_error(args.udata1, args.udata2); - return -1; -} - -/* ***************************************************************************** -Building the instrustion list (parsing the template) -***************************************************************************** */ - -/* The parsing implementation, converts a template to an instruction array */ -MUSTACHE_FUNC mustache_s *(mustache_load)(mustache_load_args_s args) { - mustache_error_en err_if_missing; - mustache__loader_stack_s s; - uint8_t flag = 0; - - if (!args.err) - args.err = &err_if_missing; - s.path_capa = 0; - s.path = NULL; - s.data = NULL; - s.data_len = 0; - s.i = NULL; - s.i_capa = 32; - s.index = 0; - s.padding = 0; - s.m = malloc(sizeof(*s.m) + (sizeof(*s.i) * 32)); - MUSTACHE_ASSERT(s.m, "failed to allocate memory for mustache data"); - s.m->u.read_only_pt = 0; - s.m->u.read_only.data_length = 0; - s.m->u.read_only.intruction_count = 0; - s.i = MUSTACH2INSTRUCTIONS(s.m); - s.err = args.err; - - if (!args.filename_len && args.filename) - args.filename_len = strlen(args.filename); - - if (args.data) { - if (mustache__load_data(&s, args.filename, args.filename_len, args.data, - args.data_len) == -1) { - goto error; - } - } else { - if (mustache__load_file(&s, args.filename, args.filename_len) == -1) { - goto error; - } - } - - /* loop while there are templates to be parsed on the stack */ - while (s.index) { - /* parsing loop */ - while (s.stack[s.index].data_pos < s.stack[s.index].data_end) { - /* stand-alone tag flag, also containes padding length after bit 1 */ - uint32_t stand_alone = 0; - uint32_t stand_alone_pos = 0; - /* start parsing at current position */ - const char *start = s.data + s.stack[s.index].data_pos; - /* find the next instruction (beg == beginning) */ - char *beg = strstr(start, s.stack[s.index].del_start); - const char *org_beg = beg; - if (!beg || beg >= s.data + s.stack[s.index].data_end) { - /* no instructions left, only text */ - mustache__push_text_instruction(&s, s.stack[s.index].data_pos, - s.stack[s.index].data_end - - s.stack[s.index].data_pos); - s.stack[s.index].data_pos = s.stack[s.index].data_end; - continue; - } - if (beg != start) { - /* there's text before the instruction */ - mustache__push_text_instruction(&s, s.stack[s.index].data_pos, - (uint32_t)(uintptr_t)(beg - start)); - } - /* move beg (reading position) after the delimiter */ - beg += s.stack[s.index].del_start_len; - /* seek the end of the instruction delimiter */ - char *end = strstr(beg, s.stack[s.index].del_end); - if (!end || end >= s.data + s.stack[s.index].data_end) { - /* delimiter not closed */ - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - - /* update reading position in the stack */ - s.stack[s.index].data_pos = (end - s.data) + s.stack[s.index].del_end_len; - - /* Test for stand-alone tags */ - if (!end[s.stack[s.index].del_end_len] || - end[s.stack[s.index].del_end_len] == '\n' || - (end[s.stack[s.index].del_end_len] == '\r' && - end[1 + s.stack[s.index].del_end_len] == '\n')) { - char *pad = beg - (s.stack[s.index].del_start_len + 1); - while (pad >= start && (pad[0] == ' ' || pad[0] == '\t')) - --pad; - if (pad[0] == '\n' || pad[0] == 0) { - /* Yes, this a stand-alone tag, store padding length + flag */ - ++pad; - stand_alone_pos = pad - s.data; - stand_alone = - ((beg - (pad + s.stack[s.index].del_start_len)) << 1) | 1; - } - } - - /* parse instruction content */ - flag = 1; - - switch (beg[0]) { - case '!': - /* comment, do nothing... almost */ - mustache__stand_alone_adjust(&s, stand_alone); - break; - - case '=': - /* define new seperators */ - mustache__stand_alone_adjust(&s, stand_alone); - ++beg; - --end; - if (end[0] != '=') { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - --end; - MUSTACHE_IGNORE_WHITESPACE(beg, 1); - MUSTACHE_IGNORE_WHITESPACE(end, -1); - ++end; - { - char *div = beg; - while (div < end && !isspace(*div)) { - ++div; - } - if (div == end || div == beg) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - if (div - beg >= MUSTACHE_DELIMITER_LENGTH_LIMIT) { - *args.err = MUSTACHE_ERR_DELIMITER_TOO_LONG; - goto error; - } - /* copy starting delimiter */ - s.stack[s.index].del_start_len = div - beg; - for (size_t i = 0; i < s.stack[s.index].del_start_len; ++i) { - s.stack[s.index].del_start[i] = beg[i]; - } - s.stack[s.index].del_start[s.stack[s.index].del_start_len] = 0; - - ++div; - MUSTACHE_IGNORE_WHITESPACE(div, 1); - if (div == end) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - if (end - div >= MUSTACHE_DELIMITER_LENGTH_LIMIT) { - *args.err = MUSTACHE_ERR_DELIMITER_TOO_LONG; - goto error; - } - /* copy ending delimiter */ - s.stack[s.index].del_end_len = end - div; - for (size_t i = 0; i < s.stack[s.index].del_end_len; ++i) { - s.stack[s.index].del_end[i] = div[i]; - } - s.stack[s.index].del_end[s.stack[s.index].del_end_len] = 0; - } - break; - - case '^': - /* start inverted section */ - flag = 0; - /* fallthrough */ - case '#': - /* start section (or inverted section) */ - mustache__stand_alone_adjust(&s, stand_alone); - ++beg; - --end; - MUSTACHE_IGNORE_WHITESPACE(beg, 1); - MUSTACHE_IGNORE_WHITESPACE(end, -1); - ++end; - - ++s.stack[s.index].open_sections; - if (s.stack[s.index].open_sections >= MUSTACHE_NESTING_LIMIT) { - *args.err = MUSTACHE_ERR_TOO_DEEP; - goto error; - } - if (beg - s.data >= UINT16_MAX) { - *args.err = MUSTACHE_ERR_NAME_TOO_LONG; - } - if (mustache__instruction_push( - &s, - (mustache__instruction_s){ - .instruction = (flag ? MUSTACHE_SECTION_START - : MUSTACHE_SECTION_START_INV), - .data = { - .name_pos = beg - s.data, - .name_len = end - beg, - .offset = s.stack[s.index].data_pos - (beg - s.data), - }})) { - goto error; - } - break; - - case '>': - /* partial template - search data for loaded template or load new */ - mustache__stand_alone_adjust(&s, stand_alone); - if ((stand_alone >> 1)) { - /* TODO: add padding markers */ - if (mustache__instruction_push( - &s, (mustache__instruction_s){ - .instruction = MUSTACHE_PADDING_PUSH, - .data = { - .name_pos = stand_alone_pos, - .name_len = (stand_alone >> 1), - .end = s.padding, - }})) { - goto error; - } - s.padding = s.m->u.read_only.intruction_count - 1; - } - ++beg; - --end; - MUSTACHE_IGNORE_WHITESPACE(beg, 1); - MUSTACHE_IGNORE_WHITESPACE(end, -1); - ++end; - ssize_t loaded = mustache__load_file(&s, beg, end - beg); - if (loaded == -1) - goto error; - /* Add latest padding section to text */ - if ((stand_alone >> 1)) { - if (loaded) - mustache__instruction_push( - &s, (mustache__instruction_s){ - .instruction = MUSTACHE_WRITE_TEXT, - .data = - { - .name_pos = stand_alone_pos, - .name_len = (stand_alone >> 1), - }, - }); - else if (mustache__instruction_push( - &s, (mustache__instruction_s){.instruction = - MUSTACHE_PADDING_POP})) - goto error; - } - break; - - case '/': - /* section end */ - mustache__stand_alone_adjust(&s, stand_alone); - ++beg; - --end; - MUSTACHE_IGNORE_WHITESPACE(beg, 1); - MUSTACHE_IGNORE_WHITESPACE(end, -1); - ++end; - if (!s.stack[s.index].open_sections) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } else { - uint32_t pos = s.m->u.read_only.intruction_count; - uint32_t nested = 0; - do { - --pos; - if (s.i[pos].instruction == MUSTACHE_SECTION_END) - ++nested; - else if (s.i[pos].instruction == MUSTACHE_SECTION_START || - s.i[pos].instruction == MUSTACHE_SECTION_START_INV) { - if (nested) { - --nested; - } else { - /* test instruction closure */ - if (s.i[pos].data.name_len != end - beg || - memcmp(beg, s.data + s.i[pos].data.name_pos, - s.i[pos].data.name_len)) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - /* update initial instruction (do this before adding closure) */ - s.i[pos].data.end = s.m->u.read_only.intruction_count; - s.i[pos].data.len = org_beg - (s.data + s.i[pos].data.name_pos + - s.i[pos].data.offset); - /* add closure instruction */ - mustache__instruction_push( - &s, (mustache__instruction_s){.instruction = - MUSTACHE_SECTION_END, - .data = s.i[pos].data}); - /* update closure count */ - --s.stack[s.index].open_sections; - /* stop loop */ - pos = 0; - beg = NULL; - } - } - } while (pos); - if (beg) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - } - break; - - case '{': - /* step the read position forward if the ending was '}}}' */ - if (s.data[s.stack[s.index].data_pos] == '}' && - s.stack[s.index].del_end[0] == '}' && - s.stack[s.index].del_end[s.stack[s.index].del_end_len - 1] == '}') { - ++s.stack[s.index].data_pos; - } - /* fallthrough */ - case '&': - /* unescaped variable data */ - flag = 0; - /* fallthrough */ - case ':': /*fallthrough*/ - case '<': /*fallthrough*/ - ++beg; /*fallthrough*/ - default: - --end; - MUSTACHE_IGNORE_WHITESPACE(beg, 1); - MUSTACHE_IGNORE_WHITESPACE(end, -1); - ++end; - mustache__instruction_push( - &s, (mustache__instruction_s){ - .instruction = (flag ? MUSTACHE_WRITE_ARG - : MUSTACHE_WRITE_ARG_UNESCAPED), - .data = {.name_pos = beg - s.data, .name_len = end - beg}}); - break; - } - } - /* make sure all sections were closed */ - if (s.stack[s.index].open_sections) { - *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; - goto error; - } - /* move padding from section tail to post closure (adjust for padding - * changes) */ - flag = 0; - if (s.m->u.read_only.intruction_count && - s.i[s.m->u.read_only.intruction_count - 1].instruction == - MUSTACHE_PADDING_WRITE) { - --s.m->u.read_only.intruction_count; - flag = 1; - } - /* mark section length */ - mustache__data_segment_s seg = mustache__data_segment_read( - (uint8_t *)s.data + s.stack[s.index].data_start); - s.i[seg.inst_start].data.end = s.m->u.read_only.intruction_count; - /* add instruction closure */ - mustache__instruction_push( - &s, (mustache__instruction_s){.instruction = MUSTACHE_SECTION_END}); - /* TODO: pop any padding (if exists) */ - if (s.padding && s.padding + 1 == seg.inst_start) { - s.padding = s.i[s.padding].data.end; - mustache__instruction_push(&s, (mustache__instruction_s){ - .instruction = MUSTACHE_PADDING_POP, - }); - } - /* complete padding switch*/ - if (flag) { - mustache__instruction_push(&s, (mustache__instruction_s){ - .instruction = MUSTACHE_PADDING_WRITE, - }); - flag = 0; - } - /* pop stack */ - --s.index; - } - - s.m = realloc(s.m, sizeof(*s.m) + - (sizeof(*s.i) * s.m->u.read_only.intruction_count) + - s.data_len); - MUSTACHE_ASSERT(s.m, - "failed to allocate memory for consolidated mustache data"); - memcpy(MUSTACH2DATA(s.m), s.data, s.data_len); - free(s.data); - free(s.path); - - *args.err = MUSTACHE_OK; - return s.m; - -error: - free(s.data); - free(s.path); - free(s.m); - return NULL; -} - -#endif /* INCLUDE_MUSTACHE_IMPLEMENTATION */ - -#undef MUSTACHE_FUNC -#undef MUSTACH2INSTRUCTIONS -#undef MUSTACH2DATA -#undef MUSTACHE_OBJECT_OFFSET -#undef MUSTACHE_IGNORE_WHITESPACE - -#endif /* H_MUSTACHE_LOADR_H */ diff --git a/ext/iodine/redis_engine.c b/ext/iodine/redis_engine.c deleted file mode 100644 index d9716b8f..00000000 --- a/ext/iodine/redis_engine.c +++ /dev/null @@ -1,957 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ - -#define FIO_INCLUDE_LINKED_LIST -#define FIO_INCLUDE_STR -// #define DEBUG 1 -#include - -#include - -#include -#include - -#define REDIS_READ_BUFFER 8192 -/* ***************************************************************************** -The Redis Engine and Callbacks Object -***************************************************************************** */ - -typedef struct { - fio_pubsub_engine_s en; - struct redis_engine_internal_s { - fio_protocol_s protocol; - intptr_t uuid; - resp_parser_s parser; - void (*on_message)(struct redis_engine_internal_s *parser, FIOBJ msg); - FIOBJ str; - FIOBJ ary; - uint32_t ary_count; - uint16_t buf_pos; - uint16_t nesting; - } pub_data, sub_data; - subscription_s *publication_forwarder; - subscription_s *cmd_forwarder; - subscription_s *cmd_reply; - char *address; - char *port; - char *auth; - FIOBJ last_ch; - size_t auth_len; - size_t ref; - fio_ls_embd_s queue; - fio_lock_i lock; - fio_lock_i lock_connection; - uint8_t ping_int; - volatile uint8_t pub_sent; - volatile uint8_t flag; - uint8_t buf[]; -} redis_engine_s; - -typedef struct { - fio_ls_embd_s node; - void (*callback)(fio_pubsub_engine_s *e, FIOBJ reply, void *udata); - void *udata; - size_t cmd_len; - uint8_t cmd[]; -} redis_commands_s; - -/** converts from a publishing protocol to an `redis_engine_s`. */ -#define pub2redis(pr) FIO_LS_EMBD_OBJ(redis_engine_s, pub_data, (pr)) -/** converts from a subscribing protocol to an `redis_engine_s`. */ -#define sub2redis(pr) FIO_LS_EMBD_OBJ(redis_engine_s, sub_data, (pr)) - -/** converts from a `resp_parser_s` to the internal data structure. */ -#define parser2data(prsr) \ - FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, (prsr)) - -/* releases any resources used by an internal engine*/ -static inline void redis_internal_reset(struct redis_engine_internal_s *i) { - i->buf_pos = 0; - i->parser = (resp_parser_s){.obj_countdown = 0, .expecting = 0}; - fiobj_free((FIOBJ)fio_ct_if(i->ary == FIOBJ_INVALID, (uintptr_t)i->str, - (uintptr_t)i->ary)); - i->str = FIOBJ_INVALID; - i->ary = FIOBJ_INVALID; - i->ary_count = 0; - i->nesting = 0; - i->uuid = -1; -} - -/** cleans up and frees the engine data. */ -static inline void redis_free(redis_engine_s *r) { - if (fio_atomic_sub(&r->ref, 1)) - return; - FIO_LOG_DEBUG("freeing redis engine for %s:%s", r->address, r->port); - redis_internal_reset(&r->pub_data); - redis_internal_reset(&r->sub_data); - fiobj_free(r->last_ch); - while (fio_ls_embd_any(&r->queue)) { - fio_free( - FIO_LS_EMBD_OBJ(redis_commands_s, node, fio_ls_embd_pop(&r->queue))); - } - fio_unsubscribe(r->publication_forwarder); - r->publication_forwarder = NULL; - fio_unsubscribe(r->cmd_forwarder); - r->cmd_forwarder = NULL; - fio_unsubscribe(r->cmd_reply); - r->cmd_reply = NULL; - fio_free(r); -} - -/* ***************************************************************************** -Simple RESP formatting -***************************************************************************** */ - -inline static void fiobj2resp___internal(FIOBJ dest, FIOBJ obj) { - fio_str_info_s s; - switch (FIOBJ_TYPE(obj)) { - case FIOBJ_T_NULL: - fiobj_str_write(dest, "$-1\r\n", 5); - break; - case FIOBJ_T_ARRAY: - fiobj_str_write(dest, "*", 1); - fiobj_str_write_i(dest, fiobj_ary_count(obj)); - fiobj_str_write(dest, "\r\n", 2); - break; - case FIOBJ_T_HASH: - fiobj_str_write(dest, "*", 1); - fiobj_str_write_i(dest, fiobj_hash_count(obj) * 2); - fiobj_str_write(dest, "\r\n", 2); - break; - case FIOBJ_T_TRUE: - fiobj_str_write(dest, "$4\r\ntrue\r\n", 10); - break; - case FIOBJ_T_FALSE: - fiobj_str_write(dest, "$4\r\nfalse\r\n", 11); - break; -#if 0 - /* Numbers aren't as good for commands as one might think... */ - case FIOBJ_T_NUMBER: - fiobj_str_write(dest, ":", 1); - fiobj_str_write_i(dest, fiobj_obj2num(obj)); - fiobj_str_write(dest, "\r\n", 2); - break; -#else - case FIOBJ_T_NUMBER: /* overflow */ -#endif - case FIOBJ_T_FLOAT: /* overflow */ - case FIOBJ_T_UNKNOWN: /* overflow */ - case FIOBJ_T_STRING: /* overflow */ - case FIOBJ_T_DATA: - s = fiobj_obj2cstr(obj); - fiobj_str_write(dest, "$", 1); - fiobj_str_write_i(dest, s.len); - fiobj_str_write(dest, "\r\n", 2); - fiobj_str_write(dest, s.data, s.len); - fiobj_str_write(dest, "\r\n", 2); - break; - } -} - -static int fiobj2resp_task(FIOBJ o, void *dest_) { - if (fiobj_hash_key_in_loop()) - fiobj2resp___internal((FIOBJ)dest_, fiobj_hash_key_in_loop()); - fiobj2resp___internal((FIOBJ)dest_, o); - return 0; -} - -/** - * Converts FIOBJ objects into a RESP string (client mode). - */ -static FIOBJ fiobj2resp(FIOBJ dest, FIOBJ obj) { - fiobj_each2(obj, fiobj2resp_task, (void *)dest); - return dest; -} - -/** - * Converts FIOBJ objects into a RESP string (client mode). - * - * Don't call `fiobj_free`, object will self-destruct. - */ -static inline FIOBJ fiobj2resp_tmp(FIOBJ obj) { - return fiobj2resp(fiobj_str_tmp(), obj); -} - -/* ***************************************************************************** -RESP parser callbacks -***************************************************************************** */ - -/** a local static callback, called when a parser / protocol error occurs. */ -static int resp_on_parser_error(resp_parser_s *parser) { - struct redis_engine_internal_s *i = parser2data(parser); - FIO_LOG_ERROR("(redis) parser error - attempting to restart connection.\n"); - fio_close(i->uuid); - return -1; -} - -/** a local static callback, called when the RESP message is complete. */ -static int resp_on_message(resp_parser_s *parser) { - struct redis_engine_internal_s *i = parser2data(parser); - FIOBJ msg = i->ary ? i->ary : i->str; - i->on_message(i, msg); - /* cleanup */ - fiobj_free(msg); - i->ary = FIOBJ_INVALID; - i->str = FIOBJ_INVALID; - return 0; -} - -/** a local helper to add parsed objects to the data store. */ -static inline void resp_add_obj(struct redis_engine_internal_s *dest, FIOBJ o) { - if (dest->ary) { - fiobj_ary_push(dest->ary, o); - --dest->ary_count; - if (!dest->ary_count && dest->nesting) { - FIOBJ tmp = fiobj_ary_shift(dest->ary); - dest->ary_count = fiobj_obj2num(tmp); - fiobj_free(tmp); - dest->ary = fiobj_ary_shift(dest->ary); - --dest->nesting; - } - } - dest->str = o; -} - -/** a local static callback, called when a Number object is parsed. */ -static int resp_on_number(resp_parser_s *parser, int64_t num) { - struct redis_engine_internal_s *data = parser2data(parser); - resp_add_obj(data, fiobj_num_new(num)); - return 0; -} -/** a local static callback, called when a OK message is received. */ -static int resp_on_okay(resp_parser_s *parser) { - struct redis_engine_internal_s *data = parser2data(parser); - resp_add_obj(data, fiobj_true()); - return 0; -} -/** a local static callback, called when NULL is received. */ -static int resp_on_null(resp_parser_s *parser) { - struct redis_engine_internal_s *data = parser2data(parser); - resp_add_obj(data, fiobj_null()); - return 0; -} - -/** - * a local static callback, called when a String should be allocated. - * - * `str_len` is the expected number of bytes that will fill the final string - * object, without any NUL byte marker (the string might be binary). - * - * If this function returns any value besides 0, parsing is stopped. - */ -static int resp_on_start_string(resp_parser_s *parser, size_t str_len) { - struct redis_engine_internal_s *data = parser2data(parser); - resp_add_obj(data, fiobj_str_buf(str_len)); - return 0; -} -/** a local static callback, called as String objects are streamed. */ -static int resp_on_string_chunk(resp_parser_s *parser, void *data, size_t len) { - struct redis_engine_internal_s *i = parser2data(parser); - fiobj_str_write(i->str, data, len); - return 0; -} -/** a local static callback, called when a String object had finished - * streaming. - */ -static int resp_on_end_string(resp_parser_s *parser) { - return 0; - (void)parser; -} - -/** a local static callback, called an error message is received. */ -static int resp_on_err_msg(resp_parser_s *parser, void *data, size_t len) { - struct redis_engine_internal_s *i = parser2data(parser); - resp_add_obj(i, fiobj_str_new(data, len)); - return 0; -} - -/** - * a local static callback, called when an Array should be allocated. - * - * `array_len` is the expected number of objects that will fill the Array - * object. - * - * There's no `resp_on_end_array` callback since the RESP protocol assumes the - * message is finished along with the Array (`resp_on_message` is called). - * However, just in case a non-conforming client/server sends nested Arrays, - * the callback should test against possible overflow or nested Array endings. - * - * If this function returns any value besides 0, parsing is stopped. - */ -static int resp_on_start_array(resp_parser_s *parser, size_t array_len) { - struct redis_engine_internal_s *i = parser2data(parser); - if (i->ary) { - ++i->nesting; - FIOBJ tmp = fiobj_ary_new2(array_len + 2); - fiobj_ary_push(tmp, fiobj_num_new(i->ary_count)); - fiobj_ary_push(tmp, fiobj_num_new(i->ary)); - i->ary = tmp; - } else { - i->ary = fiobj_ary_new2(array_len + 2); - } - i->ary_count = array_len; - return 0; -} - -/* ***************************************************************************** -Publication and Command Handling -***************************************************************************** */ - -/* the deferred callback handler */ -static void redis_perform_callback(void *e, void *cmd_) { - redis_commands_s *cmd = cmd_; - FIOBJ reply = (FIOBJ)cmd->node.next; - if (cmd->callback) - cmd->callback(e, reply, cmd->udata); - fiobj_free(reply); - FIO_LOG_DEBUG("Handled: %s\n", cmd->cmd); - fio_free(cmd); -} - -/* send command within lock, to ensure flag integrity */ -static void redis_send_next_command_unsafe(redis_engine_s *r) { - if (!r->pub_sent && fio_ls_embd_any(&r->queue)) { - r->pub_sent = 1; - redis_commands_s *cmd = - FIO_LS_EMBD_OBJ(redis_commands_s, node, r->queue.next); - fio_write2(r->pub_data.uuid, .data.buffer = cmd->cmd, - .length = cmd->cmd_len, .after.dealloc = FIO_DEALLOC_NOOP); - FIO_LOG_DEBUG("(redis %d) Sending (%zu bytes):\n%s\n", (int)getpid(), - cmd->cmd_len, cmd->cmd); - } -} - -/* attach a command to the queue */ -static void redis_attach_cmd(redis_engine_s *r, redis_commands_s *cmd) { - fio_lock(&r->lock); - fio_ls_embd_push(&r->queue, &cmd->node); - redis_send_next_command_unsafe(r); - fio_unlock(&r->lock); -} - -/** a local static callback, called when the RESP message is complete. */ -static void resp_on_pub_message(struct redis_engine_internal_s *i, FIOBJ msg) { - redis_engine_s *r = pub2redis(i); - // #if DEBUG - if (FIO_LOG_LEVEL >= FIO_LOG_LEVEL_DEBUG) { - FIOBJ json = fiobj_obj2json(msg, 1); - FIO_LOG_DEBUG("Redis reply:\n%s\n", fiobj_obj2cstr(json).data); - fiobj_free(json); - } - // #endif - /* publishing / command parser */ - fio_lock(&r->lock); - fio_ls_embd_s *node = fio_ls_embd_shift(&r->queue); - r->pub_sent = 0; - redis_send_next_command_unsafe(r); - fio_unlock(&r->lock); - if (!node) { - /* TODO: possible ping? from server?! not likely... */ - FIO_LOG_WARNING("(redis %d) received a reply when no command was sent.", - (int)getpid()); - return; - } - node->next = (void *)fiobj_dup(msg); - fio_defer(redis_perform_callback, &r->en, - FIO_LS_EMBD_OBJ(redis_commands_s, node, node)); -} - -/* ***************************************************************************** -Subscription Message Handling -***************************************************************************** */ - -/** a local static callback, called when the RESP message is complete. */ -static void resp_on_sub_message(struct redis_engine_internal_s *i, FIOBJ msg) { - redis_engine_s *r = sub2redis(i); - /* subscriotion parser */ - if (FIOBJ_TYPE(msg) != FIOBJ_T_ARRAY) { - if (FIOBJ_TYPE(msg) != FIOBJ_T_STRING || fiobj_obj2cstr(msg).len != 4 || - fiobj_obj2cstr(msg).data[0] != 'P') { - FIO_LOG_WARNING("(redis) unexpected data format in " - "subscription stream (%zu bytes):\n %s\n", - fiobj_obj2cstr(msg).len, fiobj_obj2cstr(msg).data); - } - } else { - // FIOBJ *ary = fiobj_ary2ptr(msg); - // for (size_t i = 0; i < fiobj_ary_count(msg); ++i) { - // fio_str_info_s tmp = fiobj_obj2cstr(ary[i]); - // fprintf(stderr, "(%lu) %s\n", (unsigned long)i, tmp.data); - // } - fio_str_info_s tmp = fiobj_obj2cstr(fiobj_ary_index(msg, 0)); - if (tmp.len == 7) { /* "message" */ - fiobj_free(r->last_ch); - r->last_ch = fiobj_dup(fiobj_ary_index(msg, 1)); - fio_publish(.channel = fiobj_obj2cstr(r->last_ch), - .message = fiobj_obj2cstr(fiobj_ary_index(msg, 2)), - .engine = FIO_PUBSUB_CLUSTER); - } else if (tmp.len == 8) { /* "pmessage" */ - if (!fiobj_iseq(r->last_ch, fiobj_ary_index(msg, 2))) - fio_publish(.channel = fiobj_obj2cstr(fiobj_ary_index(msg, 2)), - .message = fiobj_obj2cstr(fiobj_ary_index(msg, 3)), - .engine = FIO_PUBSUB_CLUSTER); - } - } -} - -/* ***************************************************************************** -Connection Callbacks (fio_protocol_s) and Engine -***************************************************************************** */ - -/** defined later - connects to Redis */ -static void redis_connect(void *r, void *i); - -#define defer_redis_connect(r, i) \ - do { \ - fio_atomic_add(&(r)->ref, 1); \ - fio_defer(redis_connect, (r), (i)); \ - } while (0); - -/** Called when a data is available, but will not run concurrently */ -static void redis_on_data(intptr_t uuid, fio_protocol_s *pr) { - struct redis_engine_internal_s *internal = - (struct redis_engine_internal_s *)pr; - uint8_t *buf; - if (internal->on_message == resp_on_sub_message) { - buf = sub2redis(pr)->buf + REDIS_READ_BUFFER; - } else { - buf = pub2redis(pr)->buf; - } - ssize_t i = fio_read(uuid, buf + internal->buf_pos, - REDIS_READ_BUFFER - internal->buf_pos); - if (i <= 0) - return; - - internal->buf_pos += i; - i = resp_parse(&internal->parser, buf, internal->buf_pos); - if (i) { - memmove(buf, buf + internal->buf_pos - i, i); - } - internal->buf_pos = i; -} - -/** Called when the connection was closed, but will not run concurrently */ -static void redis_on_close(intptr_t uuid, fio_protocol_s *pr) { - struct redis_engine_internal_s *internal = - (struct redis_engine_internal_s *)pr; - redis_internal_reset(internal); - redis_engine_s *r; - if (internal->on_message == resp_on_sub_message) { - r = sub2redis(pr); - fiobj_free(r->last_ch); - r->last_ch = FIOBJ_INVALID; - if (r->flag) { - /* reconnection for subscription connection. */ - if (uuid != -1) { - FIO_LOG_WARNING("(redis %d) subscription connection lost. " - "Reconnecting...", - (int)getpid()); - } - fio_atomic_sub(&r->ref, 1); - defer_redis_connect(r, internal); - } else { - redis_free(r); - } - } else { - r = pub2redis(pr); - if (r->flag && uuid != -1) { - FIO_LOG_WARNING("(redis %d) publication connection lost. " - "Reconnecting...", - (int)getpid()); - } - r->pub_sent = 0; - fio_close(r->sub_data.uuid); - redis_free(r); - } - (void)uuid; -} - -/** Called before the facil.io reactor is shut down. */ -static uint8_t redis_on_shutdown(intptr_t uuid, fio_protocol_s *pr) { - fio_write2(uuid, .data.buffer = "*1\r\n$4\r\nQUIT\r\n", .length = 14, - .after.dealloc = FIO_DEALLOC_NOOP); - return 0; - (void)pr; -} - -/** Called on connection timeout. */ -static void redis_sub_ping(intptr_t uuid, fio_protocol_s *pr) { - fio_write2(uuid, .data.buffer = "*1\r\n$4\r\nPING\r\n", .length = 14, - .after.dealloc = FIO_DEALLOC_NOOP); - (void)pr; -} - -/** Called on connection timeout. */ -static void redis_pub_ping(intptr_t uuid, fio_protocol_s *pr) { - redis_engine_s *r = pub2redis(pr); - if (fio_ls_embd_any(&r->queue)) { - FIO_LOG_WARNING("(redis) Redis server unresponsive, disconnecting."); - fio_close(uuid); - return; - } - redis_commands_s *cmd = fio_malloc(sizeof(*cmd) + 15); - FIO_ASSERT_ALLOC(cmd); - *cmd = (redis_commands_s){.cmd_len = 14}; - memcpy(cmd->cmd, "*1\r\n$4\r\nPING\r\n\0", 15); - redis_attach_cmd(r, cmd); -} - -/* ***************************************************************************** -Connecting to Redis -***************************************************************************** */ - -static void redis_on_auth(fio_pubsub_engine_s *e, FIOBJ reply, void *udata) { - if (FIOBJ_TYPE_IS(reply, FIOBJ_T_TRUE)) { - fio_str_info_s s = fiobj_obj2cstr(reply); - FIO_LOG_WARNING("(redis) Authentication FAILED." - " %.*s", - (int)s.len, s.data); - } - (void)e; - (void)udata; -} - -static void redis_on_connect(intptr_t uuid, void *i_) { - struct redis_engine_internal_s *i = i_; - redis_engine_s *r; - i->uuid = uuid; - - if (i->on_message == resp_on_sub_message) { - r = sub2redis(i); - if (r->auth_len) { - fio_write2(uuid, .data.buffer = r->auth, .length = r->auth_len, - .after.dealloc = FIO_DEALLOC_NOOP); - } - fio_pubsub_reattach(&r->en); - if (r->pub_data.uuid == -1) { - defer_redis_connect(r, &r->pub_data); - } - FIO_LOG_INFO("(redis %d) subscription connection established.", - (int)getpid()); - } else { - r = pub2redis(i); - if (r->auth_len) { - redis_commands_s *cmd = fio_malloc(sizeof(*cmd) + r->auth_len); - FIO_ASSERT_ALLOC(cmd); - *cmd = - (redis_commands_s){.cmd_len = r->auth_len, .callback = redis_on_auth}; - memcpy(cmd->cmd, r->auth, r->auth_len); - fio_lock(&r->lock); - r->pub_sent = 0; - fio_ls_embd_unshift(&r->queue, &cmd->node); - redis_send_next_command_unsafe(r); - fio_unlock(&r->lock); - } else { - fio_lock(&r->lock); - r->pub_sent = 0; - redis_send_next_command_unsafe(r); - fio_unlock(&r->lock); - } - FIO_LOG_INFO("(redis %d) publication connection established.", - (int)getpid()); - } - - i->protocol.rsv = 0; - fio_attach(uuid, &i->protocol); - fio_timeout_set(uuid, r->ping_int); - - return; -} - -static void redis_on_connect_failed(intptr_t uuid, void *i_) { - struct redis_engine_internal_s *i = i_; - i->uuid = -1; - i->protocol.on_close(-1, &i->protocol); - (void)uuid; -} - -static void redis_connect(void *r_, void *i_) { - redis_engine_s *r = r_; - struct redis_engine_internal_s *i = i_; - fio_lock(&r->lock_connection); - if (r->flag == 0 || i->uuid != -1 || !fio_is_running()) { - fio_unlock(&r->lock_connection); - redis_free(r); - return; - } - // fio_atomic_add(&r->ref, 1); - i->uuid = fio_connect(.address = r->address, .port = r->port, - .on_connect = redis_on_connect, .udata = i, - .on_fail = redis_on_connect_failed); - fio_unlock(&r->lock_connection); -} - -/* ***************************************************************************** -Engine / Bridge Callbacks (Root Process) -***************************************************************************** */ - -static void redis_on_subscribe_root(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, - fio_match_fn match) { - redis_engine_s *r = (redis_engine_s *)eng; - if (r->sub_data.uuid != -1) { - FIOBJ cmd = fiobj_str_buf(96 + channel.len); - if (match == FIO_MATCH_GLOB) - fiobj_str_write(cmd, "*2\r\n$10\r\nPSUBSCRIBE\r\n$", 22); - else - fiobj_str_write(cmd, "*2\r\n$9\r\nSUBSCRIBE\r\n$", 20); - fiobj_str_write_i(cmd, channel.len); - fiobj_str_write(cmd, "\r\n", 2); - fiobj_str_write(cmd, channel.data, channel.len); - fiobj_str_write(cmd, "\r\n", 2); - // { - // fio_str_info_s s = fiobj_obj2cstr(cmd); - // fprintf(stderr, "(%d) Sending Subscription (%p):\n%s\n", getpid(), - // (void *)r->sub_data.uuid, s.data); - // } - fiobj_send_free(r->sub_data.uuid, cmd); - } -} - -static void redis_on_unsubscribe_root(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, - fio_match_fn match) { - redis_engine_s *r = (redis_engine_s *)eng; - if (r->sub_data.uuid != -1) { - fio_str_s *cmd = fio_str_new2(); - fio_str_capa_assert(cmd, 96 + channel.len); - if (match == FIO_MATCH_GLOB) - fio_str_write(cmd, "*2\r\n$12\r\nPUNSUBSCRIBE\r\n$", 24); - else - fio_str_write(cmd, "*2\r\n$11\r\nUNSUBSCRIBE\r\n$", 23); - fio_str_write_i(cmd, channel.len); - fio_str_write(cmd, "\r\n", 2); - fio_str_write(cmd, channel.data, channel.len); - fio_str_write(cmd, "\r\n", 2); - // { - // fio_str_info_s s = fio_str_info(cmd); - // fprintf(stderr, "(%d) Cancel Subscription (%p):\n%s\n", getpid(), - // (void *)r->sub_data.uuid, s.data); - // } - fio_str_send_free2(r->sub_data.uuid, cmd); - } -} - -static void redis_on_publish_root(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, fio_str_info_s msg, - uint8_t is_json) { - redis_engine_s *r = (redis_engine_s *)eng; - redis_commands_s *cmd = fio_malloc(sizeof(*cmd) + channel.len + msg.len + 96); - FIO_ASSERT_ALLOC(cmd); - *cmd = (redis_commands_s){.cmd_len = 0}; - memcpy(cmd->cmd, "*3\r\n$7\r\nPUBLISH\r\n$", 18); - char *buf = (char *)cmd->cmd + 18; - buf += fio_ltoa((void *)buf, channel.len, 10); - *buf++ = '\r'; - *buf++ = '\n'; - memcpy(buf, channel.data, channel.len); - buf += channel.len; - *buf++ = '\r'; - *buf++ = '\n'; - *buf++ = '$'; - buf += fio_ltoa(buf, msg.len, 10); - *buf++ = '\r'; - *buf++ = '\n'; - memcpy(buf, msg.data, msg.len); - buf += msg.len; - *buf++ = '\r'; - *buf++ = '\n'; - *buf = 0; - FIO_LOG_DEBUG("(%d) Publishing:\n%s", (int)getpid(), cmd->cmd); - cmd->cmd_len = (uintptr_t)buf - (uintptr_t)(cmd + 1); - redis_attach_cmd(r, cmd); - return; - (void)is_json; -} - -/* ***************************************************************************** -Engine / Bridge Stub Callbacks (Child Process) -***************************************************************************** */ - -static void redis_on_mock_subscribe_child(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, - fio_match_fn match) { - /* do nothing, root process is notified about (un)subscriptions by facil.io */ - (void)eng; - (void)channel; - (void)match; -} - -static void redis_on_publish_child(const fio_pubsub_engine_s *eng, - fio_str_info_s channel, fio_str_info_s msg, - uint8_t is_json) { - /* attach engine data to channel (prepend) */ - fio_str_s tmp = FIO_STR_INIT; - /* by using fio_str_s, short names are allocated on the stack */ - fio_str_info_s tmp_info = fio_str_resize(&tmp, channel.len + 8); - fio_u2str64(tmp_info.data, (uintptr_t)eng); - memcpy(tmp_info.data + 8, channel.data, channel.len); - /* forward publication request to Root */ - fio_publish(.filter = -1, .channel = tmp_info, .message = msg, - .engine = FIO_PUBSUB_ROOT, .is_json = is_json); - fio_str_free(&tmp); - (void)eng; -} - -/* ***************************************************************************** -Root Publication Handler -***************************************************************************** */ - -/* listens to filter -1 and publishes and messages */ -static void redis_on_internal_publish(fio_msg_s *msg) { - if (msg->channel.len < 8) - return; /* internal error, unexpected data */ - void *en = (void *)(uintptr_t)fio_str2u64(msg->channel.data); - if (en != msg->udata1) - return; /* should be delivered by a different engine */ - /* step after the engine data */ - msg->channel.len -= 8; - msg->channel.data += 8; - /* forward to publishing */ - FIO_LOG_DEBUG("Forwarding to engine %p, on channel %s", msg->udata1, - msg->channel.data); - redis_on_publish_root(msg->udata1, msg->channel, msg->msg, msg->is_json); -} - -/* ***************************************************************************** -Sending commands using the Root connection -***************************************************************************** */ - -/* callback from the Redis reply */ -static void redis_forward_reply(fio_pubsub_engine_s *e, FIOBJ reply, - void *udata) { - uint8_t *data = udata; - fio_pubsub_engine_s *engine = (fio_pubsub_engine_s *)(uintptr_t)fio_str2u64(data + 0); - void *callback = (void *)(uintptr_t)fio_str2u64(data + 8); - if (engine != e || !callback) { - FIO_LOG_DEBUG("Redis reply not forwarded (callback: %p)", callback); - return; - } - int32_t pid = (int32_t)fio_str2u32(data + 24); - FIOBJ rp = fiobj_obj2json(reply, 0); - fio_publish(.filter = (-10 - (int32_t)pid), .channel.data = (char *)data, - .channel.len = 28, .message = fiobj_obj2cstr(rp), .is_json = 1); - fiobj_free(rp); -} - -/* listens to channel -2 for commands that need to be sent (only ROOT) */ -static void redis_on_internal_cmd(fio_msg_s *msg) { - // void*(void *)fio_str2u64(msg->msg.data); - fio_pubsub_engine_s *engine = - (fio_pubsub_engine_s *)(uintptr_t)fio_str2u64(msg->channel.data + 0); - if (engine != msg->udata1) { - return; - } - redis_commands_s *cmd = fio_malloc(sizeof(*cmd) + msg->msg.len + 1 + 28); - FIO_ASSERT_ALLOC(cmd); - *cmd = (redis_commands_s){.callback = redis_forward_reply, - .udata = (cmd->cmd + msg->msg.len + 1), - .cmd_len = msg->msg.len}; - memcpy(cmd->cmd, msg->msg.data, msg->msg.len); - memcpy(cmd->cmd + msg->msg.len + 1, msg->channel.data, 28); - redis_attach_cmd((redis_engine_s *)engine, cmd); - // fprintf(stderr, " *** Attached CMD (%d) ***\n%s\n", getpid(), cmd->cmd); -} - -/* Listens on filter `-10 -getpid()` for incoming reply data */ -static void redis_on_internal_reply(fio_msg_s *msg) { - fio_pubsub_engine_s *engine = - (fio_pubsub_engine_s *)(uintptr_t)fio_str2u64(msg->channel.data + 0); - if (engine != msg->udata1) { - FIO_LOG_DEBUG("Redis reply not forwarded (engine mismatch: %p != %p)", - (void *)engine, msg->udata1); - return; - } - FIOBJ reply; - fiobj_json2obj(&reply, msg->msg.data, msg->msg.len); - void (*callback)(fio_pubsub_engine_s *, FIOBJ, void *) = (void (*)( - fio_pubsub_engine_s *, FIOBJ, void *))(uintptr_t)fio_str2u64(msg->channel.data + 8); - void *udata = (void *)(uintptr_t)fio_str2u64(msg->channel.data + 16); - callback(engine, reply, udata); - fiobj_free(reply); -} - -/* publishes a Redis command to Root's filter -2 */ -intptr_t redis_engine_send(fio_pubsub_engine_s *engine, FIOBJ command, - void (*callback)(fio_pubsub_engine_s *e, FIOBJ reply, - void *udata), - void *udata) { - if ((uintptr_t)engine < 4) { - FIO_LOG_WARNING("(redis send) trying to use one of the core engines"); - return -1; - } - // if(fio_is_master()) { - // FIOBJ resp = fiobj2resp_tmp(fio_str_info_s obj1, FIOBJ obj2); - // TODO... - // } else { - /* forward publication request to Root */ - fio_str_s tmp = FIO_STR_INIT; - fio_str_info_s ti = fio_str_resize(&tmp, 28); - /* combine metadata */ - fio_u2str64(ti.data + 0, (uintptr_t)engine); - fio_u2str64(ti.data + 8, (uintptr_t)callback); - fio_u2str64(ti.data + 16, (uintptr_t)udata); - fio_u2str32(ti.data + 24, (uint32_t)getpid()); - FIOBJ cmd = fiobj2resp_tmp(command); - fio_publish(.filter = -2, .channel = ti, .message = fiobj_obj2cstr(cmd), - .engine = FIO_PUBSUB_ROOT, .is_json = 0); - fio_str_free(&tmp); - // } - return 0; -} - -/* ***************************************************************************** -Redis Engine Creation -***************************************************************************** */ - -static void redis_on_facil_start(void *r_) { - redis_engine_s *r = r_; - r->flag = 1; - if (!fio_is_valid(r->sub_data.uuid)) { - defer_redis_connect(r, &r->sub_data); - } -} -static void redis_on_facil_shutdown(void *r_) { - redis_engine_s *r = r_; - r->flag = 0; -} - -static void redis_on_engine_fork(void *r_) { - redis_engine_s *r = r_; - r->flag = 0; - r->lock = FIO_LOCK_INIT; - fio_force_close(r->sub_data.uuid); - r->sub_data.uuid = -1; - fio_force_close(r->pub_data.uuid); - r->pub_data.uuid = -1; - while (fio_ls_embd_any(&r->queue)) { - redis_commands_s *cmd = - FIO_LS_EMBD_OBJ(redis_commands_s, node, fio_ls_embd_pop(&r->queue)); - fio_free(cmd); - } - r->en = (fio_pubsub_engine_s){ - .subscribe = redis_on_mock_subscribe_child, - .unsubscribe = redis_on_mock_subscribe_child, - .publish = redis_on_publish_child, - }; - fio_unsubscribe(r->publication_forwarder); - r->publication_forwarder = NULL; - fio_unsubscribe(r->cmd_forwarder); - r->cmd_forwarder = NULL; - fio_unsubscribe(r->cmd_reply); - r->cmd_reply = - fio_subscribe(.filter = -10 - (int32_t)getpid(), - .on_message = redis_on_internal_reply, .udata1 = r); -} - -fio_pubsub_engine_s *redis_engine_create -FIO_IGNORE_MACRO(struct redis_engine_create_args args) { - if (getpid() != fio_parent_pid()) { - FIO_LOG_FATAL("(redis) Redis engine initialization can only " - "be performed in the Root process."); - kill(0, SIGINT); - fio_stop(); - return NULL; - } - if (!args.address.len && args.address.data) - args.address.len = strlen(args.address.data); - if (!args.port.len && args.port.data) - args.port.len = strlen(args.port.data); - if (!args.auth.len && args.auth.data) { - args.auth.len = strlen(args.auth.data); - } - - if (!args.address.data || !args.address.len) { - args.address = (fio_str_info_s){.len = 9, .data = (char *)"localhost"}; - } - if (!args.port.data || !args.port.len) { - args.port = (fio_str_info_s){.len = 4, .data = (char *)"6379"}; - } - redis_engine_s *r = - fio_malloc(sizeof(*r) + args.port.len + 1 + args.address.len + 1 + - args.auth.len + 1 + (REDIS_READ_BUFFER * 2)); - FIO_ASSERT_ALLOC(r); - *r = (redis_engine_s){ - .en = - { - .subscribe = redis_on_subscribe_root, - .unsubscribe = redis_on_unsubscribe_root, - .publish = redis_on_publish_root, - }, - .pub_data = - { - .protocol = - { - .on_data = redis_on_data, - .on_close = redis_on_close, - .on_shutdown = redis_on_shutdown, - .ping = redis_pub_ping, - }, - .uuid = -1, - .on_message = resp_on_pub_message, - }, - .sub_data = - { - .protocol = - { - .on_data = redis_on_data, - .on_close = redis_on_close, - .on_shutdown = redis_on_shutdown, - .ping = redis_sub_ping, - }, - .on_message = resp_on_sub_message, - .uuid = -1, - }, - .publication_forwarder = - fio_subscribe(.filter = -1, .udata1 = r, - .on_message = redis_on_internal_publish), - .cmd_forwarder = fio_subscribe(.filter = -2, .udata1 = r, - .on_message = redis_on_internal_cmd), - .cmd_reply = - fio_subscribe(.filter = -10 - (uint32_t)getpid(), .udata1 = r, - .on_message = redis_on_internal_reply), - .address = ((char *)(r + 1) + (REDIS_READ_BUFFER * 2)), - .port = - ((char *)(r + 1) + (REDIS_READ_BUFFER * 2) + args.address.len + 1), - .auth = ((char *)(r + 1) + (REDIS_READ_BUFFER * 2) + args.address.len + - args.port.len + 2), - .auth_len = args.auth.len, - .ref = 1, - .queue = FIO_LS_INIT(r->queue), - .lock = FIO_LOCK_INIT, - .lock_connection = FIO_LOCK_INIT, - .ping_int = args.ping_interval, - .flag = 1, - }; - memcpy(r->address, args.address.data, args.address.len); - memcpy(r->port, args.port.data, args.port.len); - if (args.auth.len) - memcpy(r->auth, args.auth.data, args.auth.len); - fio_pubsub_attach(&r->en); - redis_on_facil_start(r); - fio_state_callback_add(FIO_CALL_IN_CHILD, redis_on_engine_fork, r); - fio_state_callback_add(FIO_CALL_ON_SHUTDOWN, redis_on_facil_shutdown, r); - /* if restarting */ - fio_state_callback_add(FIO_CALL_PRE_START, redis_on_facil_start, r); - - FIO_LOG_DEBUG("Redis engine initialized %p", (void *)r); - return &r->en; -} - -/* ***************************************************************************** -Redis Engine Destruction -***************************************************************************** */ - -void redis_engine_destroy(fio_pubsub_engine_s *engine) { - redis_engine_s *r = (redis_engine_s *)engine; - r->flag = 0; - fio_pubsub_detach(&r->en); - fio_state_callback_remove(FIO_CALL_IN_CHILD, redis_on_engine_fork, r); - fio_state_callback_remove(FIO_CALL_ON_SHUTDOWN, redis_on_facil_shutdown, r); - fio_state_callback_remove(FIO_CALL_PRE_START, redis_on_facil_start, r); - FIO_LOG_DEBUG("Redis engine destroyed %p", (void *)r); - redis_free(r); -} diff --git a/ext/iodine/redis_engine.h b/ext/iodine/redis_engine.h deleted file mode 100644 index 5870ee44..00000000 --- a/ext/iodine/redis_engine.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_REDIS_ENGINE_H -#define H_REDIS_ENGINE_H - -#include -#include - -/* support C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/** possible arguments for the `redis_engine_create` function call */ -struct redis_engine_create_args { - /** Redis server's address, defaults to localhost. */ - fio_str_info_s address; - /** Redis server's port, defaults to 6379. */ - fio_str_info_s port; - /** Redis server's password, if any. */ - fio_str_info_s auth; - /** A `ping` will be sent every `ping_interval` interval or inactivity. */ - uint8_t ping_interval; -}; - -/** - * See the {fio.h} file for documentation about engines. - * - * The engine is active only after facil.io starts running. - * - * A `ping` will be sent every `ping_interval` interval or inactivity. The - * default value (0) will fallback to facil.io's maximum time of inactivity (5 - * minutes) before polling on the connection's protocol. - * - * function names speak for themselves ;-) - * - * Note: The Redis engine assumes it will stay alive until all the messages and - * callbacks have been called (or facil.io exits)... If the engine is destroyed - * midway, memory leaks might occur (and little puppies might cry). - */ -fio_pubsub_engine_s *redis_engine_create(struct redis_engine_create_args); -#define redis_engine_create(...) \ - redis_engine_create((struct redis_engine_create_args){__VA_ARGS__}) - -/** - * Sends a Redis command through the engine's connection. - * - * The response will be sent back using the optional callback. `udata` is passed - * along untouched. - * - * The message will be resent on network failures, until a response validates - * the fact that the command was sent (or the engine is destroyed). - * - * Note: NEVER call Pub/Sub commands using this function, as it will violate the - * Redis connection's protocol (best case scenario, a disconnection will occur - * before and messages are lost). - */ -intptr_t redis_engine_send(fio_pubsub_engine_s *engine, FIOBJ command, - void (*callback)(fio_pubsub_engine_s *e, FIOBJ reply, - void *udata), - void *udata); - -/** - * See the {pubsub.h} file for documentation about engines. - * - * function names speak for themselves ;-) - */ -void redis_engine_destroy(fio_pubsub_engine_s *engine); - -/* support C++ */ -#ifdef __cplusplus -} -#endif - -#endif /* H_REDIS_ENGINE_H */ diff --git a/ext/iodine/resp_parser.h b/ext/iodine/resp_parser.h deleted file mode 100644 index 9a348a91..00000000 --- a/ext/iodine/resp_parser.h +++ /dev/null @@ -1,317 +0,0 @@ -/* -Copyright: Boaz segev, 2016-2019 -License: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_RESP_PARSER_H -/** - * This single file library is a RESP parser for Redis connections. - * - * To use this file, the `.c` file in which this file is included MUST define a - * number of callbacks, as later inticated. - * - * When feeding the parser, the parser will inform of any trailing bytes (bytes - * at the end of the buffer that could not be parsed). These bytes should be - * resent to the parser along with more data. Zero is a valid return value. - * - * Note: mostly, callback return vaslues are ignored. - */ -#define H_RESP_PARSER_H - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include - -/* ***************************************************************************** -The Parser -***************************************************************************** */ - -typedef struct resp_parser_s { - /* for internal use - (array / object countdown) */ - intptr_t obj_countdown; - /* for internal use - (string byte consumption) */ - intptr_t expecting; -} resp_parser_s; - -/** - * Returns the number of bytes to be resent. i.e., for a return value 5, the - * last 5 bytes in the buffer need to be resent to the parser. - */ -static size_t resp_parse(resp_parser_s *parser, const void *buffer, - size_t length); - -/* ***************************************************************************** -Required Parser Callbacks (to be defined by the including file) -***************************************************************************** */ - -/** a local static callback, called when the RESP message is complete. */ -static int resp_on_message(resp_parser_s *parser); - -/** a local static callback, called when a Number object is parsed. */ -static int resp_on_number(resp_parser_s *parser, int64_t num); -/** a local static callback, called when a OK message is received. */ -static int resp_on_okay(resp_parser_s *parser); -/** a local static callback, called when NULL is received. */ -static int resp_on_null(resp_parser_s *parser); - -/** - * a local static callback, called when a String should be allocated. - * - * `str_len` is the expected number of bytes that will fill the final string - * object, without any NUL byte marker (the string might be binary). - * - * If this function returns any value besides 0, parsing is stopped. - */ -static int resp_on_start_string(resp_parser_s *parser, size_t str_len); -/** a local static callback, called as String objects are streamed. */ -static int resp_on_string_chunk(resp_parser_s *parser, void *data, size_t len); -/** a local static callback, called when a String object had finished streaming. - */ -static int resp_on_end_string(resp_parser_s *parser); - -/** a local static callback, called an error message is received. */ -static int resp_on_err_msg(resp_parser_s *parser, void *data, size_t len); - -/** - * a local static callback, called when an Array should be allocated. - * - * `array_len` is the expected number of objects that will fill the Array - * object. - * - * There's no `resp_on_end_array` callback since the RESP protocol assumes the - * message is finished along with the Array (`resp_on_message` is called). - * However, just in case a non-conforming client/server sends nested Arrays, the - * callback should test against possible overflow or nested Array endings. - * - * If this function returns any value besides 0, parsing is stopped. - */ -static int resp_on_start_array(resp_parser_s *parser, size_t array_len); - -/** a local static callback, called when a parser / protocol error occurs. */ -static int resp_on_parser_error(resp_parser_s *parser); - -/* ***************************************************************************** -Seeking the new line... -***************************************************************************** */ - -#if FIO_MEMCHAR - -/** - * This seems to be faster on some systems, especially for smaller distances. - * - * On newer systems, `memchr` should be faster. - */ -static inline int seek2ch(uint8_t **buffer, register const uint8_t *limit, - const uint8_t c) { - if (**buffer == c) - return 1; - -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) - /* too short for this mess */ - if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7))) - goto finish; - - /* align memory */ - { - const uint8_t *alignment = - (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8); - if (limit >= alignment) { - while (*buffer < alignment) { - if (**buffer == c) - return 1; - *buffer += 1; - } - } - } - const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7)); -#else - const uint8_t *limit64 = (uint8_t *)limit - 7; -#endif - uint64_t wanted1 = 0x0101010101010101ULL * c; - for (; *buffer < limit64; *buffer += 8) { - const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1); - const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu; - const uint64_t t1 = (eq1 & 0x8080808080808080llu); - if ((t0 & t1)) { - break; - } - } -#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) -finish: -#endif - while (*buffer < limit) { - if (**buffer == c) - return 1; - (*buffer)++; - } - return 0; -} - -#else - -/* a helper that seeks any char, converts it to NUL and returns 1 if found. */ -inline static uint8_t seek2ch(uint8_t **pos, const uint8_t *limit, uint8_t ch) { - /* This is library based alternative that is sometimes slower */ - if (*pos >= limit || **pos == ch) { - return 0; - } - uint8_t *tmp = (uint8_t *)memchr(*pos, ch, limit - (*pos)); - if (tmp) { - *pos = tmp; - return 1; - } - *pos = (uint8_t *)limit; - return 0; -} - -#endif - -/* ***************************************************************************** -Parsing RESP requests -***************************************************************************** */ - -/** - * Returns the number of bytes to be resent. i.e., for a return value 5, the - * last 5 bytes in the buffer need to be resent to the parser. - */ -static size_t resp_parse(resp_parser_s *parser, const void *buffer, - size_t length) { - if (!parser->obj_countdown) - parser->obj_countdown = 1; /* always expect something... */ - uint8_t *pos = (uint8_t *)buffer; - const uint8_t *stop = pos + length; - while (pos < stop) { - uint8_t *eol; - if (parser->expecting) { - if (pos + parser->expecting + 2 > stop) { - /* read, but make sure the buffer includes the new line markers */ - size_t tmp = (size_t)((uintptr_t)stop - (uintptr_t)pos); - if ((intptr_t)tmp >= parser->expecting) - tmp = parser->expecting - 1; - resp_on_string_chunk(parser, (void *)pos, tmp); - parser->expecting -= tmp; - return (size_t)((uintptr_t)stop - ((uintptr_t)pos + tmp)); /* 0 or 1 */ - } else { - resp_on_string_chunk(parser, (void *)pos, parser->expecting); - resp_on_end_string(parser); - pos += parser->expecting; - if (pos[0] == '\r') - ++pos; - if (pos[0] == '\n') - ++pos; - parser->expecting = 0; - --parser->obj_countdown; - if (parser->obj_countdown <= 0) { - parser->obj_countdown = 1; - if (resp_on_message(parser)) - goto finish; - } - continue; - } - } - eol = pos; - if (seek2ch(&eol, stop, '\n') == 0) - break; - switch (*pos) { - case '+': - if (pos[1] == 'O' && pos[2] == 'K' && pos[3] == '\r' && pos[4] == '\n') { - resp_on_okay(parser); - --parser->obj_countdown; - break; - } - if (resp_on_start_string(parser, - (size_t)((uintptr_t)eol - (uintptr_t)pos - 2))) { - pos = eol + 1; - goto finish; - } - resp_on_string_chunk(parser, (void *)(pos + 1), - (size_t)((uintptr_t)eol - (uintptr_t)pos - 1)); - resp_on_end_string(parser); - --parser->obj_countdown; - break; - case '-': - resp_on_err_msg(parser, pos, - (size_t)((uintptr_t)eol - (uintptr_t)pos - 1)); - --parser->obj_countdown; - break; - case '*': /* fallthrough */ - case '$': /* fallthrough */ - case ':': { - uint8_t id = *pos; - uint8_t inv = 0; - int64_t i = 0; - ++pos; - if (pos[0] == '-') { - inv = 1; - ++pos; - } - while ((size_t)(pos[0] - (uint8_t)'0') <= 9) { - i = (i * 10) + (pos[0] - ((uint8_t)'0')); - ++pos; - } - if (inv) - i = i * -1; - - switch (id) { - case ':': - resp_on_number(parser, i); - --parser->obj_countdown; - break; - case '$': - if (i < 0) { - resp_on_null(parser); - --parser->obj_countdown; - } else if (i == 0) { - resp_on_start_string(parser, 0); - resp_on_end_string(parser); - --parser->obj_countdown; - eol += 2; /* consume the extra "\r\n" */ - } else { - if (resp_on_start_string(parser, i)) { - pos = eol + 1; - goto finish; - } - parser->expecting = i; - } - break; - case '*': - if (i < 0) { - resp_on_null(parser); - } else { - if (resp_on_start_array(parser, i)) { - pos = eol + 1; - goto finish; - } - parser->obj_countdown += i; - } - --parser->obj_countdown; - break; - } - } break; - default: - if (!parser->obj_countdown && !parser->expecting) { - /* possible (probable) inline command... for server authoring. */ - /* Not Supported, PRs are welcome. */ - resp_on_parser_error(parser); - return (size_t)((uintptr_t)stop - (uintptr_t)pos); - } else { - resp_on_parser_error(parser); - return (size_t)((uintptr_t)stop - (uintptr_t)pos); - } - } - pos = eol + 1; - if (parser->obj_countdown <= 0 && !parser->expecting) { - parser->obj_countdown = 1; - resp_on_message(parser); - } - } -finish: - return (size_t)((uintptr_t)stop - (uintptr_t)pos); -} - -#endif /* H_RESP_PARSER_H */ diff --git a/ext/iodine/websocket_parser.h b/ext/iodine/websocket_parser.h deleted file mode 100644 index 56c4f063..00000000 --- a/ext/iodine/websocket_parser.h +++ /dev/null @@ -1,506 +0,0 @@ -/* -copyright: Boaz Segev, 2017-2019 -license: MIT - -Feel free to copy, use and enjoy according to the license specified. -*/ -#ifndef H_WEBSOCKET_PARSER_H -/**\file - -A single file WebSocket message parser and WebSocket message wrapper, decoupled -from any IO layer. - -Notice that this header file library includes static funnction declerations that -must be implemented by the including file (the callbacks). - -*/ -#define H_WEBSOCKET_PARSER_H -#include -#include -#include -#if DEBUG -#include -#endif -/* ***************************************************************************** -API - Message Wrapping -***************************************************************************** */ - -/** returns the length of the buffer required to wrap a message `len` long */ -static inline __attribute__((unused)) uint64_t -websocket_wrapped_len(uint64_t len); - -/** - * Wraps a WebSocket server message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Further opcode values: - * * %x0 denotes a continuation frame - * * %x1 denotes a text frame - * * %x2 denotes a binary frame - * * %x3-7 are reserved for further non-control frames - * * %x8 denotes a connection close - * * %x9 denotes a ping - * * %xA denotes a pong - * * %xB-F are reserved for further control frames - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len)` - */ -inline static uint64_t __attribute__((unused)) -websocket_server_wrap(void *target, void *msg, uint64_t len, - unsigned char opcode, unsigned char first, - unsigned char last, unsigned char rsv); - -/** - * Wraps a WebSocket client message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4` - */ -inline static __attribute__((unused)) uint64_t -websocket_client_wrap(void *target, void *msg, uint64_t len, - unsigned char opcode, unsigned char first, - unsigned char last, unsigned char rsv); - -/* ***************************************************************************** -Callbacks - Required functions that must be inplemented to use this header -***************************************************************************** */ - -static void websocket_on_unwrapped(void *udata, void *msg, uint64_t len, - char first, char last, char text, - unsigned char rsv); -static void websocket_on_protocol_ping(void *udata, void *msg, uint64_t len); -static void websocket_on_protocol_pong(void *udata, void *msg, uint64_t len); -static void websocket_on_protocol_close(void *udata); -static void websocket_on_protocol_error(void *udata); - -/* ***************************************************************************** -API - Parsing (unwrapping) -***************************************************************************** */ - -/** the returned value for `websocket_buffer_required` */ -struct websocket_packet_info_s { - /** the expected packet length */ - uint64_t packet_length; - /** the packet's "head" size (before the data) */ - uint8_t head_length; - /** a flag indicating if the packet is masked */ - uint8_t masked; -}; - -/** - * Returns all known information regarding the upcoming message. - * - * @returns a struct websocket_packet_info_s. - * - * On protocol error, the `head_length` value is 0 (no valid head detected). - */ -inline static struct websocket_packet_info_s -websocket_buffer_peek(void *buffer, uint64_t len); - -/** - * Consumes the data in the buffer, calling any callbacks required. - * - * Returns the remaining data in the existing buffer (can be 0). - * - * Notice: if there's any data in the buffer that can't be parsed - * just yet, `memmove` is used to place the data at the beginning of the buffer. - */ -inline static __attribute__((unused)) uint64_t -websocket_consume(void *buffer, uint64_t len, void *udata, - uint8_t require_masking); - -/* ***************************************************************************** -API - Internal Helpers -***************************************************************************** */ - -/** used internally to mask and unmask client messages. */ -inline static void websocket_xmask(void *msg, uint64_t len, uint32_t mask); - -/* ***************************************************************************** - - Implementation - -***************************************************************************** */ - -/* ***************************************************************************** -Message masking -***************************************************************************** */ -/** used internally to mask and unmask client messages. */ -void websocket_xmask(void *msg, uint64_t len, uint32_t mask) { - if (len > 7) { - { /* XOR any unaligned memory (4 byte alignment) */ - const uintptr_t offset = 4 - ((uintptr_t)msg & 3); - switch (offset) { - case 3: - ((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2]; - /* fallthrough */ - case 2: - ((uint8_t *)msg)[1] ^= ((uint8_t *)(&mask))[1]; - /* fallthrough */ - case 1: - ((uint8_t *)msg)[0] ^= ((uint8_t *)(&mask))[0]; - /* rotate mask and move pointer to first 4 byte alignment */ - uint64_t comb = mask | ((uint64_t)mask << 32); - ((uint8_t *)(&mask))[0] = ((uint8_t *)(&comb))[0 + offset]; - ((uint8_t *)(&mask))[1] = ((uint8_t *)(&comb))[1 + offset]; - ((uint8_t *)(&mask))[2] = ((uint8_t *)(&comb))[2 + offset]; - ((uint8_t *)(&mask))[3] = ((uint8_t *)(&comb))[3 + offset]; - msg = (void *)((uintptr_t)msg + offset); - len -= offset; - } - } -#if UINTPTR_MAX <= 0xFFFFFFFF - /* handle 4 byte XOR alignment in 32 bit mnachine*/ - while (len >= 4) { - *((uint32_t *)msg) ^= mask; - len -= 4; - msg = (void *)((uintptr_t)msg + 4); - } -#else - /* handle first 4 byte XOR alignment and move on to 64 bits */ - if ((uintptr_t)msg & 7) { - *((uint32_t *)msg) ^= mask; - len -= 4; - msg = (void *)((uintptr_t)msg + 4); - } - /* intrinsic / XOR by 8 byte block, memory aligned */ - const uint64_t xmask = (((uint64_t)mask) << 32) | mask; - while (len >= 8) { - *((uint64_t *)msg) ^= xmask; - len -= 8; - msg = (void *)((uintptr_t)msg + 8); - } -#endif - } - - /* XOR any leftover bytes (might be non aligned) */ - switch (len) { - case 7: - ((uint8_t *)msg)[6] ^= ((uint8_t *)(&mask))[2]; - /* fallthrough */ - case 6: - ((uint8_t *)msg)[5] ^= ((uint8_t *)(&mask))[1]; - /* fallthrough */ - case 5: - ((uint8_t *)msg)[4] ^= ((uint8_t *)(&mask))[0]; - /* fallthrough */ - case 4: - ((uint8_t *)msg)[3] ^= ((uint8_t *)(&mask))[3]; - /* fallthrough */ - case 3: - ((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2]; - /* fallthrough */ - case 2: - ((uint8_t *)msg)[1] ^= ((uint8_t *)(&mask))[1]; - /* fallthrough */ - case 1: - ((uint8_t *)msg)[0] ^= ((uint8_t *)(&mask))[0]; - /* fallthrough */ - } -} - -/* ***************************************************************************** -Message wrapping -***************************************************************************** */ - -/** Converts an unaligned network ordered byte stream to a 16 bit number. */ -#define websocket_str2u16(c) \ - ((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) | \ - (uint16_t)(((uint8_t *)(c))[1]))) - -/** Converts an unaligned network ordered byte stream to a 64 bit number. */ -#define websocket_str2u64(c) \ - ((uint64_t)((((uint64_t)((uint8_t *)(c))[0]) << 56) | \ - (((uint64_t)((uint8_t *)(c))[1]) << 48) | \ - (((uint64_t)((uint8_t *)(c))[2]) << 40) | \ - (((uint64_t)((uint8_t *)(c))[3]) << 32) | \ - (((uint64_t)((uint8_t *)(c))[4]) << 24) | \ - (((uint64_t)((uint8_t *)(c))[5]) << 16) | \ - (((uint64_t)((uint8_t *)(c))[6]) << 8) | (((uint8_t *)(c))[7]))) - -/** Writes a local 16 bit number to an unaligned buffer in network order. */ -#define websocket_u2str16(buffer, i) \ - do { \ - ((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF; \ - ((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF; \ - } while (0); - -/** Writes a local 64 bit number to an unaligned buffer in network order. */ -#define websocket_u2str64(buffer, i) \ - do { \ - ((uint8_t *)(buffer))[0] = ((uint64_t)(i) >> 56) & 0xFF; \ - ((uint8_t *)(buffer))[1] = ((uint64_t)(i) >> 48) & 0xFF; \ - ((uint8_t *)(buffer))[2] = ((uint64_t)(i) >> 40) & 0xFF; \ - ((uint8_t *)(buffer))[3] = ((uint64_t)(i) >> 32) & 0xFF; \ - ((uint8_t *)(buffer))[4] = ((uint64_t)(i) >> 24) & 0xFF; \ - ((uint8_t *)(buffer))[5] = ((uint64_t)(i) >> 16) & 0xFF; \ - ((uint8_t *)(buffer))[6] = ((uint64_t)(i) >> 8) & 0xFF; \ - ((uint8_t *)(buffer))[7] = ((uint64_t)(i)) & 0xFF; \ - } while (0); - -/** returns the length of the buffer required to wrap a message `len` long */ -static inline uint64_t websocket_wrapped_len(uint64_t len) { - if (len < 126) - return len + 2; - if (len < (1UL << 16)) - return len + 4; - return len + 10; -} - -/** - * Wraps a WebSocket server message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Further opcode values: - * * %x0 denotes a continuation frame - * * %x1 denotes a text frame - * * %x2 denotes a binary frame - * * %x3-7 are reserved for further non-control frames - * * %x8 denotes a connection close - * * %x9 denotes a ping - * * %xA denotes a pong - * * %xB-F are reserved for further control frames - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len)` - */ -static uint64_t websocket_server_wrap(void *target, void *msg, uint64_t len, - unsigned char opcode, unsigned char first, - unsigned char last, unsigned char rsv) { - ((uint8_t *)target)[0] = 0 | - /* opcode */ (((first ? opcode : 0) & 15)) | - /* rsv */ ((rsv & 7) << 4) | - /*fin*/ ((last & 1) << 7); - if (len < 126) { - ((uint8_t *)target)[1] = len; - memcpy(((uint8_t *)target) + 2, msg, len); - return len + 2; - } else if (len < (1UL << 16)) { - /* head is 4 bytes */ - ((uint8_t *)target)[1] = 126; - websocket_u2str16(((uint8_t *)target + 2), len); - memcpy((uint8_t *)target + 4, msg, len); - return len + 4; - } - /* Really Long Message */ - ((uint8_t *)target)[1] = 127; - websocket_u2str64(((uint8_t *)target + 2), len); - memcpy((uint8_t *)target + 10, msg, len); - return len + 10; -} - -/** - * Wraps a WebSocket client message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len) + - * 4` - */ -static uint64_t websocket_client_wrap(void *target, void *msg, uint64_t len, - unsigned char opcode, unsigned char first, - unsigned char last, unsigned char rsv) { - uint32_t mask = rand() | 0x01020408; - ((uint8_t *)target)[0] = 0 | - /* opcode */ (((first ? opcode : 0) & 15)) | - /* rsv */ ((rsv & 7) << 4) | - /*fin*/ ((last & 1) << 7); - if (len < 126) { - ((uint8_t *)target)[1] = len | 128; - ((uint8_t *)target)[2] = ((uint8_t *)(&mask))[0]; - ((uint8_t *)target)[3] = ((uint8_t *)(&mask))[1]; - ((uint8_t *)target)[4] = ((uint8_t *)(&mask))[2]; - ((uint8_t *)target)[5] = ((uint8_t *)(&mask))[3]; - memcpy(((uint8_t *)target) + 6, msg, len); - websocket_xmask((uint8_t *)target + 6, len, mask); - return len + 6; - } else if (len < (1UL << 16)) { - /* head is 4 bytes */ - ((uint8_t *)target)[1] = 126 | 128; - websocket_u2str16(((uint8_t *)target + 2), len); - ((uint8_t *)target)[4] = ((uint8_t *)(&mask))[0]; - ((uint8_t *)target)[5] = ((uint8_t *)(&mask))[1]; - ((uint8_t *)target)[6] = ((uint8_t *)(&mask))[2]; - ((uint8_t *)target)[7] = ((uint8_t *)(&mask))[3]; - memcpy((uint8_t *)target + 8, msg, len); - websocket_xmask((uint8_t *)target + 8, len, mask); - return len + 8; - } - /* Really Long Message */ - ((uint8_t *)target)[1] = 255; - websocket_u2str64(((uint8_t *)target + 2), len); - ((uint8_t *)target)[10] = ((uint8_t *)(&mask))[0]; - ((uint8_t *)target)[11] = ((uint8_t *)(&mask))[1]; - ((uint8_t *)target)[12] = ((uint8_t *)(&mask))[2]; - ((uint8_t *)target)[13] = ((uint8_t *)(&mask))[3]; - memcpy((uint8_t *)target + 14, msg, len); - websocket_xmask((uint8_t *)target + 14, len, mask); - return len + 14; -} - -/* ***************************************************************************** -Message unwrapping -***************************************************************************** */ - -/** - * Returns all known information regarding the upcoming message. - * - * @returns a struct websocket_packet_info_s. - * - * On protocol error, the `head_length` value is 0 (no valid head detected). - */ -inline static struct websocket_packet_info_s -websocket_buffer_peek(void *buffer, uint64_t len) { - if (len < 2) { - const struct websocket_packet_info_s info = {0 /* packet */, 2 /* head */, - 0 /* masked? */}; - return info; - } - const uint8_t mask_f = (((uint8_t *)buffer)[1] >> 7) & 1; - const uint8_t mask_l = (mask_f << 2); - uint8_t len_indicator = ((((uint8_t *)buffer)[1]) & 127); - switch (len_indicator) { - case 126: - if (len < 4) - return (struct websocket_packet_info_s){0, (uint8_t)(4 + mask_l), mask_f}; - return (struct websocket_packet_info_s){ - (uint64_t)websocket_str2u16(((uint8_t *)buffer + 2)), - (uint8_t)(4 + mask_l), mask_f}; - case 127: - if (len < 10) - return (struct websocket_packet_info_s){0, (uint8_t)(10 + mask_l), - mask_f}; - { - uint64_t msg_len = websocket_str2u64(((uint8_t *)buffer + 2)); - if (msg_len >> 62) - return (struct websocket_packet_info_s){0, 0, 0}; - return (struct websocket_packet_info_s){msg_len, (uint8_t)(10 + mask_l), - mask_f}; - } - default: - return (struct websocket_packet_info_s){len_indicator, - (uint8_t)(2 + mask_l), mask_f}; - } -} - -/** - * Consumes the data in the buffer, calling any callbacks required. - * - * Returns the remaining data in the existing buffer (can be 0). - */ -static uint64_t websocket_consume(void *buffer, uint64_t len, void *udata, - uint8_t require_masking) { - volatile struct websocket_packet_info_s info = - websocket_buffer_peek(buffer, len); - if (!info.head_length) { -#if DEBUG - fprintf(stderr, "ERROR: WebSocket protocol error - malicious header.\n"); -#endif - websocket_on_protocol_error(udata); - return 0; - } - if (info.head_length + info.packet_length > len) - return len; - uint64_t reminder = len; - uint8_t *pos = (uint8_t *)buffer; - while (info.head_length + info.packet_length <= reminder) { - /* parse head */ - void *payload = (void *)(pos + info.head_length); - /* unmask? */ - if (info.masked) { - /* masked */ - uint32_t mask; // = ((uint32_t *)payload)[-1]; - ((uint8_t *)(&mask))[0] = ((uint8_t *)(payload))[-4]; - ((uint8_t *)(&mask))[1] = ((uint8_t *)(payload))[-3]; - ((uint8_t *)(&mask))[2] = ((uint8_t *)(payload))[-2]; - ((uint8_t *)(&mask))[3] = ((uint8_t *)(payload))[-1]; - websocket_xmask(payload, info.packet_length, mask); - } else if (require_masking && info.packet_length) { -#if DEBUG - fprintf(stderr, "ERROR: WebSocket protocol error - unmasked data.\n"); -#endif - websocket_on_protocol_error(udata); - return 0; - } - /* call callback */ - switch (pos[0] & 15) { - case 0: - /* continuation frame */ - websocket_on_unwrapped(udata, payload, info.packet_length, 0, - ((pos[0] >> 7) & 1), 0, ((pos[0] >> 4) & 7)); - break; - case 1: - /* text frame */ - websocket_on_unwrapped(udata, payload, info.packet_length, 1, - ((pos[0] >> 7) & 1), 1, ((pos[0] >> 4) & 7)); - break; - case 2: - /* data frame */ - websocket_on_unwrapped(udata, payload, info.packet_length, 1, - ((pos[0] >> 7) & 1), 0, ((pos[0] >> 4) & 7)); - break; - case 8: - /* close frame */ - websocket_on_protocol_close(udata); - break; - case 9: - /* ping frame */ - websocket_on_protocol_ping(udata, payload, info.packet_length); - break; - case 10: - /* pong frame */ - websocket_on_protocol_pong(udata, payload, info.packet_length); - break; - default: -#if DEBUG - fprintf(stderr, "ERROR: WebSocket protocol error - unknown opcode %u\n", - (unsigned int)(pos[0] & 15)); -#endif - websocket_on_protocol_error(udata); - } - /* step forward */ - reminder = reminder - (info.head_length + info.packet_length); - if (!reminder) - return 0; - pos += info.head_length + info.packet_length; - info = websocket_buffer_peek(pos, reminder); - } - /* reset buffer state - support pipelining */ - memmove(buffer, (uint8_t *)buffer + len - reminder, reminder); - return reminder; -} - -#endif diff --git a/ext/iodine/websockets.c b/ext/iodine/websockets.c deleted file mode 100644 index 77c3b9ce..00000000 --- a/ext/iodine/websockets.c +++ /dev/null @@ -1,752 +0,0 @@ -/* -copyright: Boaz Segev, 2016-2019 -license: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#define FIO_INCLUDE_STR -#include - -/* subscription lists have a long lifetime */ -#define FIO_FORCE_MALLOC_TMP 1 -#define FIO_INCLUDE_LINKED_LIST -#include - -#include - -#include -#include - -#ifndef __MINGW32__ -#include -#endif -#include -#include -#include -#include -#include - -#include - -#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \ - !defined(__MINGW32__) -#include -#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define __BIG_ENDIAN__ -#endif -#endif - -/******************************************************************************* -Buffer management - update to change the way the buffer is handled. -*/ -struct buffer_s { - void *data; - size_t size; -}; - -#pragma weak create_ws_buffer -/** returns a buffer_s struct, with a buffer (at least) `size` long. */ -struct buffer_s create_ws_buffer(ws_s *owner); - -#pragma weak resize_ws_buffer -/** returns a buffer_s struct, with a buffer (at least) `size` long. */ -struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s); - -#pragma weak free_ws_buffer -/** releases an existing buffer. */ -void free_ws_buffer(ws_s *owner, struct buffer_s); - -/** Sets the initial buffer size. (4Kb)*/ -#define WS_INITIAL_BUFFER_SIZE 4096UL - -/******************************************************************************* -Buffer management - simple implementation... -Since Websocket connections have a long life expectancy, optimizing this part of -the code probably wouldn't offer a high performance boost. -*/ - -// buffer increments by 4,096 Bytes (4Kb) -#define round_up_buffer_size(size) (((size) >> 12) + 1) << 12 - -struct buffer_s create_ws_buffer(ws_s *owner) { - (void)(owner); - struct buffer_s buff; - buff.size = WS_INITIAL_BUFFER_SIZE; - buff.data = malloc(buff.size); - FIO_ASSERT_ALLOC(buff.data); - return buff; -} - -struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buff) { - buff.size = round_up_buffer_size(buff.size); - void *tmp = realloc(buff.data, buff.size); - if (!tmp) { - free_ws_buffer(owner, buff); - buff.size = 0; - } - buff.data = tmp; - return buff; -} -void free_ws_buffer(ws_s *owner, struct buffer_s buff) { - (void)(owner); - free(buff.data); -} - -#undef round_up_buffer_size - -/******************************************************************************* -Create/Destroy the websocket object (prototypes) -*/ - -static ws_s *new_websocket(); -static void destroy_ws(ws_s *ws); - -/******************************************************************************* -The Websocket object (protocol + parser) -*/ -struct ws_s { - /** The Websocket protocol */ - fio_protocol_s protocol; - /** connection data */ - intptr_t fd; - /** callbacks */ - void (*on_message)(ws_s *ws, fio_str_info_s msg, uint8_t is_text); - void (*on_shutdown)(ws_s *ws); - void (*on_ready)(ws_s *ws); - void (*on_open)(ws_s *ws); - void (*on_close)(intptr_t uuid, void *udata); - /** Opaque user data. */ - void *udata; - /** The maximum websocket message size */ - size_t max_msg_size; - /** active pub/sub subscriptions */ - fio_ls_s subscriptions; - fio_lock_i sub_lock; - /** socket buffer. */ - struct buffer_s buffer; - /** data length (how much of the buffer actually used). */ - size_t length; - /** total data length (including continuation frames). */ - size_t total_length; - /** message buffer. */ - FIOBJ msg; - /** latest text state. */ - uint8_t is_text; - /** websocket connection type. */ - uint8_t is_client; -}; - -/* ***************************************************************************** -Create/Destroy the websocket subscription objects -***************************************************************************** */ - -static inline void clear_subscriptions(ws_s *ws) { - fio_lock(&ws->sub_lock); - while (fio_ls_any(&ws->subscriptions)) { - fio_unsubscribe(fio_ls_pop(&ws->subscriptions)); - } - fio_unlock(&ws->sub_lock); -} - -/* ***************************************************************************** -Callbacks - Required functions for websocket_parser.h -***************************************************************************** */ - -static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len, - char first, char last, char text, - unsigned char rsv) { - ws_s *ws = ws_p; - if (!ws) - return; - if (last && first) { - ws->on_message(ws, (fio_str_info_s){.data = msg, .len = len}, - (uint8_t)text); - return; - } - if (ws->msg == FIOBJ_INVALID) - ws->msg = fiobj_str_buf(len); - ws->total_length += len; - if (first) { - ws->is_text = (uint8_t)text; - } - fiobj_str_write(ws->msg, msg, len); - if (last) { - ws->on_message(ws, fiobj_obj2cstr(ws->msg), ws->is_text); - fiobj_str_resize(ws->msg, 0); - ws->total_length = 0; - } - - (void)rsv; -} -static void websocket_on_protocol_ping(void *ws_p, void *msg_, uint64_t len) { - ws_s *ws = ws_p; - if (msg_) { - void *buff = malloc(len + 16); - FIO_ASSERT_ALLOC(buff); - len = (((ws_s *)ws)->is_client - ? websocket_client_wrap(buff, msg_, len, 10, 1, 1, 0) - : websocket_server_wrap(buff, msg_, len, 10, 1, 1, 0)); - fio_write2(ws->fd, .data.buffer = buff, .length = len); - } else { - if (((ws_s *)ws)->is_client) { - fio_write2(ws->fd, .data.buffer = "\x8a\x80mask", .length = 6, - .after.dealloc = FIO_DEALLOC_NOOP); - } else { - fio_write2(ws->fd, .data.buffer = "\x8a\x00", .length = 2, - .after.dealloc = FIO_DEALLOC_NOOP); - } - } - FIO_LOG_DEBUG("Received ping and sent pong for Websocket %p (%d)", ws_p, - (int)(((ws_s *)ws_p)->fd)); -} -static void websocket_on_protocol_pong(void *ws_p, void *msg, uint64_t len) { - FIO_LOG_DEBUG("Received pong for Websocket %p (%d)", ws_p, - (int)(((ws_s *)ws_p)->fd)); - (void)len; - (void)msg; - (void)ws_p; -} -static void websocket_on_protocol_close(void *ws_p) { - ws_s *ws = ws_p; - fio_close(ws->fd); -} -static void websocket_on_protocol_error(void *ws_p) { - ws_s *ws = ws_p; - fio_close(ws->fd); -} - -/******************************************************************************* -The Websocket Protocol implementation -*/ - -#define ws_protocol(fd) ((ws_s *)(server_get_protocol(fd))) - -static void ws_ping(intptr_t fd, fio_protocol_s *ws) { - (void)(ws); - if (((ws_s *)ws)->is_client) { - fio_write2(fd, .data.buffer = "\x89\x80MASK", .length = 6, - .after.dealloc = FIO_DEALLOC_NOOP); - } else { - fio_write2(fd, .data.buffer = "\x89\x00", .length = 2, - .after.dealloc = FIO_DEALLOC_NOOP); - } - FIO_LOG_DEBUG("Sent ping for Websocket %p (%d)", (void *)ws, (int)fd); -} - -static void on_close(intptr_t uuid, fio_protocol_s *_ws) { - destroy_ws((ws_s *)_ws); - (void)uuid; -} - -static void on_ready(intptr_t fduuid, fio_protocol_s *ws) { - (void)(fduuid); - if (((ws_s *)ws)->on_ready) - ((ws_s *)ws)->on_ready((ws_s *)ws); -} - -static uint8_t on_shutdown(intptr_t fd, fio_protocol_s *ws) { - (void)(fd); - if (ws && ((ws_s *)ws)->on_shutdown) - ((ws_s *)ws)->on_shutdown((ws_s *)ws); - if (((ws_s *)ws)->is_client) { - fio_write2(fd, .data.buffer = "\x88\x80MASK", .length = 6, - .after.dealloc = FIO_DEALLOC_NOOP); - } else { - fio_write2(fd, .data.buffer = "\x88\x00", .length = 2, - .after.dealloc = FIO_DEALLOC_NOOP); - } - return 0; -} - -static void on_data(intptr_t sockfd, fio_protocol_s *ws_) { - ws_s *const ws = (ws_s *)ws_; - if (ws == NULL) - return; - struct websocket_packet_info_s info = - websocket_buffer_peek(ws->buffer.data, ws->length); - const uint64_t raw_length = info.packet_length + info.head_length; - /* test expected data amount */ - if (ws->max_msg_size < raw_length + ws->total_length) { - /* too big */ - websocket_close(ws); - return; - } - /* test buffer capacity */ - if (raw_length > ws->buffer.size) { - ws->buffer.size = (size_t)raw_length; - ws->buffer = resize_ws_buffer(ws, ws->buffer); - if (!ws->buffer.data) { - // no memory. - websocket_close(ws); - return; - } - } - - const ssize_t len = fio_read(sockfd, (uint8_t *)ws->buffer.data + ws->length, - ws->buffer.size - ws->length); - if (len <= 0) { - return; - } - ws->length = websocket_consume(ws->buffer.data, ws->length + len, ws, - (~(ws->is_client) & 1)); - - fio_force_event(sockfd, FIO_EVENT_ON_DATA); -} - -static void on_data_first(intptr_t sockfd, fio_protocol_s *ws_) { - ws_s *const ws = (ws_s *)ws_; - if (ws->on_open) - ws->on_open(ws); - ws->protocol.on_data = on_data; - ws->protocol.on_ready = on_ready; - - if (ws->length) { - ws->length = websocket_consume(ws->buffer.data, ws->length, ws, - (~(ws->is_client) & 1)); - } - fio_force_event(sockfd, FIO_EVENT_ON_DATA); - fio_force_event(sockfd, FIO_EVENT_ON_READY); -} - -/* later */ -static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text, - char first, char last, char client); - -/******************************************************************************* -Create/Destroy the websocket object -*/ - -static ws_s *new_websocket(intptr_t uuid) { - // allocate the protocol object - ws_s *ws = malloc(sizeof(*ws)); - FIO_ASSERT_ALLOC(ws); - *ws = (ws_s){ - .protocol.ping = ws_ping, - .protocol.on_data = on_data_first, - .protocol.on_close = on_close, - .protocol.on_ready = NULL /* filled in after `on_open` */, - .protocol.on_shutdown = on_shutdown, - .subscriptions = FIO_LS_INIT(ws->subscriptions), - .is_client = 0, - .fd = uuid, - }; - return ws; -} -static void destroy_ws(ws_s *ws) { - if (ws->on_close) - ws->on_close(ws->fd, ws->udata); - if (ws->msg) - fiobj_free(ws->msg); - clear_subscriptions(ws); - free_ws_buffer(ws, ws->buffer); - free(ws); -} - -void websocket_attach(intptr_t uuid, http_settings_s *http_settings, - websocket_settings_s *args, void *data, size_t length) { - ws_s *ws = new_websocket(uuid); - // we have an active websocket connection - prep the connection buffer - ws->buffer = create_ws_buffer(ws); - // Setup ws callbacks - ws->on_open = args->on_open; - ws->on_close = args->on_close; - ws->on_message = args->on_message; - ws->on_ready = args->on_ready; - ws->on_shutdown = args->on_shutdown; - // setup any user data - ws->udata = args->udata; - if (http_settings) { - // client mode? - ws->is_client = http_settings->is_client; - // buffer limits - ws->max_msg_size = http_settings->ws_max_msg_size; - // update the timeout - fio_timeout_set(uuid, http_settings->ws_timeout); - } else { - ws->max_msg_size = (1024 * 256); - fio_timeout_set(uuid, 40); - } - - if (data && length) { - if (length > ws->buffer.size) { - ws->buffer.size = length; - ws->buffer = resize_ws_buffer(ws, ws->buffer); - if (!ws->buffer.data) { - // no memory. - fio_attach(uuid, (fio_protocol_s *)ws); - websocket_close(ws); - return; - } - } - memcpy(ws->buffer.data, data, length); - ws->length = length; - } - // update the protocol object, cleaning up the old one - fio_attach(uuid, (fio_protocol_s *)ws); - // allow the on_open and on_data to take over the control. - fio_force_event(uuid, FIO_EVENT_ON_DATA); -} - -/******************************************************************************* -Writing to the Websocket -*/ -#define WS_MAX_FRAME_SIZE \ - (FIO_MEMORY_BLOCK_ALLOC_LIMIT - 4096) // should be less then `unsigned short` - -static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text, - char first, char last, char client) { - if (len <= WS_MAX_FRAME_SIZE) { - void *buff = fio_malloc(len + 16); - FIO_ASSERT_ALLOC(buff); - len = (client ? websocket_client_wrap(buff, data, len, (text ? 1 : 2), - first, last, 0) - : websocket_server_wrap(buff, data, len, (text ? 1 : 2), - first, last, 0)); - fio_write2(fd, .data.buffer = buff, .length = len, - .after.dealloc = fio_free); - } else { - /* frame fragmentation is better for large data then large frames */ - while (len > WS_MAX_FRAME_SIZE) { - websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client); - data = ((uint8_t *)data) + WS_MAX_FRAME_SIZE; - first = 0; - len -= WS_MAX_FRAME_SIZE; - } - websocket_write_impl(fd, data, len, text, first, 1, client); - } - return; -} - -/* ***************************************************************************** -Multi-client broadcast optimizations -***************************************************************************** */ - -static void websocket_optimize_free(fio_msg_s *msg, void *metadata) { - fiobj_free((FIOBJ)metadata); - (void)msg; -} - -static inline fio_msg_metadata_s websocket_optimize(fio_str_info_s msg, - unsigned char opcode) { - FIOBJ out = fiobj_str_buf(msg.len + 10); - fiobj_str_resize(out, - websocket_server_wrap(fiobj_obj2cstr(out).data, msg.data, - msg.len, opcode, 1, 1, 0)); - fio_msg_metadata_s ret = { - .on_finish = websocket_optimize_free, - .metadata = (void *)out, - }; - return ret; -} -static fio_msg_metadata_s websocket_optimize_generic(fio_str_info_s ch, - fio_str_info_s msg, - uint8_t is_json) { - fio_str_s tmp = FIO_STR_INIT_EXISTING(ch.data, ch.len, 0); // don't free - tmp.dealloc = NULL; - unsigned char opcode = 2; - if (tmp.len <= (2 << 19) && fio_str_utf8_valid(&tmp)) { - opcode = 1; - } - fio_msg_metadata_s ret = websocket_optimize(msg, opcode); - ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB; - return ret; - (void)ch; - (void)is_json; -} - -static fio_msg_metadata_s websocket_optimize_text(fio_str_info_s ch, - fio_str_info_s msg, - uint8_t is_json) { - fio_msg_metadata_s ret = websocket_optimize(msg, 1); - ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT; - return ret; - (void)ch; - (void)is_json; -} - -static fio_msg_metadata_s websocket_optimize_binary(fio_str_info_s ch, - fio_str_info_s msg, - uint8_t is_json) { - fio_msg_metadata_s ret = websocket_optimize(msg, 2); - ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY; - return ret; - (void)ch; - (void)is_json; -} - -/** - * Enables (or disables) broadcast optimizations. - * - * When using WebSocket pub/sub system is originally optimized for either - * non-direct transmission (messages are handled by callbacks) or direct - * transmission to 1-3 clients per channel (on average), meaning that the - * majority of the messages are meant for a single recipient (or multiple - * callback recipients) and only some are expected to be directly transmitted to - * a group. - * - * However, when most messages are intended for direct transmission to more than - * 3 clients (on average), certain optimizations can be made to improve memory - * consumption (minimize duplication or WebSocket network data). - * - * This function allows enablement (or disablement) of these optimizations. - * These optimizations include: - * - * * WEBSOCKET_OPTIMIZE_PUBSUB - optimize all direct transmission messages, - * best attempt to detect Text vs. Binary data. - * * WEBSOCKET_OPTIMIZE_PUBSUB_TEXT - optimize direct pub/sub text messages. - * * WEBSOCKET_OPTIMIZE_PUBSUB_BINARY - optimize direct pub/sub binary messages. - * - * Note: to disable an optimization it should be disabled the same amount of - * times it was enabled - multiple optimization enablements for the same type - * are merged, but reference counted (disabled when reference is zero). - */ -void websocket_optimize4broadcasts(intptr_t type, int enable) { - static intptr_t generic = 0; - static intptr_t text = 0; - static intptr_t binary = 0; - fio_msg_metadata_s (*callback)(fio_str_info_s, fio_str_info_s, uint8_t); - intptr_t *counter; - switch ((0 - type)) { - case (0 - WEBSOCKET_OPTIMIZE_PUBSUB): - counter = &generic; - callback = websocket_optimize_generic; - break; - case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_TEXT): - counter = &text; - callback = websocket_optimize_text; - break; - case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_BINARY): - counter = &binary; - callback = websocket_optimize_binary; - break; - default: - return; - } - if (enable) { - if (fio_atomic_add(counter, 1) == 1) { - fio_message_metadata_callback_set(callback, 1); - } - } else { - if (fio_atomic_sub(counter, 1) == 0) { - fio_message_metadata_callback_set(callback, 0); - } - } -} - -/* ***************************************************************************** -Subscription handling -***************************************************************************** */ - -typedef struct { - void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg, - void *udata); - void (*on_unsubscribe)(void *udata); - void *udata; -} websocket_sub_data_s; - -static inline void websocket_on_pubsub_message_direct_internal(fio_msg_s *msg, - uint8_t txt) { - fio_protocol_s *pr = - fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE); - if (!pr) { - if (errno == EBADF) - return; - fio_message_defer(msg); - return; - } - FIOBJ message = FIOBJ_INVALID; - FIOBJ pre_wrapped = FIOBJ_INVALID; - if (!((ws_s *)pr)->is_client) { - /* pre-wrapping is only for client data */ - switch (txt) { - case 0: - pre_wrapped = - (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_BINARY); - break; - case 1: - pre_wrapped = - (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_TEXT); - break; - case 2: - pre_wrapped = (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB); - break; - default: - break; - } - if (pre_wrapped) { - // FIO_LOG_DEBUG( - // "pub/sub WebSocket optimization route for pre-wrapped message."); - fiobj_send_free((intptr_t)msg->udata1, fiobj_dup(pre_wrapped)); - goto finish; - } - } - if (txt == 2) { - /* unknown text state */ - fio_str_s tmp = - FIO_STR_INIT_STATIC2(msg->msg.data, msg->msg.len); // don't free - txt = (tmp.len >= (2 << 14) ? 0 : fio_str_utf8_valid(&tmp)); - } - websocket_write((ws_s *)pr, msg->msg, txt & 1); - fiobj_free(message); -finish: - fio_protocol_unlock(pr, FIO_PR_LOCK_WRITE); -} - -static void websocket_on_pubsub_message_direct(fio_msg_s *msg) { - websocket_on_pubsub_message_direct_internal(msg, 2); -} - -static void websocket_on_pubsub_message_direct_txt(fio_msg_s *msg) { - websocket_on_pubsub_message_direct_internal(msg, 1); -} - -static void websocket_on_pubsub_message_direct_bin(fio_msg_s *msg) { - websocket_on_pubsub_message_direct_internal(msg, 0); -} - -static void websocket_on_pubsub_message(fio_msg_s *msg) { - fio_protocol_s *pr = - fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_TASK); - if (!pr) { - if (errno == EBADF) - return; - fio_message_defer(msg); - return; - } - websocket_sub_data_s *d = msg->udata2; - - if (d->on_message) - d->on_message((ws_s *)pr, msg->channel, msg->msg, d->udata); - fio_protocol_unlock(pr, FIO_PR_LOCK_TASK); -} - -static void websocket_on_unsubscribe(void *u1, void *u2) { - websocket_sub_data_s *d = u2; - if (d->on_unsubscribe) { - d->on_unsubscribe(d->udata); - } - - if ((intptr_t)d->on_message == (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB) { - websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB, 0); - } else if ((intptr_t)d->on_message == - (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_TEXT) { - websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_TEXT, 0); - } else if ((intptr_t)d->on_message == - (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_BINARY) { - websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_BINARY, 0); - } - free(d); - (void)u1; -} - -/** - * Returns a subscription ID on success and 0 on failure. - */ -#undef websocket_subscribe -uintptr_t websocket_subscribe(struct websocket_subscribe_s args) { - if (!args.ws || !fio_is_valid(args.ws->fd)) - goto error; - websocket_sub_data_s *d = malloc(sizeof(*d)); - FIO_ASSERT_ALLOC(d); - *d = (websocket_sub_data_s){ - .udata = args.udata, - .on_message = args.on_message, - .on_unsubscribe = args.on_unsubscribe, - }; - void (*handler)(fio_msg_s *) = websocket_on_pubsub_message; - if (!args.on_message) { - intptr_t br_type; - if (args.force_binary) { - br_type = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY; - handler = websocket_on_pubsub_message_direct_bin; - } else if (args.force_text) { - br_type = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT; - handler = websocket_on_pubsub_message_direct_txt; - } else { - br_type = WEBSOCKET_OPTIMIZE_PUBSUB; - handler = websocket_on_pubsub_message_direct; - } - websocket_optimize4broadcasts(br_type, 1); - d->on_message = - (void (*)(ws_s *, fio_str_info_s, fio_str_info_s, void *))br_type; - } - subscription_s *sub = - fio_subscribe(.channel = args.channel, .match = args.match, - .on_unsubscribe = websocket_on_unsubscribe, - .on_message = handler, .udata1 = (void *)args.ws->fd, - .udata2 = d); - if (!sub) { - /* don't free `d`, return (`d` freed by fio_subscribe) */ - return 0; - } - fio_ls_s *pos; - fio_lock(&args.ws->sub_lock); - pos = fio_ls_push(&args.ws->subscriptions, sub); - fio_unlock(&args.ws->sub_lock); - - return (uintptr_t)pos; -error: - if (args.on_unsubscribe) - args.on_unsubscribe(args.udata); - return 0; -} - -/** - * Unsubscribes from a channel. - */ -void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id) { - fio_unsubscribe((subscription_s *)((fio_ls_s *)subscription_id)->obj); - fio_lock(&ws->sub_lock); - fio_ls_remove((fio_ls_s *)subscription_id); - fio_unlock(&ws->sub_lock); - - (void)ws; -} - -/******************************************************************************* -The API implementation -*/ - -/** Returns the opaque user data associated with the websocket. */ -void *websocket_udata_get(ws_s *ws) { return ws->udata; } - -/** Returns the the process specific connection's UUID (see `libsock`). */ -intptr_t websocket_uuid(ws_s *ws) { return ws->fd; } - -/** Sets the opaque user data associated with the websocket. - * Returns the old value, if any. */ -void *websocket_udata_set(ws_s *ws, void *udata) { - void *old = ws->udata; - ws->udata = udata; - return old; -} - -/** - * Returns 1 if the WebSocket connection is in Client mode (connected to a - * remote server) and 0 if the connection is in Server mode (a connection - * established using facil.io's HTTP server). - */ -uint8_t websocket_is_client(ws_s *ws) { return ws->is_client; } - -/** Writes data to the websocket. Returns -1 on failure (0 on success). */ -int websocket_write(ws_s *ws, fio_str_info_s msg, uint8_t is_text) { - if (fio_is_valid(ws->fd)) { - websocket_write_impl(ws->fd, msg.data, msg.len, is_text, 1, 1, - ws->is_client); - return 0; - } - return -1; -} -/** Closes a websocket connection. */ -void websocket_close(ws_s *ws) { - if (ws->is_client) { - fio_write2(ws->fd, .data.buffer = "\x88\x80MASK", .length = 6, - .after.dealloc = FIO_DEALLOC_NOOP); - } else { - fio_write2(ws->fd, .data.buffer = "\x88\x00", .length = 2, - .after.dealloc = FIO_DEALLOC_NOOP); - } - fio_close(ws->fd); - return; -} diff --git a/ext/iodine/websockets.h b/ext/iodine/websockets.h deleted file mode 100644 index 6a78fa65..00000000 --- a/ext/iodine/websockets.h +++ /dev/null @@ -1,185 +0,0 @@ -/* -copyright: Boaz Segev, 2016-2019 -license: MIT - -Feel free to copy, use and enjoy according to the license provided. -*/ -#ifndef H_WEBSOCKETS_H -#define H_WEBSOCKETS_H - -#include - -/* support C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/** used internally: attaches the Websocket protocol to the socket. */ -void websocket_attach(intptr_t uuid, http_settings_s *http_settings, - websocket_settings_s *args, void *data, size_t length); - -/* ***************************************************************************** -Websocket information -***************************************************************************** */ - -/** Returns the opaque user data associated with the websocket. */ -void *websocket_udata_get(ws_s *ws); - -/** - * Sets the opaque user data associated with the websocket. - * - * Returns the old value, if any. - */ -void *websocket_udata_set(ws_s *ws, void *udata); - -/** - * Returns the underlying socket UUID. - * - * This is only relevant for collecting the protocol object from outside of - * websocket events, as the socket shouldn't be written to. - */ -intptr_t websocket_uuid(ws_s *ws); - -/** - * Returns 1 if the WebSocket connection is in Client mode (connected to a - * remote server) and 0 if the connection is in Server mode (a connection - * established using facil.io's HTTP server). - */ -uint8_t websocket_is_client(ws_s *ws); - -/* ***************************************************************************** -Websocket Connection Management (write / close) -***************************************************************************** */ - -/** Writes data to the websocket. Returns -1 on failure (0 on success). */ -int websocket_write(ws_s *ws, fio_str_info_s msg, uint8_t is_text); -/** Closes a websocket connection. */ -void websocket_close(ws_s *ws); - -/* ***************************************************************************** -Websocket Pub/Sub -================= - -API for websocket pub/sub that can be used to publish messages across process -boundries. - -Supports pub/sub engines (see {pubsub.h}) that can connect to a backend service -such as Redis. - -The default pub/sub engine (if `NULL` or unspecified) will publish the messages -to the process cluster (all the processes in `fio_run`). - -To publish to a channel, use the API provided in {pubsub.h}. -***************************************************************************** */ - -/** Possible arguments for the {websocket_subscribe} function. */ -struct websocket_subscribe_s { - /** the websocket receiving the message. REQUIRED. */ - ws_s *ws; - /** the channel where the message was published. */ - fio_str_info_s channel; - /** - * The callback that handles pub/sub notifications. - * - * Default: send directly to websocket client. - */ - void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg, - void *udata); - /** - * An optional cleanup callback for the `udata`. - */ - void (*on_unsubscribe)(void *udata); - /** User opaque data, passed along to the notification. */ - void *udata; - /** An optional callback for pattern matching. */ - fio_match_fn match; - /** - * When using client forwarding (no `on_message` callback), this indicates if - * messages should be sent to the client as binary blobs, which is the safest - * approach. - * - * Default: tests for UTF-8 data encoding and sends as text if valid UTF-8. - * Messages above ~32Kb are always assumed to be binary. - */ - unsigned force_binary : 1; - /** - * When using client forwarding (no `on_message` callback), this indicates if - * messages should be sent to the client as text. - * - * `force_binary` has precedence. - * - * Default: see above. - * - */ - unsigned force_text : 1; -}; - -/** - * Subscribes to a channel. See {struct websocket_subscribe_s} for possible - * arguments. - * - * Returns a subscription ID on success and 0 on failure. - * - * All subscriptions are automatically revoked once the websocket is closed. - * - * If the connections subscribes to the same channel more than once, messages - * will be merged. However, another subscription ID will be assigned, since two - * calls to {websocket_unsubscribe} will be required in order to unregister from - * the channel. - */ -uintptr_t websocket_subscribe(struct websocket_subscribe_s args); - -#define websocket_subscribe(wbsckt, ...) \ - websocket_subscribe((struct websocket_subscribe_s){.ws = wbsckt, __VA_ARGS__}) - -/** - * Unsubscribes from a channel. - * - * Failures are silent. - * - * All subscriptions are automatically revoked once the websocket is closed. So - * only use this function to unsubscribe while the websocket is open. - */ -void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id); - -/** Optimize generic broadcasts, for use in websocket_optimize4broadcasts. */ -#define WEBSOCKET_OPTIMIZE_PUBSUB (-32) -/** Optimize text broadcasts, for use in websocket_optimize4broadcasts. */ -#define WEBSOCKET_OPTIMIZE_PUBSUB_TEXT (-33) -/** Optimize binary broadcasts, for use in websocket_optimize4broadcasts. */ -#define WEBSOCKET_OPTIMIZE_PUBSUB_BINARY (-34) - -/** - * Enables (or disables) broadcast optimizations. - * - * This is performed automatically by the `websocket_subscribe` function. - * However, this function is provided for enabling the pub/sub metadata based - * optimizations for external connections / subscriptions. - * - * This function allows enablement (or disablement) of these optimizations: - * - * * WEBSOCKET_OPTIMIZE_PUBSUB - optimize all direct transmission messages, - * best attempt to detect Text vs. Binary data. - * * WEBSOCKET_OPTIMIZE_PUBSUB_TEXT - optimize direct pub/sub text messages. - * * WEBSOCKET_OPTIMIZE_PUBSUB_BINARY - optimize direct pub/sub binary messages. - * - * Note: to disable an optimization it should be disabled the same amount of - * times it was enabled - multiple optimization enablements for the same type - * are merged, but reference counted (disabled when reference is zero). - * - * Note2: The pub/sub metadata type ID will match the optimnization type - * requested (i.e., `WEBSOCKET_OPTIMIZE_PUBSUB`) and the optimized data is a - * FIOBJ String containing a pre-encoded WebSocket packet ready to be sent. - * i.e.: - * - * FIOBJ pre_wrapped = (FIOBJ)fio_message_metadata(msg, - * WEBSOCKET_OPTIMIZE_PUBSUB); - * fiobj_send_free((intptr_t)msg->udata1, fiobj_dup(pre_wrapped)); - */ -void websocket_optimize4broadcasts(intptr_t type, int enable); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/iodine.gemspec b/iodine.gemspec index 7c6e8be9..e59623a9 100644 --- a/iodine.gemspec +++ b/iodine.gemspec @@ -1,50 +1,46 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'iodine/version' +# frozen_string_literal: true + +require_relative "lib/iodine/version" Gem::Specification.new do |spec| - spec.name = 'iodine' - spec.version = Iodine::VERSION - spec.authors = ['Boaz Segev'] - spec.email = ['bo@plezi.io'] - - spec.summary = 'iodine - a fast HTTP / Websocket Server with Pub/Sub support, optimized for Ruby MRI on Linux / BSD / Windows' - spec.description = 'A fast HTTP / Websocket Server with built-in Pub/Sub support (with or without Redis), static file support and many other features, optimized for Ruby MRI on Linux / BSD / macOS / Windows' - spec.homepage = 'https://github.com/boazsegev/iodine' - spec.license = 'MIT' - - # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or - # delete this section to allow pushing this gem to any host. - if spec.respond_to?(:metadata) - spec.metadata['allowed_push_host'] = 'https://rubygems.org' - else - raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' + spec.name = "iodine" + spec.version = Iodine::VERSION + spec.authors = ["Boaz Segev"] + spec.email = ["bo@bowild.com"] + + spec.summary = 'iodine - a fast HTTP / Websocket Server with Pub/Sub support, optimized for Ruby MRI on Linux / BSD / Windows' + spec.description = 'A fast HTTP / Websocket Server with built-in Pub/Sub support (with or without Redis), static file support and many other features, optimized for Ruby MRI on Linux / BSD / macOS / Windows' + spec.homepage = "https://github.com/boazsegev/iodine" + spec.license = "MIT" + spec.required_ruby_version = ">= 3.0.0" + + spec.metadata["allowed_push_host"] = "https://rubygems.org" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = "https://github.com/boazsegev/iodine" + spec.metadata["changelog_uri"] = "https://github.com/boazsegev/iodine/CHANGELOG.md" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + end end - - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = %w(lib ext) - - spec.extensions = %w(ext/iodine/extconf.rb) - - spec.required_ruby_version = '>= 2.2.2' # Because earlier versions had been discontinued - - spec.requirements << 'A Unix based system: Linux / macOS / BSD.' - spec.requirements << 'An updated C compiler.' - spec.requirements << 'Ruby >= 2.3.8 (Ruby EOL).' - spec.requirements << 'Ruby >= 2.5.0 recommended.' - spec.requirements << 'TLS requires OpenSSL >= 1.1.0.' - spec.requirements << 'Or Windows with Ruby >= 3.0.0 build with MingW and MingW as compiler.' - - # spec.add_development_dependency 'bundler', '>= 1.10', '< 2.0' - spec.add_development_dependency 'rake', '>= 12.0', '< 14.0' - spec.add_development_dependency 'minitest', '>=5', '< 6.0' - spec.add_development_dependency 'rspec', '>=3.9.0', '< 4.0' - spec.add_development_dependency 'spec', '>=5.3.0', '< 6.0' + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib", "ext"] + + # Development dependencies spec.add_development_dependency 'rake-compiler', '>= 1', '< 2.0' + spec.extensions = %w(ext/iodine/extconf.rb) spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n" + "Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations)." + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html end diff --git a/lib/iodine.rb b/lib/iodine.rb index e326387f..cbb0f4c3 100644 --- a/lib/iodine.rb +++ b/lib/iodine.rb @@ -1,274 +1,57 @@ +# frozen_string_literal: true + require 'stringio' # Used internally as a default RackIO require 'socket' # TCPSocket is used internally for Hijack support -# require 'openssl' # For SSL/TLS support using OpenSSL +require 'logger' # rack.logger became a required field for Rack -require_relative './iodine/version' -require_relative './iodine/iodine_ext' # loading a binary C extension +require_relative "iodine/version" -# Iodine is an HTTP / WebSocket server as well as an Evented Network Tool Library. In essense, Iodine is a Ruby port for the [facil.io](http://facil.io) C library. -# -# Here is a simple telnet based echo server using Iodine (see full list at {Iodine::Connection}): -# -# -# require 'iodine' -# # define the protocol for our service -# module EchoProtocol -# def on_open(client) -# # Set a connection timeout -# client.timeout = 10 -# # Write a welcome message -# client.write "Echo server running on Iodine #{Iodine::VERSION}.\r\n" -# end -# # this is called for incoming data - note data might be fragmented. -# def on_message(client, data) -# # write the data we received -# client.write "echo: #{data}" -# # close the connection when the time comes -# client.close if data =~ /^bye[\n\r]/ -# end -# # called if the connection is still open and the server is shutting down. -# def on_shutdown(client) -# # write the data we received -# client.write "Server going away\r\n" -# end -# extend self -# end -# # create the service instance, the block returns a connection handler. -# Iodine.listen(port: "3000") { EchoProtocol } -# # start the service -# Iodine.threads = 1 -# Iodine.start -# -# -# -# Methods for setting up and starting {Iodine} include {start}, {threads}, {threads=}, {workers} and {workers=}. -# -# Methods for setting startup / operational callbacks include {on_idle}, {on_state}. -# -# Methods for asynchronous execution include {run} (same as {defer}), {run_after} and {run_every}. -# -# Methods for application wide pub/sub include {subscribe}, {unsubscribe} and {publish}. Connection specific pub/sub methods are documented in the {Iodine::Connection} class). -# -# Methods for TCP/IP, Unix Sockets and HTTP connections include {listen} and {connect}. -# -# Note that the HTTP server supports both TCP/IP and Unix Sockets as well as SSE / WebSockets extensions. -# -# Iodine doesn't call {patch_rack} automatically, but doing so will improve Rack's performace. -# -# Please read the {file:README.md} file for an introduction to Iodine. -# -module Iodine +require "iodine/iodine" - # Will monkey patch some Rack methods to increase their performance. - # - # This is recommended, see {Iodine::Rack::Utils} for details. - def self.patch_rack - begin - require 'rack' - rescue LoadError - end - ::Rack::Utils.class_eval do - Iodine::Base::MonkeyPatch::RackUtils.methods(false).each do |m| - ::Rack::Utils.define_singleton_method(m, - Iodine::Base::MonkeyPatch::RackUtils.instance_method(m) ) - end - end - end +# Support common ENV options for setting concurrency. +Iodine.threads = ENV["THREADS"].to_i if Iodine.threads.zero? +Iodine.workers = ENV["WORKERS"].to_i if Iodine.workers.zero? +Iodine.threads = ENV["WEB_THREADS"].to_i if Iodine.threads.zero? +Iodine.workers = ENV["WEB_WORKERS"].to_i if Iodine.workers.zero? +Iodine.threads = ENV["RAILS_MAX_THREADS"].to_i if Iodine.threads.zero? +Iodine.workers = ENV["WEB_CONCURRENCY"].to_i if Iodine.workers.zero? - # @deprecated use {Iodine.listen} with `service: :http`. - # - # Sets a block of code to run once a Worker process shuts down (both in single process mode and cluster mode). - def self.listen2http(args, &block) - warn "Iodine.listen2http is deprecated, use Iodine.listen(service: :http)." - args[:service] = :http; - Iodine.listen(args, &block) +module Iodine + class Base + # Trap SIGINT only if it wasn't trapped beforehand. + def self.trap_sigint + old_trap = trap("SIGINT") { puts "(#{Process.pid}) Received Ctrl-C and shutting down iodine." } + trap("SIGINT", &old_trap) if(old_trap && old_trap.respond_to?(:call)) end + end +end - # @deprecated use {Iodine.on_state}. - # - # Sets a block of code to run before a new worker process is forked (cluster mode only). - def self.before_fork(&block) - warn "Iodine.before_fork is deprecated, use Iodine.on_state(:before_fork)." - Iodine.on_state(:before_fork, &block) - end - # @deprecated use {Iodine.on_state}. - # - # Sets a block of code to run after a new worker process is forked (cluster mode only). - # - # Code runs in both the parent and the child. - def self.after_fork(&block) - warn "Iodine.after_fork is deprecated, use Iodine.on_state(:after_fork)." - Iodine.on_state(:after_fork, &block) - end - # @deprecated use {Iodine.on_state}. - # - # Sets a block of code to run in the worker process, after a new worker process is forked (cluster mode only). - def self.after_fork_in_worker(&block) - warn "Iodine.after_fork_in_worker is deprecated, use Iodine.on_state(:enter_child)." - Iodine.on_state(:enter_child, &block) - end - # @deprecated use {Iodine.on_state}. - # - # Sets a block of code to run in the master / root process, after a new worker process is forked (cluster mode only). - def self.after_fork_in_master(&block) - warn "Iodine.after_fork_in_master is deprecated, use Iodine.on_state(:enter_master)." - Iodine.on_state(:enter_master, &block) - end - # @deprecated use {Iodine.on_state}. - # - # Sets a block of code to run once a Worker process shuts down (both in single process mode and cluster mode). - def self.on_shutdown(&block) - warn "Iodine.on_shutdown is deprecated, use Iodine.on_state(:on_finish)." - Iodine.on_state(:on_finish, &block) - end - module PubSub - # @deprecated use {Iodine::PubSub.detach}. - def self.dettach(engine) - warn "Iodine::PubSub.dettach is deprecated (was a typo), use Iodine::PubSub.detach(engine)." - Iodine::PubSub.detach(engine) - end - end +# Trap SIGINT only if it wasn't trapped beforehand +Iodine::Base.trap_sigint - ### trap some signals to avoid excessive exception reports - begin - old_sigint = Signal.trap("SIGINT") { old_sigint.call if old_sigint.respond_to?(:call) } - rescue Exception - end - begin - old_sigterm = Signal.trap("SIGTERM") { old_sigterm.call if old_sigterm.respond_to?(:call) } - rescue Exception - end - begin - old_sigpipe = Signal.trap("SIGPIPE") { old_sigpipe.call if old_sigpipe.respond_to?(:call) } - rescue Exception - end - begin - old_sigusr1 = Signal.trap("SIGUSR1") { old_sigusr1.call if old_sigusr1.respond_to?(:call) } - rescue Exception - end +# NeoRack Support +if !defined?(Server) + # NeoRack support requires Iodine to set the name Server to the Iodine NeoRack server module. + Server = Iodine + module Server + # NeoRack support requires Server::Event to map to the class implementing the NeoRack events. + Event = Connection + end end -require 'rack/handler/iodine' unless defined? ::Iodine::Rack::IODINE_RACK_LOADED - -### Automatic ActiveRecord and Sequel support for forking (preventing connection sharing) +### Automatic Sequel support for forking (preventing connection sharing) Iodine.on_state(:before_fork) do - if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:connection) - begin - ActiveRecord::Base.connection.disconnect! - rescue - end - end if defined?(Sequel) begin - Sequel::DATABASES.each { |database| database.disconnect } - rescue - end - end -end -Iodine.on_state(:after_fork) do - if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:establish_connection) - begin - ActiveRecord::Base.establish_connection + Sequel::DATABASES.each(&:disconnect) rescue end end end -### Parse CLI for default HTTP settings -Iodine::Base::CLI.parse if defined?(IODINE_PARSE_CLI) && IODINE_PARSE_CLI - -### Set default port (if missing) -Iodine::DEFAULT_SETTINGS[:port] ||= (ENV["PORT"] || "3000") - -### Set default binding (if missing) -Iodine::DEFAULT_SETTINGS[:address] ||= nil - ### Initialize Redis if set in CLI -Iodine::PubSub.default = Iodine::PubSub::Redis.new(Iodine::DEFAULT_SETTINGS[:redis_], ping: Iodine::DEFAULT_SETTINGS[:redis_ping_]) if Iodine::DEFAULT_SETTINGS[:redis_] - -### PID file generation -if Iodine::DEFAULT_SETTINGS[:pid_] - pid_filename = Iodine::DEFAULT_SETTINGS[:pid_] - Iodine::DEFAULT_SETTINGS.delete :pid_ - pid_filename << "iodine.pid" if(pid_filename[-1] == '/') - if File.exist?(pid_filename) - raise "pid filename shold point to a valid file name (not a folder)!" if(!File.file?(pid_filename)) - File.delete(pid_filename) - end - Iodine.on_state(:pre_start) do - IO.binwrite(pid_filename, "#{Process.pid}\r\n") - end - Iodine.on_state(:on_finish) do - File.delete(pid_filename) - end -end - -### Puma / Thin DSL compatibility - depracated (DSLs are evil) - -if(!defined?(after_fork)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code whenever a new worker process spins up (performed once per worker). - def after_fork(*args, &block) - warn "after_fork is deprecated, use Iodine.on_state(:after_fork)." - Iodine.on_state(:after_fork, &block) - end -end -if(!defined?(after_fork_in_worker)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code whenever a new worker process spins up (performed once per worker). - def after_fork_in_worker(*args, &block) - warn "after_fork_in_worker is deprecated, use Iodine.on_state(:enter_child)." - Iodine.on_state(:enter_child, &block) - end -end -if(!defined?(after_fork_in_master)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code whenever a new worker process spins up (performed once per worker). - def after_fork_in_master(*args, &block) - warn "after_fork_in_master is deprecated, use Iodine.on_state(:enter_master)." - Iodine.on_state(:enter_master, &block) - end -end -if(!defined?(on_worker_boot)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code before a new worker process spins up (performed once per worker). - def on_worker_boot(*args, &block) - warn "on_worker_boot is deprecated, use Iodine.on_state(:after_fork)." - Iodine.on_state(:after_fork, &block) - end -end -if(!defined?(on_worker_fork)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code before a new worker process spins up (performed once per worker). - def on_worker_fork(*args, &block) - warn "on_worker_fork is deprecated, use Iodine.on_state(:before_fork)." - Iodine.on_state(:before_fork, &block) - end -end -if(!defined?(before_fork)) - # @deprecated use {Iodine.on_state}. - # - # Performs a block of code just before a new worker process spins up (performed once per worker, in the master thread). - def before_fork(*args, &block) - warn "before_fork is deprecated, use Iodine.on_state(:before_fork)." - Iodine.on_state(:before_fork, &block) - end -end +# Iodine::PubSub.default = Iodine::PubSub::Redis.new(Iodine::Base::CLI['-r'], ping: Iodine::Base::CLI['-rp']) if Iodine::Base::CLI['-r'] - -############# -## At end of loading - -### Load configuration filer -if Iodine::DEFAULT_SETTINGS[:conf_] - require Iodine::DEFAULT_SETTINGS[:conf_] - Iodine::DEFAULT_SETTINGS.delete :conf_ -end diff --git a/lib/iodine/benchmark.rb b/lib/iodine/benchmark.rb new file mode 100644 index 00000000..1c6d0223 --- /dev/null +++ b/lib/iodine/benchmark.rb @@ -0,0 +1,250 @@ +require 'iodine' + +module Iodine + + # This module includes benchmarking methods to test performance. + # + # Before running the benchmarks, require the `Iodine::Benchmark` module: + # + # require 'iodine/benchmark' + # + # This module does **not** include benchmarking for the Iodine server itself. + # + # These methods and the results shown should be used with caution, as they often tell a partial story. + # + # To run the benchmarks, you'll need to install the `benchmark-ips` gem. + # + # To install the benchmarking gem add `benchmark-ips` to your `Gemfile` or run: + # + # gem install benchmark-ips + # + module Benchmark + + # Benchmarks Iodine::JSON vs native Ruby JSON and vs Oj (if installed). + def self.json(data_length = 1000) + require 'oj' rescue nil + require 'benchmark/ips' + require 'json' + # make a big data store with nothings + data_1000 = [] + data_length.times do + tmp = {f: rand() }; + tmp[:i] = (tmp[:f] * 1000000).to_i + tmp[:str] = tmp[:i].to_s + tmp[:escaped] = "Hello\nNew\nLines!" + tmp[:sym] = tmp[:str].to_sym + tmp[:ary] = [] + tmp[:ary_empty] = [] + tmp[:hash_empty] = Hash.new + 100.times {|i| tmp[:ary] << i } + data_1000 << tmp + end + json_string = data_1000.to_json + puts "-----" + puts "Benchmark #{data_1000.length} item tree, and #{json_string.length} bytes of JSON" + # benchmark stringification + ::Benchmark.ips do |x| + x.report(" Ruby obj.to_json") do |times| + data_1000.to_json + end + x.report("Iodine::JSON.stringify") do |times| + Iodine::JSON.stringify(data_1000) + end + if(defined?(Oj)) + x.report(" Oj.dump") do |times| + Oj.dump(data_1000) + end + end + x.compare! + end ; nil + # benchmark parsing + ::Benchmark.ips do |x| + x.report(" Ruby JSON.parse") do |times| + JSON.parse(json_string) + end + x.report("Iodine::JSON.parse") do |times| + Iodine::JSON.parse(json_string) + end + if(defined?(Oj)) + x.report(" Oj.load") do |times| + Oj.load(json_string) + end + end + x.compare! + end + nil + end + + # Benchmarks Iodine::Mustache vs the original Mustache Ruby gem (if installed). + # + # Benchmark code was copied, in part, from: + # https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb + # + # The test is, sadly, biased and doesn't test for missing elements, proc/method resolution or template partials. + # + def self.mustache(items = 1000) + require 'benchmark/ips' + require 'mustache' + template = """ + {{#products}} +
      + +
      + {{/products}} + """ + + # fill Hash objects with values for template rendering + data = { + products: [] + } + data_escaped = { + products: [] + } + + items.times do + data[:products] << { + :external_index=>"product", + :url=>"/products/7", + :image=>"products/product.jpg" + } + data_escaped[:products] << { + :external_index=>"This should've been \"properly\" escaped.", + :url=>"/products/7", + :image=>"products/product.jpg" + } + end + + # prepare Iodine::Mustache reduced Mustache template engine + mus_view = Iodine::Mustache.new template: template + + # prepare official Mustache template engine + view = ::Mustache.new + view.template = template + view.render # Call render once so the template will be compiled + + # benchmark different use cases + ::Benchmark.ips do |x| + x.report("Ruby Mustache render list of #{items.to_s}") do |times| + view.render(data) + end + x.report("Iodine::Mustache render list of #{items.to_s}") do |times| + mus_view.render(data) + end + + x.report("Ruby Mustache render list of #{items.to_s} with escaped data") do |times| + view.render(data_escaped) + end + x.report("Iodine::Mustache render list of #{items.to_s} with escaped data") do |times| + mus_view.render(data_escaped) + end + + x.report("Ruby Mustache - no caching - render list of #{items.to_s}") do |times| + ::Mustache.render(template, data) + end + x.report("Iodine::Mustache - no caching - render list of #{items.to_s}") do |times| + Iodine::Mustache.render(template: template, ctx: data) + end + x.compare! + end ; nil + end + + # Benchmark the {Iodine::Utils} module and see if you want to use {Iodine::Utils.monkey_patch} when using Rack. + def self.utils + require 'rack' + require 'cgi' + require 'benchmark/ips' + # a String in need of decoding + encoded = '%E3 + %83 + %AB + %E3 + %83 + %93 + %E3 + %82 + %A4 + %E3 + %82 + %B9 + %E3 + %81 + %A8' + decoded = ::Rack::Utils.unescape(encoded, "binary") + html_xss = "" + html_xss_safe = Rack::Utils.escape_html html_xss + short_str1 = Array.new(64) { 'a' } .join + short_str2 = Array.new(64) { 'a' } .join + long_str1 = Array.new(4094) { 'a' } .join + long_str2 = Array.new(4094) { 'a' } .join + now_preclaculated = Time.now + ::Benchmark.ips do |bm| + bm.report(" Iodine rfc2822") { Iodine::Utils.rfc2822(now_preclaculated) } + bm.report(" Rack rfc2822") { ::Rack::Utils.rfc2822(now_preclaculated) } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine unescape") { Iodine::Utils.unescape encoded } + bm.report(" Rack unescape") { ::Rack::Utils.unescape encoded } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine escape") { Iodine::Utils.escape decoded } + bm.report(" Rack escape") { ::Rack::Utils.escape decoded } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine escape HTML") { Iodine::Utils.escape_html html_xss } + bm.report(" Rack escape HTML") { ::Rack::Utils.escape_html html_xss } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine unescape HTML") { Iodine::Utils.unescape_html html_xss_safe } + bm.report(" CGI unescape HTML") { CGI.unescapeHTML html_xss_safe } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine secure compare (#{short_str2.bytesize} Bytes)") { Iodine::Utils.secure_compare short_str1, short_str2 } + bm.report(" Rack secure compare (#{short_str2.bytesize} Bytes)") { ::Rack::Utils.secure_compare short_str1, short_str2 } + bm.compare! + end; ::Benchmark.ips do |bm| + bm.report("Iodine secure compare (#{long_str1.bytesize} Bytes)") { Iodine::Utils.secure_compare long_str1, long_str2 } + bm.report(" Rack secure compare (#{long_str1.bytesize} Bytes)") { ::Rack::Utils.secure_compare long_str1, long_str2 } + bm.compare! + end && nil + end + + # Benchmark the internal mini-Hash map. + def self.minimap(counter = 30) + require 'benchmark/ips' + keys = [] + counter.times {|i| keys << "counter_string_#{i}" } + frozen_keys = [] + counter.times {|i| keys[i].dup.freeze } + + tests = [ + ["New (#{counter + 1} Empty Maps)", Proc.new {|m| counter.times { m.class.new } }, true ], + ["New + Set (#{counter} Numbers)", Proc.new {|m| counter.times {|i| m[i] = i } }, true ], + ["Overwrite (#{counter} Numbers)", Proc.new {|m| counter.times {|i| m[i] = i } } ], + ["Get (#{counter} Numbers + 1 missing)", Proc.new {|m| (counter + 1).times {|i| m[i] } } ], + ["New + Set (#{counter} Strings)", Proc.new {|m| counter.times {|i| m[keys[i]] = i } }, true ], + ["Overwrite (#{counter} Strings)", Proc.new {|m| counter.times {|i| m[keys[i]] = i } } ], + ["Get (#{counter} Strings + 1 missing)", Proc.new {|m| (counter + 1).times {|i| m[keys[i]] } } ], + ["New + Set (#{counter} Strings.freeze)", Proc.new {|m| counter.times {|i| m[frozen_keys[i]] = i } }, true ], + ["Overwrite (#{counter} Strings.freeze)", Proc.new {|m| counter.times {|i| m[frozen_keys[i]] = i } } ], + ["Get (#{counter} Strings.freeze + 1 missing)", Proc.new {|m| (counter + 1).times {|i| m[frozen_keys[i]] } } ], + ["each (#{counter} Strings)", Proc.new {|m| m.each {|k,v| k; v; } } ] + ] + + + klasses = [Iodine::Base::MiniMap, ::Hash] + + maps = [] + klasses.each {|k| maps << k.new } + + tests.each do |t| + ::Benchmark.ips do |bm| + klasses.each_index do |i| + if(t[2]) + bm.report("#{klasses[i].name.ljust(22)} #{t[0]}") { maps[i] = klasses[i].new ; t[1].call(maps[i]) } + else + bm.report("#{klasses[i].name.ljust(22)} #{t[0]}") { t[1].call(maps[i]) } + end + end + bm.compare! + end + end + nil + end + + end +end diff --git a/lib/iodine/connection.rb b/lib/iodine/connection.rb deleted file mode 100644 index 93024512..00000000 --- a/lib/iodine/connection.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Iodine - - # The default connection settings used by {Iodine.listen} and {Iodine.connect}. - # - # It's a Hash object that allows Iodine default values to be manipulated. i.e.: - # - # DEFAULT_SETTINGS[:port] = "8080" # replaces the default port, which is `ENV["port"] || "3000"`. - DEFAULT_SETTINGS = {} - - # @deprecated use {Iodine::DEFAULT_SETTINGS}. - # - # The default connection settings used by {Iodine.listen} and {Iodine.connect}. - DEFAULT_HTTP_ARGS = DEFAULT_SETTINGS - - # Iodine's {Iodine::Connection} class is the class that TCP/IP, WebSockets and SSE connections inherit from. - # - # Instances of this class are passed to the callback objects. i.e.: - # - # module MyConnectionCallbacks - # - # # called when the callback object is linked with a new client - # def on_open client - # client.is_a?(Iodine::Connection) # => true - # end - # - # # called when data is available - # def on_message client, data - # client.is_a?(Iodine::Connection) # => true - # end - # - # # called when the server is shutting down, before closing the client - # # (it's still possible to send messages to the client) - # def on_shutdown client - # client.is_a?(Iodine::Connection) # => true - # end - # - # # called when the client is closed (no longer available) - # def on_close client - # client.is_a?(Iodine::Connection) # => true - # end - # - # # called when all the previous calls to `client.write` have completed - # # (the local buffer was drained and is now empty) - # def on_drained client - # client.is_a?(Iodine::Connection) # => true - # end - # - # # called when timeout was reached, llowing a `ping` to be sent - # def ping client - # client.is_a?(Iodine::Connection) # => true - # clint.close() # close connection on timeout is the default - # end - # - # # Allows the module to be used as a static callback object (avoiding object allocation) - # extend self - # end - # - # All connection related actions can be performed using the methods provided through this class. - class Connection - end -end diff --git a/lib/iodine/documentation.rb b/lib/iodine/documentation.rb new file mode 100644 index 00000000..cdc10d74 --- /dev/null +++ b/lib/iodine/documentation.rb @@ -0,0 +1,1047 @@ +if !defined?(Iodine) +# Iodine is a high performance HTTP / WebSocket server and Evented Network Tool Library. +# +# In essence, Iodine is a partial Ruby port for the [facil.io](http://facil.io) C library. +# +# Here is a simple text echo server using Iodine (see full list at {Iodine::Connection}): +# +# +# require 'iodine' +# +# # Define the protocol for our service +# module EchoProtocol +# +# # Called when a new connection is opened. +# def on_open(client) +# # Write a welcome message +# puts "MY LOG: Client #{client.object_id} opened"; +# client.write "Echo server running on Iodine #{Iodine::VERSION}.\r\n" +# end +# +# # this is called for incoming data - note data might be fragmented. +# def on_message(client, data) +# # write the data we received +# client.write "echo: #{data}" +# # close the connection when the time comes +# client.close if data =~ /^bye[\n\r]/i +# end +# +# # called if the connection is still open and the server is shutting down. +# def on_shutdown(client) +# # write the data we received +# client.write "Server going away\r\n" +# end +# +# # Called once all calls to `write` have been sent on the wire. +# def on_drained(client) +# puts "MY LOG: Client #{client.object_id} outgoing buffer now empty"; +# end +# +# # Called when `ping` time has elapsed with no data transferred on the wire. +# def on_timeout(client) +# puts "MY LOG: Client #{client.object_id} timing out"; +# # Write something to reset timeout. +# client.write "Server message: are you there?\r\n" +# end +# +# # Called when the connection is closed. +# def on_close(client) +# # Log closure +# puts "MY LOG: Client #{client.object_id} closed"; +# end +# +# # We can use a singleton pattern, +# # as the Protocol object doesn't contain any per-connection data. +# extend self +# end +# +# # create the service instance, the block returns a connection handler. +# Iodine.listen(handler: EchoProtocol, service: :tcp) +# # start the service +# Iodine.threads = 1 +# Iodine.start +# +# +# +# Methods for setting up and starting {Iodine} include {start}, {threads}, {threads=}, {workers} and {workers=}. +# +# Methods for setting startup / operational callbacks include {on_state}. +# +# Methods for asynchronous execution include {run} (same as {defer}), {run_after} and {run_every}. +# +# Methods for application wide pub/sub include {subscribe}, {unsubscribe} and {publish}. Connection specific pub/sub methods are documented in the {Iodine::Connection} class). +# +# Methods for TCP/IP, Unix Sockets and HTTP connections include {listen} and {connect}. +# +# Note that the HTTP server supports both TCP/IP and Unix Sockets as well as SSE / WebSockets extensions. +# +# Iodine doesn't call {patch_rack} automatically, but doing so will improve Rack's performace. +# +# Please read the {file:README.md} file for an introduction to Iodine. +# +module Iodine + + # [Number] The number of worker threads per worker process. Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc'). + def self.threads(); end + # [Number] The number of worker threads per worker process. Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc'). + def self.threads=(threads); end + # [Number] The worker processes. Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc'). + def self.workers(); end + # [Number] The worker processes. Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc'). + def self.workers=(workers); end + + # Starts the Iodine IO reactor. Method returns only after Iodine is stopped and cleanup is complete. + def self.start(); end + # Stops the Iodine IO reactor (as if `SIGINT` or `SIGTERM` were detected. Causes {start} to return. + def self.stop(); end + + # [Boolean] Returns `true` if current process is the master process. + def self.master?(); end + # [Boolean] Returns `true` if current process is a worker process. + # + # Note: may also be the master process if {Iodine.workers} was zero (non-cluster mode). + def self.worker?(); end + # [Boolean] Returns `true` if the Iodine IO reactor is running in the current process and the IO reactor wasn't signaled to stop. + def self.running?(); end + + # [Number] The level of verbosity used for Iodine logging. + def self.verbosity(); end + # [Number] The level of verbosity used for Iodine logging. + # + # Each level of logging includes all lower levels. + # + # - 5 == Debug messages. + # - 4 == Normal. + # - 3 == Warnings. + # - 2 == Errors. + # - 1 == Fatal Errors only. + def self.verbosity=(logging_level); end + + # Listens on the `url` to connections requesting `service`. + # + # The `handler` handles the following events and supports the following callbacks: + # + # | | | + # |---|---| + # | `on_http(client)` | Called when an HTTP request arrives. | + # | `on_authenticate_websocket(client)` | Called when a WebSocket Upgrade request is detected, **must** return `true` or the request is denied. | + # | `on_open(client)` | Called when a WebSocket or `raw`/`tcp`connection had been established. | + # | `on_message(client, data)` | Called when a WebSocket message arrives. For `raw`/`tcp` connections, `data` contains the part of the byte stream that was read by Iodine. | + # | `on_drained(client)` | Called when all the calls to `client.write` have been completed (the outgoing buffer has been sent). | + # | `on_timeout(client)` | Called when `ping` time has elapsed with no data was exchanged on the wire. | + # | `on_shutdown(client)` | Called if the connection is still open and the server is shutting down. | + # | `on_close(client)` | Called when the connection is closed | + # | `on_authenticate_sse(client)` | Called when an Event Source (SSE) request is detected, **must** return `true` or the request is denied. | + # | `on_eventsource_reconnect(client)` | Called when an Event Source (SSE) request contains re-connection data. | + # | `on_eventsource(client, event)` | Called when an Event Source (SSE) event is received (client mode). | + # | `on_authenticate(client)` | **MAY** be defined instead of `on_authenticate_websocket` and `on_authenticate_sse` to unify the logic of both authentication callbacks. | + # + # Accepts the following named arguments: + # + # | | | + # |---|---| + # | url | The address to listen at (or `nil`). | + # | handler | The handler (callback) object / NeoRack application / Rack application | + # | service | The service to be listening to (`http`, `https`, `ws`, `tcp`, `unix`). Can be provided as part of the `url`'s `scheme`, i.e.: `"unix://./my_unix_socket.sock"`. | + # | tls | An Iodine::TLS instance. | + # | public | (HTTP only) Public folder for static file service. | + # | max_age | (HTTP only) The default maximum age for caching (when the etag header is provided). | + # | max_header_size | (HTTP only) The maximum size for HTTP headers. | + # | max_line_len | (HTTP only) The maximum size for a single HTTP header. | + # | max_body_size | (HTTP only) The maximum size for an HTTP payload. | + # | max_msg_size | (WebSocket only) The maximum size for a WebSocket message. | + # | timeout | (HTTP only) `keep-alive` timeout. | + # | ping | Connection timeout (WebSocket / `raw` / `tcp`). | + # | log | (HTTP only) If `true`, logs `http` requests. | + # | &block | An (optional) Rack application. | + # + # Note: Either a `handler` or a `block` (Rack App) **must** be provided. + def self.listen(url = "0.0.0.0:3000", handler = nil, service = nil); end + + # Sets a block of code to run when Iodine's core state is updated. + # + # @param [Symbol] event the state event for which the block should run (see list). + # + # @since 0.7.9 + # + # The state event Symbol can be any of the following: + # + # | | | + # |---|---| + # | `:pre_start` | the block will be called once before starting up the IO reactor. | + # | `:before_fork` | the block will be called before each time the IO reactor forks a new worker. | + # | `:after_fork` | the block will be called after each fork (both in parent and workers). | + # | `:enter_child` | the block will be called by a worker process right after forking. | + # | `:enter_master` | the block will be called by the master process after spawning a worker (after forking). | + # | `:on_start` | the block will be called every time a *worker* process starts. In single process mode, the master process is also a worker. | + # | `:on_parent_crush` | the block will be called by each worker the moment it detects the master process crashed. | + # | `:on_child_crush` | the block will be called by the parent (master) after a worker process crashed. | + # | `:start_shutdown` | the block will be called before starting the shutdown sequence. | + # | `:on_stop` | the block will be called just before stopping iodine (both on child and parent processes). | + # + # Code runs in both the parent and the child. + def self.on_state(state, &block); end + + + # Runs a block of code in the main IO thread (adds the code to the event queue). + # + # Always returns the block of code to executed (Proc object). + # + # Code will be executed only while Iodine is running (after {Iodine.start}). + # + # Code blocks that where scheduled to run before Iodine enters cluster mode will run on all child processes. + # + # The code will always run in single-threaded mode, running on the main IO thread used by Iodine. + # + # Note: blocking code MUST NOT be used in the block passed to this method, + # or Iodine's IO will hang while waiting for the blocking code to finish. + # + # Note: method also accepts a method object if passed as a parameter. + def self.defer(&block); end + + # Runs a block of code in the a worker thread (adds the code to the event queue). + # + # Always returns the block of code to executed (Proc object). + # + # Code will be executed only while Iodine is running (after {Iodine.start}). + # + # Code blocks that where scheduled to run before Iodine enters cluster mode will run on all child processes. + # + # The code may run in parallel with other calls to `run` or `defer`, as it will run in one of the worker threads. + # + # Note: method also accepts a method object if passed as a parameter. + def self.run(&block); end + + # Runs the required block after the specified number of milliseconds have passed. + # + # The block will run in the main IO thread (adds the code to the event queue). + # + # Time is counted only once Iodine started running (using {Iodine.start}). + # + # Accepts: + # + # | | | + # |---|---| + # | `:milliseconds` | the number of milliseconds between event repetitions. | + # | `:repetitions` | the number of event repetitions. Defaults to 1 (performed once). Set to 0 for never ending. | + # | `:block` | (required) a block is required, as otherwise there is nothing to perform. | + # + # + # The event will repeat itself until the number of repetitions had been depleted. + # + # Always returns a copy of the block object. + def self.run_after(milliseconds, repetitions = 1, &block); end + + # Publishes a message to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the channel name to publish to (String). + # - `filter`: the filter to publish to (Number < 32,767). + # - `message`: the actual message to publish. + # - `engine`: the pub/sub engine to use (if not the default one). + # + # i.e.: + # + # Iodine.publish("channel_name", 0, "payload") + # Iodine.publish(channel: "name", message: "payload") + def self.publish(channel = nil, filter = 0, message = nil, engine = nil); end + # Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments and an **optional** block: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # - `callback`: an **optional** object that answers to call and accepts a single argument (the message structure). + # + # Either a proc or a block **must** be provided for global subscriptions. + # + # i.e.: + # + # Iodine.subscribe("name") + # Iodine.subscribe(channel: "name") + # # or with filter + # Iodine.subscribe("name", 1) + # Iodine.subscribe(channel: "name", filter: 1) + # # or only a filter + # Iodine.subscribe(nil, 1) + # Iodine.subscribe(filter: 1) + # # with / without a proc + # Iodine.subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == Qnil;} + # Iodine.subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; } + def self.subscribe(channel = nil, filter = 0, &callback); end + # Un-Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # + # i.e.: + # + # Iodine.unsubscribe("name") + # Iodine.unsubscribe(channel: "name") + # # or with filter + # Iodine.unsubscribe("name", 1) + # Iodine.unsubscribe(channel: "name", filter: 1) + # # or only a filter + # Iodine.unsubscribe(nil, 1) + # Iodine.unsubscribe(filter: 1) + def self.unsubscribe(channel = nil, filter = 0); end + + ####################### + + # {Iodine::JSON} exposes the {Iodine::Connection#write} fallback behavior when called with non-String objects. + # + # The fallback behavior is similar to (though faster than) calling: + # + # client.write(Iodine::JSON.stringify(data)) + # + # If you want to work with JSON, consider using the `oj` gem. + # + # This API is mostly to test for Iodine JSON input/output errors and reflects what the C layer sees. + # + # ## Performance + # + # Performance... could be better. + # + # Converting Ruby objects into a JSON String (Strinfigying) should be fast even though the String data is copied twice, once to C and then to Ruby. + # + # However, Converting a JSON object into Ruby Objects is currently slow and it is better to use the `oj` gem or even the Ruby builtin parser. + # + # The reason is simple - the implementation is designed to create C objects (C Hash Maps, C Arrays, etc'), not Ruby objects. When converting from a String to Ruby Objects, the data is copied twice, once to C and then to Ruby. + # + # This especially effects parsing, where more objects are allocated, whereas {Iodine::JSON.stringify} only (re)copies the String data which is a single continuous block of memory. + # + # That's why {Iodine::JSON.stringify} is significantly faster than the Ruby `object.to_json` approach, yet slower than `JSON.parse(json_string)`. + # + # Performance depends on architecture and compiler used. Please measure: + # + # require 'iodine/benchmark' + # Iodine::Benchmark.json + module JSON + # Accepts a JSON String and returns a Ruby object. + def self.parse(json_string); end + # Accepts a Ruby object and returns a JSON String. + def self.stringify(ruby_object); end + end + + ####################### + + # {Iodine::Mustache} is a lighter implementation of the mustache template rendering gem, with a focus on a few minor security details: + # + # 1. HTML escaping is more aggressive, increasing XSS protection. Read why at: [wonko.com/post/html-escaping](https://wonko.com/post/html-escaping). + # + # 2. Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `user`, `name.first` and than as each `user`, `name` , `first`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts. + # + # 3. Less logic: i.e., lambdas / procs do not automatically invoke a re-rendering... I'd remove them completely as unsafe, but for now there's that. + # + # 4. Improved Protection against Endless Recursion: i.e., Partial templates reference themselves when recursively nested (instead of being recursively re-loaded); and Partial's context is limited to their starting point's context (cannot access parent context). + # + # It wasn't designed specifically for speed or performance... but it ended up being significantly faster. + # + # ## Usage + # + # This approach to Mustache templates may require more forethought when designing either the template or the context's data format, however it should force implementations to be more secure and performance aware. + # + # Approach: + # + # require 'iodine' + # # One-off rendering of (possibly dynamic) template: + # result = Iodine::Mustache.render(template: "{{foo}}", ctx: {foo: "bar"}) # => "bar" + # # caching of parsed template data for multiple render operations: + # view = Iodine::Mustache.new(file: "./views/foo.mustache", template: "{{foo}}") + # results = Array.new(100) {|i| view.render(foo: "bar#{i}") } # => ["bar0", "bar1", ...] + # + # ## Performance + # + # Performance may differ according to architecture and compiler used. Please measure: + # + # require 'iodine/benchmark' + # Iodine::Benchmark.mustache + # + # **Note**: the benchmark requires the `mustache` gem, which could be installed using: + # + # gem install mustache + # + # See also {file:./mustache.md}. + class Mustache + # Loads a template file and compiles it into a flattened instruction tree. + # + # Iodine::Mustache.new(file = nil, data = nil, on_yaml = nil, &block) + # + # @param file [String] a file name for the mustache template. + # + # @param template [String] the content of the mustache template. + # + # @param on_yaml [Proc] (**optional**) accepts a YAML front-matter String. + # + # @param &block to be used as an implicit `on_yaml` (if missing). + # + # @return [Iodine::Mustache] returns an Iodine::Mustache object with the provided template ready for rendering. + # + # **Note**: Either the file or template argument (or both) must be provided. + def initialize(file = nil, data = nil, on_yaml = nil, &block); end + + # Renders the template given at initialization with the provided context. + # + # m.render(ctx) + # + # @param ctx the top level context for the template data. + # + # @return [String] returns a String containing the rendered template. + def render(ctx = nil); end + + # Loads a template file and renders it into a String. + # + # Iodine::Mustache.render(file = nil, data = nil, ctx = nil, on_yaml = nil) + # + # @param file [String] a file name for the mustache template. + # + # @param template [String] the content of the mustache template. + # + # @param ctx [String] the top level context for the template data. + # + # @param on_yaml [Proc] (**optional**) accepts a YAML front-matter String. + # + # @param &block to be used as an implicit \p on_yaml (if missing). + # + # @return [String] returns a String containing the rendered template. + # + # **Note**: Either the file or template argument (or both) must be provided. + def self.render(file = nil, data = nil, ctx = nil, on_yaml = nil, &block); end + end + + ####################### + + # These are some utility, unescaping and decoding helpers provided by Iodine. + # + # These **should** be faster then their common Ruby / Rack alternative, but this may depend on your own machine and Ruby version. + # + # require 'iodine/benchmark' + # Iodine::Benchmark.utils + # + # After testing the performance, consider calling {Iodine::Utils.monkey_patch} if using Rack. + module Utils + + # Encodes a String using percent encoding (i.e., URI encoding). + def self.escape_path() ; end + # Encodes a String in place using percent encoding (i.e., URI encoding). + def self.escape_path!() ; end + # Decodes percent encoding, including the `%uxxxx` JavaScript extension. + def self.unescape_path() ; end + # Decodes percent encoding in place, including the `%uxxxx` JavaScript. + def self.unescape_path!() ; end + # Encodes a String using percent encoding (i.e., URI encoding). + def self.escape() ; end + # Encodes a String using percent encoding (i.e., URI encoding). + def self.escape!() ; end + # Decodes percent encoding, including the `%uxxxx` JavaScript extension and converting `+` to spaces. + def self.unescape() ; end + # Decodes percent encoding in place, including the `%uxxxx` JavaScript extension and converting `+` to spaces. + def self.unescape!() ; end + # Escapes String using HTML escape encoding. + # + # Note: this function may significantly increase the number of escaped characters when compared to the native implementation. + def self.escape_html() ; end + # Escapes String in place using HTML escape encoding. + # + # Note: this function may significantly increase the number of escaped characters when compared to the native implementation. + def self.escape_html!() ; end + # Decodes an HTML escaped String. + def self.unescape_html() ; end + # Decodes an HTML escaped String in place. + def self.unescape_html!() ; end + # Takes a Time object and returns a String conforming to RFC 2109. + def self.rfc2109(time) ; end + # Takes a Time object and returns a String conforming to RFC 2822. + def self.rfc2822(time) ; end + # Takes a Time object and returns a String conforming to RFC 7231. + def self.time2str(time) ; end + # Securely compares two String objects to see if they are equal. + # + # Designed to be secure against timing attacks when both String objects are of the same length. + # + # Performance depends on specific architecture and compiler used. Always measure: + # + # require 'iodine' + # require 'rack' + # require 'benchmark' + # def prove_secure_compare(name, mthd, length = 4096) + # GC.disable + # a = 0; + # b = 0; + # str1 = Array.new(length) { 'a' } .join; str2 = Array.new(length) { 'a' } .join; + # counter = 1.0 / (Benchmark.measure {1024.times {mthd.call(str1, str2)}}).total + # counter = counter / 2 + # counter = 1.0 if(counter < 1.0) + # counter = counter.to_i + # bm = Benchmark.measure do + # 2048.times do + # tmp = Benchmark.measure {counter.times {mthd.call(str1, str2)}} + # str1[0] = 'b' + # tmp2 = Benchmark.measure {counter.times {mthd.call(str1, str2)}} + # str1[0] = 'a' + # tmp = tmp2.total - tmp.total + # a += 1 if tmp >= 0 + # b += 1 if tmp <= 0 + # end + # end + # GC.enable + # counter = (a.to_f/b.to_f - b.to_f/a.to_f).abs + # msg = "" + # msg = "DIFFERENCE TOO LARGE!" if (a == 0 || b == 0 || counter > 0.3) + # puts "#{name} timing ratio #{a}:#{b} #{msg} (#{'%.3f' % counter})\n#{bm.to_s}\n" + # end + # def print_proof + # methods = [ ["String == (short string)", (Proc.new {|a,b| a == b } )], + # ["Iodine::Utils.secure_compare (short string)", Iodine::Utils.method(:secure_compare)], + # ["Rack::Utils.secure_compare (short string)", Rack::Utils.method(:secure_compare)] ] + # [64, 2048].each {|len| methods.each {|c| prove_secure_compare(c[0], c[1], len) } } + # end + # print_proof + # + def self.secure_compare() ; end + # Adds the `Iodine::Utils` methods to the modules passed as arguments. + # + # If no modules were passed to the `monkey_patch` method, `Rack::Utils` will be + # monkey patched. + def self.monkey_patch() ; end + + end + + + ####################### + + # This is the namespace for all pub/sub related classes and constants. + module PubSub + # Iodine::PubSub::Message class instances are passed to subscription callbacks. + # + # This allows subscription callbacks to get information about the pub/sub event. + class Message + # Returns the event's `id` property - a (mostly) random numerical value. + def id(); end + # Returns the event's `channel` property - the event's target channel. + def channel(); end + alias :event, :channel + # Returns the event's `filter` property - the event's numerical channel filter. + def filter(); end + # Returns the event's `message` property - the event's payload (same as {to_s}). + def message(); end + alias :msg, :message + alias :data, :message + # Returns the event's `published` property - the event's published timestamp in milliseconds since epoch. + def published(); end + + # Returns the event's `message` property - the event's payload (same as {message}). + def to_s(); end + + # Sets the event's `id` property. + def id=(); end + # Sets the event's `channel` property. + def channel=(); end + # Sets the event's `filter` property. + def filter=(); end + # Sets the event's `message` property. + def message=(); end + # Sets the event's `published` property. + def published=(); end + end + + # Iodine::PubSub::Engine class instances are used as the base class for all pub/sub engines. + # + # The Engine class makes it easy to use leverage Iodine's pub/sub system using external services. + # + # Engine child classes SHOULD override the #subscribe, #unsubscribe and #publish in order to perform this actions using the backend service (i.e. using Redis). + # + # Once an Engine instance receives a message from its backend service, it should forward the message to the Iodine distribution layer one of the other engines. i.e.: + # + # Iodine.publish channel: "subscribed_channel", message: "the_message", engine: Iodine::PubSub::Engine::CLUSTER + # + # Iodine comes with two built-in engines: + # + # * {Iodine::PubSub::Engine::CLUSTER} will distribute messages to all subscribers in the process cluster. + # + # * {Iodine::PubSub::Engine::PROCESS} will distribute messages to all subscribers sharing the same process. + class Engine + # When implementing your own initialization class, remember to call `super` to attach the engine to Iodine. + def initialize; end + # Please OVERRIDE(!) – called by iodine when a message was published to the engine (using `Iodine.publish(engine: MY_ENGINE, ...)). + def pubslish(msg); end + # Please OVERRIDE(!) – called by iodine when a named channel was subscribed to. + def subscribe(channel); end + # Please OVERRIDE(!) – called by iodine when a pattern channel was subscribed to. + def psubscribe(pattern); end + # Please OVERRIDE(!) – called by iodine when a named channel was unsubscribed to. + def unsubscribe(channel); end + # Please OVERRIDE(!) – called by iodine when a pattern channel was unsubscribed to. + def punsubscribe(pattern); end + # Please OVERRIDE(!) – called by iodine when a the engine is detached from iodine. + # + # This happens when the engine object went out of scope and it's collected by the GC (Garbage Collector). + def on_cleanup; end + # This engine publishes the message to all subscribers in Iodine's root process. + ROOT + # This engine publishes the message to all subscribers in the specific root or worker process where `Iodine.publish` was called. + PROCESS + # This engine publishes the message to all subscribers in Iodine's worker processes (except current process). + SIBLINGS + # This engine publishes the message to all subscribers in Iodine's root and worker processes. + LOCAL + # This engine publishes the message to all subscribers in Iodine's root and worker processes, as well as any detected Iodine instance on the local network. + CLUSTER + end + + end + + ####################### + + # {Iodine::Connection} is the connection instance class used for all connection callbacks to control the state of the connection. + # + # The {Iodine::Connection} class offers methods that control the different aspects of a Connection's state: + # + # - HTTP related methods include the {#status}, {#method}, {#path}, {#query}, {#headers}, {#cookie}, {#each_cookie}, {#read}, {#env} (Rack), and more methods. + # + # - WbeSocket / TCP related methods include the {#open?}, {#write}, {#pending?} and {#close} methods. + # + # - Pub/Sub related methods include the {#subscribe}, {#unsubscribe}, {#publish} and {#pubsub?} methods. + # + # - Connection specific data can be stored / accessed using {#[]}, {#[]=}, {#each}, {#store_count}, {#store_capa}, {#store_reserve} methods. + # + class Connection + # Used for Rack Hijack (rack.hijack). + class RackIO < ::IO + end + + # Unless `env` is manually set, Iodine will attempt to create a default `env` object that will follow the Rack Server specification for HTTP connections. + # + # Otherwise, the `env` attribute is usually a Hash object set by the client and may be used as an alternative to the Connection data storage (see {#[]} and {#[]=}). + attr_accessor :env + # The `handler` attribute (see {Iodine::Connection::Handler}) - this may be the Rack application or a [NeoRack](https://github.com/boazsegev/neorack) application. + attr_accessor :handler + # For HTTP connections, the `status` value is the HTTP status value that will be sent in the response. + attr_accessor :status + # For HTTP connections, the `method` value is the HTTP method value received by the client. + attr_accessor :method + # For HTTP connections, the `path` value is the HTTP URL's path String received by the client. + attr_accessor :path + # For HTTP connections, the `query` value is the HTTP URL's query String (if received by the client). + attr_accessor :query + # For HTTP connections, the `version` String value stated by the HTTP client. + attr_accessor :version + # For HTTP connections, sets all incoming request headers in the Connection instance storage ({#[]}) and returns `self`. + # + # Used to overcome lazy parsing of incoming headers. i.e.: + # + # # prints all incoming headers, assuming NeoRack guidelines were followed: + # client.headers.each do |key, value| + # if(value.is_a? Array) + # value.each {|v| puts "#{key}: #{v}" } + # else + # puts "#{key}: #{value}" + # end + # end + # + attr_accessor :headers + # (HTTP Only) Returns the length of the `body` payload. + attr_reader :length + + # Returns a the value of a key previously set. + # + # If the Connection is an HTTP connection and `key` is a Header String, the value of the header (if received) will be returned. + # + # If no value was set for the key (or no Header received), `nil` will be returned. + def [](key); end + # Sets a key value pair. + def []=(key, value); end + # Iterates over the stored key value pairs. + # + # If the Connection is an HTTP connection, `each` will also iterate over any header previously accessed. + def each(&block); end + # Returns the number of key value pairs stored. + def store_count; end + # Returns the theoretical capacity of the key value pair storage map. + def store_capa; end + # Reserved a theoretical storage capacity in the key value pair storage map. + # + # Note that the capacity is theoretical only and some extra memory is required to allow for possible (partial) hash collisions. + def store_reserve(number_of_items); end + + # Returns `true` if the connection appears to be open (no known issues). + # + # (HTTP Only) Returns `true` only if upgraded and open (WebSocket is open). + def open?(); end + # @deprecated Please check the {Server.extensions} Hash instead. + # + # Always returns `true`, since Iodine connections support the pub/sub extension. + # + # This method should be considered deprecated and will be removed in future versions. + def pubsub?(); end + # Returns the number of bytes that need to be sent before the next `on_drained` callback is called. + def pending(); end + # Schedules the connection to be closed. + def close(); end + # Sets a response header and returns `true`. + # + # If headers have already been sent, or if either `write` or `finish` has been previously called, returns `false` (doing nothing). + # + # Header `name` should be a lowercase String. This will be enforced by Iodine, converting header names to lower case and converting Symbols to Strings. + # + # `value` must be either a `String`, an `Array` of Strings, or `nil`. Iodine also accepts Symbols and certain Numbers (small numbers). + # + # **If `value` is `nil`**: + # - Performs a non-guaranteed best attempt at removing any existing headers named `name` and returns `false`. + # + # **If `value` is a `String`**: + # - A response header with the given `name` is added to the response and set to `value`. If `name` already exists, multiple headers will be sent. + # - Iodine is backwards compatible with Rack 2, treating newline characters as an array of `String`s. Eventually this will be removed. + # + # **If `value` is an `Array` of `String`s**: + # - The method behaves as if called multiple times with the same `name`, once for each element of `value` (except `nil` values are ignored). + def write_headers(name, values); end + # Writes data to the connection asynchronously. + # + # If `data` **should* be a String. However, if `data` is not a String, Iodine will attempt to convert it to a JSON String, allowing Hashes, Arrays and other native Ruby objects to be sent over the wire. Note that `data` **must** be a native Ruby Object, or it could not be converted to a JSON String. + # + # For WebSocket connections: if `data` is a UTF-8 encoded String, message will be sent as `text`, otherwise message will be sent as `binary`. + # + # For SSE connections: this is similar to calling `write_sse(nil, nil, data)`. + # + # Returns `false` on error / fail and `true` if the event was scheduled to be sent to the client. + def write(data); end + # Writes data to EventSource client connections asynchronously. + # + # The `id` and `event` elements **must** be UTF-8 encoded Strings. + # + # If `data` **should* be a String. However, if `data` is not a String, Iodine will attempt to convert it to a JSON String, allowing Hashes, Arrays and other native Ruby objects to be sent over the wire. Note that `data` **must** be a native Ruby Object, or it could not be converted to a JSON String. + # + # An empty `data` String or a missing `data` argument will cause `write_sse` to return false. + # + # Returns `false` on error / fail and `true` if the event was scheduled to be sent to the client. + def write_sse(id, event, data); end + # Closes the connection after writing `data`, or (HTTP Only) finishes the HTTP response. + # + # Note that for HTTP connections the `"content-length"` header is automatically written if no previous calls to `write` were called. + def finish(data = nil); end + + + # (HTTP Only) Returns `false` if headers can still be sent. Otherwise returns `true`. + def headers_sent?; end + # Returns `true` only if the Connection object is still valid (data can be sent). + def valid?; end + # Returns true only of the connection is a WebSocket connection. + def websocket?; end + # Returns true only of the connection is an SSE connection. + def sse?; end + + + # (HTTP Only) Returns an ASCII-8 String with the next line in the Body (same as {::IO#gets} where only `limit` is allowed). + def gets(limit = nil); end + # (HTTP Only) Returns an ASCII-8 String with (part of) the Body (see {::IO#read}). + def read(maxlen = nil, out_string = nil); end + # (HTTP Only) Sets the body's new read position. Negative values start at the end of the body. + def seek(pos = 0); end + + + # (HTTP Only) Returns an ASCII-8 String with the value of the named cookie. + def cookie(name); end + # (HTTP Only) Sets the value of the named cookie and returns `true` upon success. + # + # If `headers_sent?` returns `true`, calling this method may raise an exceptions. + # + # If `value` is `nil`, the cookie will be deleted. If `:max_age` is 0 (default), cookie will be session cookie and will be deleted by the browser at its discretion. + # + # This should behave similar to calling `write_header`, except that the cookie can be read using the {#cookie} method. + # + # Note that this method accepts named arguments. i.e.: + # + # set_cookie(name: "MyCookie", value: "My non-secret data", domain: "localhost", max_age: 1_728_000) + # + # For more details, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + def set_cookie(name, value = nil, max_age = 0, domain = nil, path = nil, same_site = nil, secure = false, http_only = false, partitioned = false); end + # Calls `block` for each name-value cookie pair. + def each_cookie(&block); end + + # Publishes a message to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the channel name to publish to (String). + # - `filter`: the filter to publish to (Number < 32,767). + # - `message`: the actual message to publish. + # - `engine`: the pub/sub engine to use (if not the default one). + # + # i.e.: + # + # Iodine::Connection.publish("channel_name", 0, "payload") + # Iodine::Connection.publish(channel: "name", message: "payload") + def self.publish(channel = nil, filter = 0, message = nil, engine = nil); end + # Publishes a message to a combination of a channel (String) and filter (number). + # + # **Note**: events published by a specific client are sent to "**everyone else**" and ignored by the client itself. + # + # **Note**: if `engine` was specified and it isn't an Iodine engine, message **may** be delivered to the client calling `publish` as well. + # + # To publish events to everyone, including the client itself, use the global {Iodine.publish} method or the {.publish} class method. + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the channel name to publish to (String). + # - `filter`: the filter to publish to (Number < 32,767). + # - `message`: the actual message to publish. + # - `engine`: the pub/sub engine to use (if not the default one). + # + # i.e.: + # + # client.publish("channel_name", 0, "payload") + # client.publish(channel: "name", message: "payload") + # + def publish(channel = nil, filter = 0, message = nil, engine = nil); end + + # Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments and an **optional** block: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # - `callback`: an **optional** object that answers to call and accepts a single argument (the message structure). + # + # Either a proc or a block MUST be provided for global subscriptions. + # + # i.e.: + # + # client.subscribe("name") + # client.subscribe(channel: "name") + # # or with filter + # client.subscribe("name", 1) + # client.subscribe(channel: "name", filter: 1) + # # or only a filter + # client.subscribe(nil, 1) + # client.subscribe(filter: 1) + # # with / without a proc + # client.subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == Qnil;} + # client.subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; } + def subscribe(channel = nil, filter = 0, &callback); end + # Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments and an **optional** block: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # - `callback`: an **optional** object that answers to call and accepts a single argument (the message structure). + # + # Either a proc or a block MUST be provided for global subscriptions. + # + # i.e.: + # + # Iodine::Connection.subscribe("name") + # Iodine::Connection.subscribe(channel: "name") + # # or with filter + # Iodine::Connection.subscribe("name", 1) + # Iodine::Connection.subscribe(channel: "name", filter: 1) + # # or only a filter + # Iodine::Connection.subscribe(nil, 1) + # Iodine::Connection.subscribe(filter: 1) + # # with / without a proc + # Iodine::Connection.subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == Qnil;} + # Iodine::Connection.subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; } + def self.subscribe(channel = nil, filter = 0, &callback); end + + # Un-Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # + # i.e.: + # + # client.unsubscribe("name") + # client.unsubscribe(channel: "name") + # # or with filter + # client.unsubscribe("name", 1) + # client.unsubscribe(channel: "name", filter: 1) + # # or only a filter + # client.unsubscribe(nil, 1) + # client.unsubscribe(filter: 1) + def unsubscribe(channel = nil, filter = 0); end + # Un-Subscribes to a combination of a channel (String) and filter (number). + # + # Accepts the following (possibly named) arguments: + # + # - `channel`: the subscription's channel name (String). + # - `filter`: the subscription's filter (Number < 32,767). + # + # i.e.: + # + # Iodine::Connection.unsubscribe("name") + # Iodine::Connection.unsubscribe(channel: "name") + # # or with filter + # Iodine::Connection.unsubscribe("name", 1) + # Iodine::Connection.unsubscribe(channel: "name", filter: 1) + # # or only a filter + # Iodine::Connection.unsubscribe(nil, 1) + # Iodine::Connection.unsubscribe(filter: 1) + def self.unsubscribe(channel = nil, filter = 0); end + + # Hijacks the connection from the Server and returns an IO object. + # + # This method MAY be used to implement a full hijack when providing compatibility with the Rack specification. + def rack_hijack; end + +####################### + + # This is an example handler for documentation purposes. + # + # This module is designed to help you author your own Handler for {Iodine.listen}, {Iodine::Connection.new} and {Iodine.connect}. + # + # A handler implementation is expected to implement at least one of the callbacks defined for this example Handler. Any missing callbacks will be defined by Iodine using smart defaults (where possible). + # + # **Note**: This module isn't implemented by Iodine, but is for documentation and guidance only. + module Handler + # Used for [NeoRack](https://github.com/boazsegev/neorack) application authoring, when receiving HTTP requests. + # + # [NeoRack](https://github.com/boazsegev/neorack) allows both Async and CGI style Web Applications, making it easier to stream data and do more with a single thread. + def self.on_http(event); end + # Used for [NeoRack](https://github.com/boazsegev/neorack) application authoring, when an HTTP request handling had finished. + def self.on_finish(event); end + # Used for [Rack](https://github.com/rack/rack) application authoring, when receiving HTTP requests. + # + # [Rack](https://github.com/rack/rack) is the classical server interface and it is supported by Iodine with minimal extensions. + def self.call(env); end + # When present, a missing `on_authenticate_sse` or missing `on_authenticate_websocket` will route to this callback, allowing authentication logic to be merged in one method. + # + # **Must** return `true` for the connection to be allowed. **Any other return value will cause the connection to be refused** (and disconnected). + # + # By default, if `on_message` or `on_open` are defined, than `on_authenticate` returns `true`. Otherwise, `on_authenticate` returns `false`. + def self.on_authenticate(connection); end + # Called when an Event Source (SSE) request is detected, to authenticate and possibly set the identity of the user. + # + # By default, if `on_open` is defined, than `on_authenticate_sse` returns `true`. Otherwise, `on_authenticate` returns `false`. + def self.on_authenticate_sse(connection); end + # Called when a WebSocket Upgrade request is detected, to authenticate and possibly set the identity of the user. + # + # By default, if `on_message` or `on_open` are defined, than `on_authenticate_websocket` returns `true`. Otherwise, `on_authenticate` returns `false`. + def self.on_authenticate_websocket(connection); end + # Called when a long-running connection opens. This will be called for Event Source, WebSocket, TCP/IP and Raw Unix Socket connections. + def self.on_open(connection); end + # Called when incoming data arrives and its behavior depends on the underlying protocol. + # + # For TCP/IP and Raw Unix connections, the data is a Binary encoded String with some (or all) of the data available in the incoming socket buffer. + # + # For WebSockets, the `data` element will contain a String with the WebSocket Message. + # + # If the WebSocket message is in text, the String encoding will be UTF-8. + # + # If the WebSocket message is binary, the String encoding will be Binary (ASCII). + def self.on_message(connection, data); end + # Called when all calls to {Iodine::Connection#write} have been handled and the outgoing buffer is now empty. + def self.on_drained(connection); end + # Called when timeout has been reached for a WebSocket / TCP/IP / Raw Unix Socket connection. + def self.on_timeout(connection); end + # Called when the worker that manages this connection (or the root process, in non-cluster mode) starts shutting down. + def self.on_shutdown(connection); end + # Called when the connection was closed (WebSocket / TCP/IP / Raw Unix Socket). + def self.on_close(connection); end + # Called when an Event Source (SSE) event has been received (when acting as an Event Source client). + def self.on_eventsource(connection, message); end + # Called an Event Source (SSE) client send a re-connection request with the ID of the last message received. + def self.on_eventsource_reconnect(connection, last_message_id); end + end + + end + + ####################### + + # Used to setup a TLS contexts for connections (incoming / outgoing). + class TLS + # Assigns the TLS context a public certificate, allowing remote parties to validate the connection's identity. + # + # A self signed certificate is automatically created if the `name` argument is specified and either (or both) of the `cert` (public certificate) or `key` (private key) arguments are missing. + # + # Some implementations allow servers to have more than a single certificate, which will be selected using the SNI extension. I believe the existing OpenSSL implementation supports this option (untested). + # + # Iodine::TLS#add_cert(name = nil, + # cert = nil, + # key = nil, + # password = nil) + # + # Certificates and keys should be String objects leading to a PEM file. + # + # This method also accepts named arguments. i.e.: + # + # tls = Iodine::TLS.new + # tls.add_cert name: "example.com" + # tls.add_cert cert: "my_cert.pem", key: "my_key.pem" + # tls.add_cert cert: "my_cert.pem", key: "my_key.pem", password: ENV['TLS_PASS'] + # + # Since TLS setup is crucial for security, an initialization error will result in Iodine crashing with an error message. This is expected behavior. + def add_cert(name = nil, cert = nil, key = nil, password = nil); end + end + + + ####################### + + # Used internally by Iodine. + class Base + + # Prints the number of object withheld from the GC (for debugging). + def self.print_debug(); end + # Sets Iodine's cache limit for frozen strings, limited to 65,535 items. + def self.cache_limit=(new_limit); end + # Returns Iodine's cache limit for frozen strings. + def self.cache_limit(); end + + + # Parses and manages CLI input. + module CLI + # Parses CLI input. + # + # If `required` is true and CLI detects an error, program will exit with an appropriate exit message and a list of valid CLI options. + def parse(required = false); end + # Returns a CLI option's value. + def [](key); end + # Sets a CLI option's value. + def []=(key, value); end + end + + # A small Hash implementation used internally to bridge C data structures with Ruby. + # + # This implementation would normally run slower when called and executed exclusively from within the Ruby layer, + # as it is optimized for C callbacks and is used to bridge Ruby and C data structures. + # + # To benchmark the C performance (excluding the Ruby overhead), call {MiniMap.cbench}. + # + # It is also possible to benchmark the Ruby performance vs. the Ruby Hash using the `Iodine::Benchmark` module. + module MiniMap + # Returns the value associated with @key. + def [](key); end + # Sets the @value associated with @key and returns it. + def []=(key, value); end + # Runs @block for each key-value pair. Returns the number of times the block was executed. + # + # m = Iodine::Base::MiniMap.new + # m[:go] = "home" + # m.each {|key, value| puts "#{key} => #{value}"} + def each(&block); end + # Returns the number of key-value pairs stored in the Hash. + def count; end + # Returns the maximum number of theoretical items the Hash can contain. + # + # Note that capacity increase will likely happen sooner and this number deals more with pre-allocated memory for the objects than with actual capacity. + def capa; end + # Reserves room in the Hash Map for at least the @capacity requested. + # + # See note in {capa}. + def reserve(capacity); end + # Benchmarks MiniMap v.s Hash performance when called from within the C layer. + # + # Numbers printed out are in time units. Lower is better. + def self.cbench(number_of_items); end + end + end + + # class Error < StandardError; end +end + +end # defined?(Iodine) diff --git a/lib/iodine/json.rb b/lib/iodine/json.rb deleted file mode 100644 index a544483e..00000000 --- a/lib/iodine/json.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Iodine - # Iodine includes a lenient JSON parser that attempts to ignore JSON errors when possible and adds some extensions such as Hex numerical representations and comments. - # - # Depending on content (specifically, the effects of float number parsing), the Iodine JSON parser speed varies. - # - # On my system, Iodine is about 20%-30% faster than Ruby MRI's parser (Ruby version 2.5.1 vs. Iodine version 0.7.4). When using symbols the speed increase is even higher (50%-90% faster). - # - # It's easy to monkey-patch the system's `JSON.parse` method (not the `JSON.parse!` method) by using `Iodine.patch_json`. - # - # You can benchmark the Iodine JSON performance and decide if you wish to monkey-patch the Ruby implementation. - # - # JSON_FILENAME="foo.json" - # - # require 'json' - # require 'iodine' - # TIMES = 100 - # STR = IO.binread(JSON_FILENAME); nil - # - # JSON.parse(STR) == Iodine::JSON.parse(STR) # => true - # JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => undefined, maybe true maybe false - # - # # warm-up - # TIMES.times { JSON.parse STR } - # TIMES.times { Iodine::JSON.parse STR } - # - # puts ""; Benchmark.bm do |b| - # sys = b.report("system") { TIMES.times { JSON.parse STR } } - # sys_sym = b.report("system-sym") { TIMES.times { JSON.parse STR, symbolize_names: true } } - # iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } } - # iodine_sym = b.report("iodine-sym") { TIMES.times { Iodine::JSON.parse STR, symbolize_names: true } } - # - # puts "----------------------------" - # puts "Iodine::JSON speed as percent of Ruby's native JSON:" - # puts "normal: #{sys/iodine}" - # puts "symolized: #{sys_sym/iodine_sym}" - # end; nil - # - # Note that the bang(!) method should NOT be used for monkey-patching the default JSON parser, since some important features are unsupported by the Iodine parser. - # - module JSON - end -end diff --git a/lib/iodine/mustache.rb b/lib/iodine/mustache.rb deleted file mode 100644 index 1c3b926b..00000000 --- a/lib/iodine/mustache.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Iodine - # Iodine includes a safe and fast Mustache templating engine. - # - # The engine is simpler and safer to use (and often faster) than the official and feature richer Ruby engine. - # - # Note: {Iodine::Mustache} behaves differently than the official Ruby templating engine in a number of ways: - # - # * When a partial template can't be found, a `LoadError` exception is raised (the official implementation outputs an empty String). - # - # * HTML escaping is more agressive, increasing XSS protection. Read why at: https://wonko.com/post/html-escaping . - # - # * Partial template padding in Iodine adds padding to dynamic text as well as static text, unlike the official Ruby mustache engine. i.e., if an argument contains a new line marker, the new line will be padded to match the partial template padding. - # - # * Lambda support is significantly different. For example, the text returned from a lambda isn't parsed (no lambda interpolation). - # - # * Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `"user","name.first"` and than as each `"use","name","first"`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts. - # - # * Dot notation supports method names (even chained method names) as long as they don't have or require arguments. For example, `user.class.to_s` will behave differently on Iodine (returns call name as `String`) than on the official mustache engine (fails / returns empty string). - # - # Iodine Mustache's engine was designed to play best with basic data structures, such as results from the {Iodine::JSON} parser and doesn't require any special classes or types. - # - # Hash data is tested for Symbol keys before being tested for String keys and methods. This means that `:key` has precedence over `"key"`. - # - # Note: Although using methods as "keys" (or argument names) is supported, no Ruby code is evaluated. This means that only trusted (pre-existing) code will execute. - # - # Iodine's {Iodine::Mustache} engine performes about 5-7 times faster(!) than the official Ruby mustache engine. Tests performed with Ruby 2.6.0, comparing iodine 0.7.33 against mustache 1.1.0 using a 2.9 GHz Intel Core i9 CPU. - # - # You can benchmark the Iodine Mustache performance and decide if you wish to switch from the official Ruby implementation. - # - # require 'benchmark/ips' - # require 'mustache' - # require 'iodine' - # - # # Benchmark code was copied, in part, from: - # # https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb - # # The test is, sadly, biased and doesn't test for missing elements, proc/method resolution or template partials. - # def benchmark_mustache - # template = """ - # {{#products}} - #
      - #
      - #
      - # - #
      - # - #
      - #
      - # {{/products}} - # """ - # - # IO.write "test_template.mustache", template - # filename = "test_template.mustache" - # - # data_1000 = { - # products: [] - # } - # data_1000_escaped = { - # products: [] - # } - # - # 1000.times do - # data_1000[:products] << { - # :external_index=>"product", - # :url=>"/products/7", - # :image=>"products/product.jpg" - # } - # data_1000_escaped[:products] << { - # :external_index=>"This should've been \"properly\" escaped.", - # :url=>"/products/7", - # :image=>"products/product.jpg" - # } - # end - # - # view = Mustache.new - # view.template = template - # view.render # Call render once so the template will be compiled - # iodine_view = Iodine::Mustache.new(template: template) - # - # Benchmark.ips do |x| - # x.report("Ruby Mustache render list of 1000") do |times| - # view.render(data_1000) - # end - # x.report("Iodine::Mustache render list of 1000") do |times| - # iodine_view.render(data_1000) - # end - # - # x.report("Ruby Mustache render list of 1000 with escaped data") do |times| - # view.render(data_1000_escaped) - # end - # x.report("Iodine::Mustache render list of 1000 with escaped data") do |times| - # iodine_view.render(data_1000_escaped) - # end - # - # x.report("Ruby Mustache - no caching - render list of 1000") do |times| - # tmp = Mustache.new - # tmp.template = template - # tmp.render(data_1000) - # end - # x.report("Iodine::Mustache - no caching - render list of 1000") do |times| - # Iodine::Mustache.render(nil, data_1000, template) - # end - # end - # nil - # end - # - # benchmark_mustache - class Mustache - end -end diff --git a/lib/iodine/pubsub.rb b/lib/iodine/pubsub.rb deleted file mode 100644 index 93a2bc49..00000000 --- a/lib/iodine/pubsub.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Iodine - # Iodine is equiped with an internal pub/sub service that allows improved resource management from a deployment perspective. - # - # @note From {https://en.wikipedia.org/wiki/Publish–subscribe_pattern Wikipedia}: publish–subscribe is a messaging pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers, but instead characterize published messages into classes without knowledge of which subscribers, if any, there may be. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. - # - # The common paradigm, which is implemented by pub/sub services like Redis, - # is for a "client" to "subscribe" to one or more "channels". Messages are streamed - # to these "channels" by different "publishers" (the application / other clients) and are - # broadcasted to the "clients" through their "subscription". - # - # Iodine's approach it to offload pub/sub resource costs from the pub/sub service - # (which is usually expensive to scale) onto the application layer. - # - # For example, the default (`nil`) pub/sub {Iodine::PubSub::Engine} implements - # an internal pub/sub service that manages subscriptions (clients and channels) throughout an Iodine process cluster without any need to connect to an external pub/sub service. - # - # If Iodine was runninng with 8 processes and 16 threads per process, - # a publishing in process A will be delivered to subscribers in process B. - # - # In addition, by inheriting the {Iodine::PubSub::Engine} class, it's easy to create pub/sub engines that connect to this - # underlying pub/sub service. This means that Iodine will call the engine's `subscribe` method only once per - # channel and once messages arrive, Iodine will distribute the messages to all the subscribed clients. - module PubSub - # The {Iodine::PubSub::Engine} class makes it easy to use leverage Iodine's pub/sub system using external services. - # - # Iodine comes with two built-in engines: - # - # * `Iodine::PubSub::Engine::CLUSTER` will distribute messages to all subscribers in the process cluster. - # * `Iodine::PubSub::Engine::PROCESS` will distribute messages to all subscribers sharing the same process. - # - # It's recommended that {Iodine::PubSub::Engine} instances be initialized only after Iodine - # started running (or the `fork`ing of the engine's connection will introduce communication issues). - # - # For this reason, the best approcah to initialization would be: - # - # class MyEngineClass < Iodine::PubSub::Engine - # # ... - # end - # - # Iodine.run do - # MyEngine = MyEngineClass.new - # end - # - # {Iodine::PubSub::Engine} child classes MUST override the {Iodine::PubSub::Engine#subscribe}, {Iodine::PubSub::Engine#unsubscribe} and {Iodine::PubSub::Engine#publish} - # in order to perform this actions using the backend service (i.e. using Redis). - # - # Once an {Iodine::PubSub::Engine} instance receives a message from the backend service, - # it should forward the message to the Iodine distribution layer using the {Iodine.publish} method, setting the 3rd argument to `false`. - # - # Iodine will than distribute the message to all registered clients in that specific process (if the engine is cluster wide, set the 3rd argument to {Iodine::PubSub::CLUSTER}. - # - class Engine - end - end -end diff --git a/lib/iodine/rack_utils.rb b/lib/iodine/rack_utils.rb deleted file mode 100644 index 1de372df..00000000 --- a/lib/iodine/rack_utils.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Iodine - # Iodine's {Iodine::Rack} module provides a Rack complient interface (connecting Iodine to Rack) for an HTTP and Websocket Server. - module Rack - # The methods in this module are designed to be Rack compatible and to provide higher performance than the original Rack methods. - # - # Iodine does NOT monkey patch Rack automatically. However, it's possible and recommended to moneky patch Rack::Utils to use the methods in this module. - # - # Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine): - # - # require 'iodine' - # require 'rack' - # # a String in need of decoding - # s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8' - # Benchmark.bm do |bm| - # # Pre-Patch - # bm.report(" Rack.unescape") {1_000_000.times { Rack::Utils.unescape s } } - # bm.report(" Rack.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } } - # bm.report(" Rack.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } } - # # Perform Patch - # Iodine.patch_rack - # puts " --- Monkey Patching Rack ---" - # # Post Patch - # bm.report("Patched.unescape") {1_000_000.times { Rack::Utils.unescape s } } - # bm.report(" Patched.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } } - # bm.report(" Patched.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } } - # end && nil - # - # Results: - # - # user system total real - # Rack.unescape 8.706881 0.019995 8.726876 ( 8.740530) - # Rack.rfc2822 3.270305 0.007519 3.277824 ( 3.279416) - # Rack.rfc2109 3.152188 0.003852 3.156040 ( 3.157975) - # --- Monkey Patching Rack --- - # Patched.unescape 0.327231 0.003125 0.330356 ( 0.337090) - # Patched.rfc2822 0.691304 0.003330 0.694634 ( 0.701172) - # Patched.rfc2109 0.685029 0.001956 0.686985 ( 0.687607) - # - # Iodine uses the same code internally for HTTP timestamping (adding missing `Date` headers) and logging. - module Utils - end - end -end diff --git a/lib/iodine/tls.rb b/lib/iodine/tls.rb deleted file mode 100644 index 4dfa6ec8..00000000 --- a/lib/iodine/tls.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Iodine - # Iodine's {Iodine::TLS} instances hold SSL/TLS settings for secure connections. - # - # This allows both secure client connections and secure server connections to be established. - # - # tls = Iodine::TLS.new "localhost" # self-signed certificate - # Iodine.listen service: "http", handler: APP, tls: tls - # - # Iodine abstracts away the underlying SSL/TLS library to minimize the risk of misuse and insecure settings. - # - # Calling TLS methods when no SSL/TLS library is available should result in iodine crashing. This is expected behavior. - # - # At the moment, only OpenSSL is supported. BearSSL support is planned. - class TLS - end -end diff --git a/lib/iodine/version.rb b/lib/iodine/version.rb index 9ed711b3..86db28c8 100644 --- a/lib/iodine/version.rb +++ b/lib/iodine/version.rb @@ -1,3 +1,16 @@ +# frozen_string_literal: true + module Iodine - VERSION = '0.7.57'.freeze + # The Iodine gem version number + VERSION = "0.8.0.dev" + # NeoRack supported extensions. + def self.extensions + return (@extentions ||= { neo_rack: "0.0.2".split(?.), + ws: "0.0.1".split(?.), + sse: "0.0.1".split(?.), + pubsub: "0.0.1".split(?.), + cookies: "0.0.1".split(?.), + from: "0.0.1".split(?.) + }) + end end diff --git a/lib/rack/handler/iodine.rb b/lib/rack/handler/iodine.rb deleted file mode 100644 index c6a5f249..00000000 --- a/lib/rack/handler/iodine.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'iodine' unless defined?(::Iodine::VERSION) - -module Iodine - # Iodine's {Iodine::Rack} module provides a Rack compliant interface (connecting Iodine to Rack) for an HTTP and Websocket Server. - module Rack - - # Runs a Rack app, as par the Rack handler requirements. - def self.run(app, options = {}) - # nested applications... is that a thing? - Iodine.listen(service: :http, handler: app, port: options[:Port], address: options[:Host]) - - # start Iodine - Iodine.start - - true - end - - # patches an assumption by Rack, issue #98 code donated by @Shelvak (Néstor Coppi) - def self.shutdown - Iodine.stop - end - - IODINE_RACK_LOADED = true - end -end - -ENV['RACK_HANDLER'] ||= 'iodine' - -begin - ::Rack::Handler.register('iodine', 'Iodine::Rack') if defined?(::Rack::Handler) - ::Rack::Handler.register('Iodine', 'Iodine::Rack') if defined?(::Rack::Handler) -rescue StandardError -end diff --git a/mustache.md b/mustache.md new file mode 100644 index 00000000..b290b651 --- /dev/null +++ b/mustache.md @@ -0,0 +1,55 @@ +# Iodine::Mustashe + +Iodine::Mustache is a lighter implementation of the mustache template rendering gem, with a focus on a few minor security details: + +1. HTML escaping is more aggressive, increasing XSS protection. Read why at: [wonko.com/post/html-escaping](https://wonko.com/post/html-escaping). + +2. Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `user`, `name.first` and than as each `user`, `name` , `first`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts. + +3. Less logic: i.e., lambdas / procs do not automatically invoke a re-rendering... I'd remove them completely as unsafe, but for now there's that. + +4. Improved Protection against Endless Recursion: i.e., Partial templates reference themselves when recursively nested (instead of being recursively re-loaded); and Partial's context is limited to their starting point's context (cannot access parent context). + +It wasn't designed specifically for speed or performance... but it ended up being significantly faster. + +## Usage + +This approach to Mustache templates may require more forethought when designing either the template or the context's data format, however it should force implementations to be more secure and performance aware. + +Approach: + +```ruby +require 'iodine' +# One-off rendering of (possibly dynamic) template: +result = Iodine::Mustache.render(template: "{{foo}}", ctx: {foo: "bar"}) # => "bar" +# caching of parsed template data for multiple render operations: +view = Iodine::Mustache.new(file: "./views/foo.mustache", template: "{{foo}}") +results = Array.new(100) {|i| view.render(foo: "bar#{i}") } # => ["bar0", "bar1", ...] +``` + +## Performance + +Performance testing requires the main Ruby `mustache` gem as well as the `benchmark-ips` gem. + +Test using: + +```ruby +require 'iodine/benchmark' +Iodine::Benchmark.mustache +``` + +On my machine this benchmark indicates about an x10 performance boost. + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine. + +This gem is a C extension, so you will need to know C to use it. + +The gem also leverages the facil.io C STL library under the hood, so things related to the facil.io library will have to go in the facil.io repo. + +Good luck! + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/sig/iodine.rbs b/sig/iodine.rbs new file mode 100644 index 00000000..04708ff8 --- /dev/null +++ b/sig/iodine.rbs @@ -0,0 +1,4 @@ +module Iodine + VERSION: String + # See the writing guide of rbs: https://github.com/ruby/rbs#guides +end diff --git a/spec/integration/chunked_encoding_spec.rb b/spec/integration/chunked_encoding_spec.rb deleted file mode 100644 index c1065af3..00000000 --- a/spec/integration/chunked_encoding_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'tempfile' -RSpec.describe 'Transfer-Encoding: chunked', with_app: :echo do - let(:body_size) { 6 } - let(:body_string) { SecureRandom.hex(body_size).to_s[0...body_size] } - let(:io) do - # ensures theres no size sneaking in - f = Tempfile.new("body4test") - f.write(body_string) - f.rewind - f - # reader, writer = IO.pipe - # writer.write(body_string) - # writer.close - # reader - end - - shared_examples 'chunked body' do - it 'returns the correct body' do - response = http_post("/", headers: headers, body: io) - - expect(response.headers['Content-Length']).to eql(body_size.to_s) - expect(response.body.to_s).to eql(body_string) - end - - it 'returns the correct Content-Length' do - response = http_post("/", headers: headers, body: io) - - expect(response.headers['Content-Length']).to eql(body_size.to_s) - end - end - - context 'when the request is not chunked' do - let(:headers) { Hash['Content-Length' => body_size] } - - include_examples 'chunked body' - end - - context 'when the request is chunked' do - let(:headers) { Hash['Transfer-Encoding' => 'chunked'] } - - include_examples 'chunked body' - end - - context 'when the header is downcased' do - let(:headers) { Hash['transfer-encoding' => 'chunked'] } - - include_examples 'chunked body' - end - - context 'when the body size is long and unevenly chunked' do - let(:headers) { Hash['Transfer-Encoding' => 'chunked'] } - let(:body_size) { 0x4001 } - - include_examples 'chunked body' - end - - context 'when the body size is long and evenly chunked' do - let(:headers) { Hash['Transfer-Encoding' => 'chunked'] } - let(:body_size) { 0x4000 + 0x4000 } - - include_examples 'chunked body' - end - - context 'when the body size is small (includes Content-Length)' do - let(:body_size) { 32 } - let(:headers) { Hash['Content-Length' => body_size.to_s] } - - include_examples 'chunked body' - end - - context 'when the body size is long (includes Content-Length)' do - let(:body_size) { 0x4000 + 0x4000 } - let(:headers) { Hash['Content-Length' => body_size.to_s] } - - include_examples 'chunked body' - end -end diff --git a/spec/integration/last_modified_header_spec.rb b/spec/integration/last_modified_header_spec.rb deleted file mode 100644 index 3a5e8404..00000000 --- a/spec/integration/last_modified_header_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'http' - -RSpec.describe 'Last-Modified Header', with_app: :last_modified do - # it 'is parseable by Time#httpdate' do - # response = http_get("/") - # last_modified_str = response.headers['Last-Modified'] - # parsed = Time.httpdate(last_modified_str) - - # expect(parsed).to be_a(Time) - # end - - it 'does not override the header if it is explicitly set' do - response = http_get("?last_modified=foo") - last_modified_str = response.headers['Last-Modified'] - - expect(last_modified_str).to eql("foo") - end - - # it 'overrides the header if the value is set to nil' do - # response = http_get("?last_modified=nil") - # last_modified_str = response.headers['Last-Modified'] - - # expect(Time.httpdate(last_modified_str)).to be_a(Time) - # end -end diff --git a/spec/log/.gitkeep b/spec/log/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 20cd8567..00000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,102 +0,0 @@ -require_relative './../lib/iodine' - -# This file was generated by the `rspec --init` command. Conventionally, all -# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. -# The generated `.rspec` file contains `--require spec_helper` which will cause -# this file to always be loaded, without a need to explicitly require it in any -# files. -# -# Given that it is always loaded, you are encouraged to keep this file as -# light-weight as possible. Requiring heavyweight dependencies from this file -# will add to the boot time of your test suite on EVERY test run, even for an -# individual file that may not need all of that loaded. Instead, consider making -# a separate helper file that requires the additional dependencies and performs -# the additional setup, and require it from the spec files that actually need -# it. -# -# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration -Dir["spec/support/**/*.rb"].each { |f| load(f) } - -RSpec.configure do |config| - # rspec-expectations config goes here. You can use an alternate - # assertion/expectation library such as wrong or the stdlib/minitest - # assertions if you prefer. - config.expect_with :rspec do |expectations| - # This option will default to `true` in RSpec 4. It makes the `description` - # and `failure_message` of custom matchers include text for helper methods - # defined using `chain`, e.g.: - # be_bigger_than(2).and_smaller_than(4).description - # # => "be bigger than 2 and smaller than 4" - # ...rather than: - # # => "be bigger than 2" - expectations.include_chain_clauses_in_custom_matcher_descriptions = true - end - - # rspec-mocks config goes here. You can use an alternate test double - # library (such as bogus or mocha) by changing the `mock_with` option here. - config.mock_with :rspec do |mocks| - # Prevents you from mocking or stubbing a method that does not exist on - # a real object. This is generally recommended, and will default to - # `true` in RSpec 4. - mocks.verify_partial_doubles = true - end - - # This option will default to `:apply_to_host_groups` in RSpec 4 (and will - # have no way to turn it off -- the option exists only for backwards - # compatibility in RSpec 3). It causes shared context metadata to be - # inherited by the metadata hash of host groups and examples, rather than - # triggering implicit auto-inclusion in groups with matching metadata. - config.shared_context_metadata_behavior = :apply_to_host_groups - -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ - # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode - config.disable_monkey_patching! - - # This setting enables warnings. It's recommended, but in some cases may - # be too noisy due to issues in dependencies. - config.warnings = true - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -end diff --git a/spec/support/apps/echo.ru b/spec/support/apps/echo.ru deleted file mode 100644 index 23ec670a..00000000 --- a/spec/support/apps/echo.ru +++ /dev/null @@ -1,9 +0,0 @@ -run ->(env) do - body = env['rack.input'].read - if body.length > 0 - puts "BODY(#{body.length}): #{body[0...8]}" - else - puts "NO BODY (content-length: #{env['CONTENT_LENGTH'].to_s})" - end - [200, {}, [body] ] -end diff --git a/spec/support/apps/last_modified.ru b/spec/support/apps/last_modified.ru deleted file mode 100644 index 91923e8a..00000000 --- a/spec/support/apps/last_modified.ru +++ /dev/null @@ -1,33 +0,0 @@ -# This is a simple Hello World Rack application -# -# Running this application from the command line is easy with: -# -# iodine hello.ru -# -# Or, in single thread and single process: -# -# iodine -t 1 -w 1 hello.ru -# -# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients): -# -# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/ -# wrk -c2000 -d5 -t12 http://localhost:3000/ - -module HelloWorld - # This is the HTTP response object according to the Rack specification. - - # this is function will be called by the Rack server (iodine) for every request. - def self.call env - req = Rack::Request.new(env) - - headers = {} - - headers['Last-Modified'] = req.params['last_modified'] if req.params['last_modified'] - headers['Last-Modified'] = nil if req.params['last_modified'] == 'nil' - - [200, headers, [] ] - end -end - -# this function call links our HelloWorld application with Rack -run HelloWorld diff --git a/spec/support/iodine_server.rb b/spec/support/iodine_server.rb deleted file mode 100644 index ff97cca0..00000000 --- a/spec/support/iodine_server.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'http' -require 'bundler' - -module Spec - module Support - module IodineServer - def http_client - HTTP.timeout(1) - end - - def http_get(path, *args) - http_client.get("http://localhost:#{server_port}#{path}", *args) - end - - def http_post(path, *args) - http_client.post("http://localhost:#{server_port}#{path}", *args) - end - - def spawn_with_test_log(cmd, verbose: ENV.key?('VERBOSE')) - test_log = verbose ? STDERR : File.open('spec/log/test.log', 'a+') - - Bundler.with_unbundled_env do - Process.spawn(cmd, out: test_log, err: test_log) - end - end - - def server_port - 2222 - end - - def wait_until_iodine_ready - tries = 0 - - loop do - begin - sleep 0.1 - Socket.tcp('localhost', server_port, connect_timeout: 1) {} - break - rescue Errno::ETIMEDOUT - raise "Could not start iodine, please check spec/log/test.log for more info" if tries > 10 - tries += 1 - rescue Errno::ECONNREFUSED - raise "Could not start iodine, please check spec/log/test.log for more info" if tries > 10 - tries += 1 - end - end - end - - def start_iodine_with_app(name, **opts) - filename = "spec/support/apps/#{name}.ru" - raise "test rack file (#{name}) does not exist" unless File.exist?(filename) - if Gem.win_platform? - cmd = "bundle exec ruby exe/iodine -w 1 -t 1 -p #{server_port}".dup - else - cmd = "bundle exec exe/iodine -w 1 -t 1 -p #{server_port}".dup - end - cmd += " -V 5 -log" if opts[:verbose] - pid = spawn_with_test_log("#{cmd} #{filename}", **opts) - wait_until_iodine_ready - pid - end - - def with_app(name, **opts) - pid = start_iodine_with_app(name, **opts) - - begin - yield if block_given? - ensure - if !pid.nil? - if Gem.win_platform? - # SIGINT or SIGILL are unreliable on Windows, try native taskkill first - Process.kill('KILL', pid) unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL") - else - Process.kill 'SIGINT', pid - end - Process.wait pid - end - end - end - end - end -end - -RSpec.configure do |config| - config.define_derived_metadata(:file_path => %r{/spec/integration/}) do |metadata| - metadata[:type] = :integration - end - - when_tagged_with_app = { with_app: ->(v) { !!v } } - - config.around(:each, when_tagged_with_app) do |ex| - with_app(ex.metadata[:with_app], verbose: ex.metadata[:verbose]) { ex.run } - end - - config.include(Spec::Support::IodineServer, type: :integration) -end diff --git a/spec/unit/iodine_spec.rb b/spec/unit/iodine_spec.rb deleted file mode 100644 index ce07cb4c..00000000 --- a/spec/unit/iodine_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -RSpec.describe Iodine do - describe '.running?' do - it 'is false when Iodine is not running' do - expect(Iodine.running?).to be(false) - end - end -end diff --git a/test/iodine_test.rb b/test/iodine_test.rb deleted file mode 100644 index e5c22767..00000000 --- a/test/iodine_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class IodineTest < Minitest::Test - def test_that_it_has_a_version_number - refute_nil ::Iodine::VERSION - end - - def test_something - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 7097a120..00000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) - -require 'iodine' -require 'minitest/autorun'