Design principles of Websocat4

  • Starting up Websocat and connection establishment is allowed to be slow (i.e. allowed to parse strings, allocate memory, walk trees). Data transfer in already established connections should be fast.
  • Rhai is used as a "control plane" and decides what connect to what and how transform layers are connected to each other.
  • When serving multiple connections, it is OK to execute Rhai code each time. It is not OK to execute Rhai code for each transferred packet within a connection.
  • Rhai script fully defines Websocat behaviour. Most other CLI options just affect how Rhai script is generated. Filenames / process arguments get serialized as bytes, maybe inserted as base64 into the script if needed.
  • It is OK to have Rhai functions that are reachable only when explicitly specifying the script (unused by high-level CLI).
  • Each connection (or connection direction) is either bytestream-oriented or datagram-oriented. Bytestream-oriented use tokio::io::{AsyncRead, AsyncWrite}, datagram-oriented connections use custom traits (not Stream<Bytes> / Sink<Bytes>).
  • Custom traits for datagram connections are geared towards Websocket use case and allows typical use cases without allocations or excessive copying data around each invocation. Unusual decisions include mutable buffer for writer (to aid in-place masking for WebSocket) and partial buffer immutability guarantees for readers.
  • WebSocket framing library is custom, designed with avoiding allocations in mind.
  • It is OK to have multiple indirections for each transferred data chunk (i.e. dyn layer between stdio and line wrapper, then between line wrapper and WebSocket framer, then between the framer and TLS layer, then between the TLS layer and TCP socket).
  • fn poll is also the way, not everything needs to be fn async .await.
  • It is OK to add more elaborate scenario_executor things to simplify Websocat's Rhai scripts.
  • Address types and overlay stacks use enum approach, not dyn.
  • Generated Rhai code should be readable and may be optimised a bit, but "callback hell" is allowed.
  • Documentation is generated by a hacky script parsing source code with regexes. Rust source code may need to be adjusted a bit to aid this.
  • Most of things should be within Safe Rust realm, but various low-level tricky things (custom socket types, raw sockets, tty, fork, dup2, signals) may use unsafe, with soundness that relies on sanity of choices supplied by end user.
  • Most of things should be accessible from CLI API, but fine-tuning order of operations or using the a specifier/overlay multiple times with different parameters may require editing Rhai script. If actually requested by users, this editing can be somehow simplified.