7.3 KiB
System Architecture
This document provides a detailed overview of the circles
project architecture. The project is composed of two core library crates, server_ws
and client_ws
, and a convenient launcher
utility.
1. High-Level Overview
The circles
project provides the core components for a client-server system designed to execute Rhai scripts in isolated environments. The launcher
application is a utility that demonstrates how to use the server_ws
and client_ws
libraries to manage multiple server instances, but the libraries themselves are the fundamental building blocks.
The core functionality revolves around:
- Orchestration: The
launcher
starts and stops multiple, independent WebSocket servers. - Client-Server Communication: A JSON-RPC 2.0 API over WebSockets allows clients to execute scripts and authenticate.
- Authentication: An optional, robust
secp256k1
signature-based authentication mechanism secures the script execution endpoint.
2. Component Architecture
2.1. server_ws
(Library)
The server_ws
crate provides the WebSocket server that handles client connections and API requests. Its key features include:
- Web Framework: Built using
Actix
, a powerful actor-based web framework for Rust. - WebSocket Handling: Uses
actix-web-actors
to manage individual WebSocket sessions. Each client connection is handled by aCircleWs
actor, ensuring that sessions are isolated from one another. - JSON-RPC API: Exposes a JSON-RPC 2.0 API with methods for script execution (
play
) and authentication (fetch_nonce
,authenticate
). - Authentication Service: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods.
2.2. client_ws
(Library)
The client_ws
crate is a WebSocket client library designed for interacting with the server_ws
. It is engineered to be cross-platform:
- Native: For native Rust applications, it uses
tokio-tungstenite
for WebSocket communication. - WebAssembly (WASM): For browser-based applications, it uses
gloo-net
to integrate with the browser's native WebSocket API. - API: Provides a flexible builder pattern for client construction and a high-level API (
CircleWsClient
) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol.
2.3. launcher
(Utility)
The launcher
is a command-line utility that demonstrates how to use the server_ws
library. It is responsible for:
- Configuration: Reading a
circles.json
file that defines a list of Circle instances to run. - Orchestration: Spawning a dedicated
server_ws
instance for each configured circle. - Lifecycle Management: Managing the lifecycle of all spawned servers and their associated Rhai workers.
2.2. server_ws
The server_ws
crate provides the WebSocket server that handles client connections and API requests. Its key features include:
- Web Framework: Built using
Actix
, a powerful actor-based web framework for Rust. - WebSocket Handling: Uses
actix-web-actors
to manage individual WebSocket sessions. Each client connection is handled by aCircleWs
actor, ensuring that sessions are isolated from one another. - JSON-RPC API: Exposes a JSON-RPC 2.0 API with methods for script execution (
play
) and authentication (fetch_nonce
,authenticate
). - Authentication Service: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods.
2.3. client_ws
The client_ws
crate is a WebSocket client library designed for interacting with the server_ws
. It is engineered to be cross-platform:
- Native: For native Rust applications, it uses
tokio-tungstenite
for WebSocket communication. - WebAssembly (WASM): For browser-based applications, it uses
gloo-net
to integrate with the browser's native WebSocket API. - API: Provides a flexible builder pattern for client construction and a high-level API (
CircleWsClient
) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol.
3. Communication and Protocols
3.1. JSON-RPC 2.0
All client-server communication, including authentication, uses the JSON-RPC 2.0 protocol over the WebSocket connection. This provides a unified, lightweight, and well-defined structure for all interactions. The formal API contract is defined in the openrpc.json file.
3.2. Authentication Flow
The authentication mechanism is designed to verify that a client possesses the private key corresponding to a given public key, without ever exposing the private key. The entire flow happens over the established WebSocket connection.
Sequence of Events:
- Keypair: The client is instantiated with a
secp256k1
keypair. - Nonce Request: The client sends a
fetch_nonce
JSON-RPC request containing its public key. - Nonce Issuance: The server generates a unique, single-use nonce, stores it in the actor's state, and returns it to the client in a JSON-RPC response.
- Signature Creation: The client signs the received nonce with its private key.
- Authentication Request: The client sends an
authenticate
JSON-RPC message, containing the public key and the generated signature. - Signature Verification: The server's WebSocket actor retrieves the stored nonce for the given public key and cryptographically verifies the signature.
- Session Update: If verification is successful, the server marks the client's WebSocket session as "authenticated," granting it access to protected methods like
play
.
4. Diagrams
4.1. System Component Diagram
graph TD
subgraph "User Machine"
Launcher[🚀 launcher]
CirclesConfig[circles.json]
Launcher -- Reads --> CirclesConfig
end
subgraph "Spawned Processes"
direction LR
subgraph "Circle 1"
Server1[🌐 server_ws on port 9001]
end
subgraph "Circle 2"
Server2[🌐 server_ws on port 9002]
end
end
Launcher -- Spawns & Manages --> Server1
Launcher -- Spawns & Manages --> Server2
subgraph "Clients"
Client1[💻 client_ws]
Client2[💻 client_ws]
end
Client1 -- Connects via WebSocket --> Server1
Client2 -- Connects via WebSocket --> Server2
4.2. Authentication Sequence Diagram
sequenceDiagram
participant Client as client_ws
participant WsActor as CircleWs Actor (WebSocket)
Client->>Client: Instantiate with keypair
Note over Client: Has public_key, private_key
Client->>+WsActor: JSON-RPC "fetch_nonce" (pubkey)
WsActor->>WsActor: generate_nonce()
WsActor->>WsActor: store_nonce(pubkey, nonce)
WsActor-->>-Client: JSON-RPC Response ({"nonce": "..."})
Client->>Client: sign(nonce, private_key)
Note over Client: Has signature
Client->>+WsActor: JSON-RPC "authenticate" (pubkey, signature)
WsActor->>WsActor: retrieve_nonce(pubkey)
WsActor->>WsActor: verify_signature(nonce, signature, pubkey)
alt Signature is Valid
WsActor->>WsActor: Set session as authenticated
WsActor-->>-Client: JSON-RPC Response ({"authenticated": true})
else Signature is Invalid
WsActor-->>-Client: JSON-RPC Error (Invalid Credentials)
end
Note over WsActor: Subsequent "play" requests will include the authenticated public key.