🇮🇹 Leggi in Italiano | 🇬🇧 Read in English
Ruggine è un'applicazione di chat client-server sicura, stateful e ottimizzata per il consumo di risorse. Il backend è interamente sviluppato in Rust, mentre il client è un'applicazione desktop basata su Tauri e React.
- 🚀 Performance & Efficienza: Sviluppato in Rust con runtime asincrono (Tokio).
- 💬 Comunicazione Real-Time: Streaming bidirezionale tramite gRPC e Protobuf.
- 🔒 Sicurezza Avanzata: Crittografia TLS 1.3, autenticazione JWT stateless, password hashing con Argon2.
- 🗄️ Doppia Persistenza: PostgreSQL lato server (transazioni ACID) e cache locale super-veloce con Sled (Rust embedded DB).
- 🖥️ Desktop App Moderna: Interfaccia utente sviluppata con React, pacchettizzata come app desktop leggera usando Tauri.
Prerequisiti: Docker, Rust/Cargo, Node.js.
- Avvia il Database (PostgreSQL):
docker-compose up -d
- Avvia il Server:
cargo run --bin server
- Avvia il Client (Tauri/React):
cd client npm install npm run tauri dev
Ruggine è progettata seguendo un'architettura client-server stateful, ottimizzata per integrità dei dati, sicurezza e minimizzazione del consumo di risorse (CPU e banda).
Di seguito sono riportate le scelte progettuali che sono state operate dal team di sviluppo con relative spiegazioni e alternative scartate.
Per la comunicazione tra client e server è stato scelto il protocollo gRPC (Google Remote Procedure Call) basato su HTTP/2, utilizzando Protobuf (Protocol Buffers) come meccanismo di serializzazione.
-
PERCHÉ QUESTA SCELTA:
- Efficienza: Protobuf serializza i dati in un formato binario compatto, riducendo l'overhead di rete rispetto a formati testuali come JSON (usato in REST) o XML. In questo modo i messaggi sono più piccoli e veloci, riducendo sia il consumo di banda che il carico sulla CPU dovuto ai processi di serializzazione/deserializzazione.
- Strong Typing: gRPC definisce un contratto rigoroso (
.proto). Questo previene del tutto errori di tipo a tempo di compilazione e facilita lo sviluppo di client e server, migliorando l'integrità dei dati scambiati. - Streaming: gRPC supporta nativamente lo streaming bidirezionale, permettendo comunicazioni più efficienti e reattive (il server può fare push verso il client evitando il polling continuo), ideale per applicazioni in tempo reale.
-
ALTERNATIVE SCARTATE:
- MQTT: Sebbene leggero, MQTT introduce un broker intermedio che aggiunge latenza e aumenta la dimensione del deployment (ulteriore eseguibile da gestire). In questo scenario il server sarebbe diventato un subscriber passivo, complicando la gestione della sicurezza e la logica di business (e.g., validare un invito prima che il messaggio venga distribuito).
- REST (HTTP/1.1 + JSON): Nonostante la sua popolarità, REST con JSON comporta un overhead significativo in termini di dimensioni dei messaggi e tempi di parsing, risultando meno efficiente rispetto a gRPC/Protobuf. Inoltre, REST non supporta nativamente lo streaming bidirezionale, e avrebbe dunque richiesto meccanismi aggiuntivi come WebSocket per ottenere funzionalità simili, frammentando l'architettura.
Per la gestione dei dati persistenti lato server (Utenti, Gruppi, Messaggi, ecc.) è stato scelto PostgreSQL come sistema di gestione di database relazionale (RDBMS).
-
PERCHÉ QUESTA SCELTA:
- Integrità referenziale (ACID): PostgreSQL garantisce transazioni ACID (Atomicità, Consistenza, Isolamento, Durabilità), essenziali per mantenere l'integrità dei dati in un sistema di messaggistica dove la coerenza è cruciale (nessun messaggio "orfano").
- Scalabilità: Anche con milioni di messaggi, l'utilizzo di indici B-Tree su campi chiave (
group_id,timestamp) garantisce tempi di recupero dello storico con complessità algoritmica O(log n). - Query complesse: PostgreSQL supporta query SQL avanzate, facilitando operazioni complesse sui dati (e.g., filtri, join per verificare i permessi) senza dover implementare logiche complicate a livello di applicazione.
-
ALTERNATIVE SCARTATE:
- Apache Kafka: Sebbene eccellente per lo streaming di dati in tempo reale (ingestion), Kafka è pessimo per il retrieval casuale dei dati (query storiche). La sua architettura basata su log append-only lo rende inadatto per query complesse e gestione di relazioni tra entità. Ciò avrebbe implicato l'uso di un database relazionale in aggiunta, complicando l'architettura e violando il requisito di dimensione ridotta dell'applicativo (il driver Kafka è pesante).
- NoSQL (MongoDB, Cassandra): Sebbene i database NoSQL offrano scalabilità orizzontale estrema, spesso sacrificano l'integrità referenziale e le transazioni ACID (teorema CAP: favoriscono Availability su Consistency). Per un sistema di chat di queste dimensioni, la complessità di gestione di un cluster NoSQL e la perdita delle JOIN superano i benefici di performance di scrittura, che sono comunque gestibili da SQL fino a carichi molto elevati.
Il client mantiene una cache degli ultimi N messaggi tramite Sled, un database embedded chiave-valore interamente scritto in Rust.
-
PERCHÉ QUESTA SCELTA:
- Rust & Embedded: Sled è un crate Rust puro, compilato direttamente nell'eseguibile finale. Questo elimina dipendenze da librerie C esterne (comuni in SQLite), facilitando la cross-compilazione e mantenendo il file binario compatto e self-contained.
- Performance: Sled è ottimizzato per le moderne architetture SSD e multi-core. Utilizza tecniche avanzate come lock-free concurrency to maximize efficiency and prevent UI freezing during I/O operations.
- Semplicità d'uso: Per una cache locale, non serve un motore SQL completo. Un approccio Key-Value (Chiave:
MessageID, Valore:MessageBody) è sufficiente e più performante in termini di footprint di memoria.
-
ALTERNATIVE SCARTATE:
- Persistenza su file (JSON, CSV, XML): Sebbene semplice da implementare, la gestione manuale della serializzazione/deserializzazione e l'assenza di indici rendono questa soluzione inefficiente. La complessità di parsing cresce linearmente con il numero di messaggi (O(n)). Inoltre, la mancanza di atomicità compromette l'integrità dei dati in caso di crash improvvisi.
- SQLite: Nonostante SQLite sia un database relazionale leggero, l'overhead di un motore SQL completo è eccessivo per una semplice cache locale sequenziale. Le operazioni SQL richiedono parsing e pianificazione delle query, risultando in un utilizzo di risorse superiore rispetto a un database Key-Value ottimizzato come Sled.
Per soddisfare il requisito del logging CPU ogni 2 minuti senza impattare le prestazioni della chat:
- Implementazione: Un task asincrono separato (gestito dal runtime Tokio) interroga le metriche di sistema usando la crate
sysinfo. - Disaccoppiamento: Questo task gira indipendentemente dal loop principale di gestione messaggi, assicurando che la scrittura del file di log (operazione I/O blocking) non rallenti la ricezione o l'invio dei messaggi.
La sicurezza è stata trattata come requisito fondamentale, bilanciando robustezza e usabilità.
- Trasporto Sicuro (TLS 1.3): Tutta la comunicazione gRPC avviene su canale cifrato TLS. È stata scelta la libreria
rustlsinvece di OpenSSL per garantire la memory safety (essendo scritta in Rust puro) e per evitare dipendenze esterne complesse da linkare, mantenendo la portabilità dell'eseguibile. - Autenticazione (JWT): Il sistema utilizza JSON Web Tokens (JWT) per un'autenticazione stateless. Dopo il login, il server rilascia un token firmato che il client allega ai metadata di ogni richiesta gRPC successiva. Questo riduce il carico di memoria sul server (nessuna sessione da mantenere).
- Hashing delle Password (Argon2): Le password non sono mai salvate in chiaro. Viene utilizzato l'algoritmo Argon2, vincitore della Password Hashing Competition. Essendo memory-hard, Argon2 rende computazionalmente impraticabili gli attacchi brute-force tramite GPU.
- Autorizzazione Server-side: Non viene mai data fiducia al client. Ogni richiesta (es. invio messaggio, unione a gruppo) viene validata dal server controllando nel database PostgreSQL se l'utente possiede i permessi necessari, prevenendo accessi non autorizzati tramite client modificati.
| Componente | Tecnologia | Ruolo | Proprietà Chiave |
|---|---|---|---|
| Server | Rust + Tokio | Orchestratore | High Concurrency, Memory Safety |
| Protocollo | gRPC (Protobuf) | Trasporto | Binary, Type-Safe, Low-Bandwidth |
| DB Server | PostgreSQL | Storage Master | ACID, Relazionale, Indexing |
| DB Client | Sled | Cache Locale | Embedded, Lock-free, Pure Rust |
| Sicurezza | rustls / Argon2 | Protezione | Memory Safe Crypto, GPU Resistent |
Ruggine is a secure, stateful, and resource-efficient client-server chat application. The backend is entirely built with Rust, while the client is a lightweight desktop app built using Tauri and React.
- 🚀 Performance & Efficiency: Built in Rust leveraging the Tokio async runtime.
- 💬 Real-Time Communication: Bidirectional streaming via gRPC and Protobuf.
- 🔒 Advanced Security: TLS 1.3 encryption, stateless JWT authentication, and password hashing via Argon2.
- 🗄️ Dual Persistence: PostgreSQL on the server (ACID transactions) and an ultra-fast local cache using Sled (Rust embedded DB).
- 🖥️ Modern Desktop App: UI built with React, packaged as a lightweight desktop app using Tauri.
Prerequisites: Docker, Rust/Cargo, Node.js.
- Start the Database (PostgreSQL):
docker-compose up -d
- Start the Server:
cargo run --bin server
- Start the Client (Tauri/React):
cd client npm install npm run tauri dev
Ruggine is designed following a stateful client-server architecture, optimized for data integrity, security, and minimization of resource consumption (CPU and bandwidth).
Below are the design choices made by the development team, along with explanations and discarded alternatives.
gRPC (Google Remote Procedure Call) based on HTTP/2 was chosen for client-server communication, utilizing Protobuf (Protocol Buffers) as the serialization mechanism.
-
WHY THIS CHOICE:
- Efficiency: Protobuf serializes data into a compact binary format, significantly reducing network overhead compared to text-based formats like JSON (used in REST) or XML. This results in smaller, faster messages, reducing both bandwidth consumption and CPU load related to serialization/deserialization processes.
- Strong Typing: gRPC defines a strict contract (
.proto). This prevents type errors at compile-time and facilitates the development of both client and server, ensuring the integrity of exchanged data. - Streaming: gRPC natively supports bidirectional streaming. This allows for more efficient and reactive communication (the server can push data to the client, avoiding continuous polling), which is ideal for real-time applications.
-
DISCARDED ALTERNATIVES:
- MQTT: Although lightweight, MQTT introduces an intermediate broker, adding latency and increasing deployment size (an additional executable to manage). In this scenario, the server would have become a passive subscriber, complicating security management and business logic (e.g., validating an invitation before the message is distributed).
- REST (HTTP/1.1 + JSON): Despite its popularity, REST with JSON incurs significant overhead regarding message size and parsing time, making it less efficient than gRPC/Protobuf. Furthermore, REST does not natively support bidirectional streaming, which would have required additional mechanisms like WebSockets to achieve similar functionality, fragmenting the architecture.
PostgreSQL was chosen as the Relational Database Management System (RDBMS) for managing persistent server-side data (Users, Groups, Messages, etc.).
-
WHY THIS CHOICE:
- Referential Integrity (ACID): PostgreSQL guarantees ACID transactions (Atomicity, Consistency, Isolation, Durability), which are essential for maintaining data integrity in a messaging system where consistency is crucial (no "orphan" messages).
- Scalability: Even with millions of messages, the use of B-Tree indices on key fields (
group_id,timestamp) guarantees history retrieval times with logarithmic algorithmic complexity O(log n). - Complex Queries: PostgreSQL supports advanced SQL queries, facilitating complex data operations (e.g., filters, joins for permission checks) without implementing complicated logic at the application level.
-
DISCARDED ALTERNATIVES:
- Apache Kafka: While excellent for real-time data streaming (ingestion), Kafka is poor for random data retrieval (historical queries). Its append-only log architecture makes it unsuitable for complex queries and managing relationships between entities. This would have implied using a relational database alongside it, complicating the architecture and violating the application's small size requirement (the Kafka driver is heavy).
- NoSQL (MongoDB, Cassandra): Although NoSQL databases offer extreme horizontal scalability, they often sacrifice referential integrity and ACID transactions (CAP theorem: favoring Availability over Consistency). For a chat system of this size, the complexity of managing a NoSQL cluster and the loss of JOIN capabilities outweigh the write performance benefits, which are manageable by SQL even at high loads.
The client maintains a cache of the last N messages using Sled, an embedded key-value database written entirely in Rust.
-
WHY THIS CHOICE:
- Rust & Embedded: Sled is a pure Rust crate compiled directly into the final executable. This eliminates dependencies on external C libraries (common in SQLite), facilitating cross-compilation and keeping the binary file compact and self-contained.
- Performance: Sled is optimized for modern SSD and multi-core architectures. It uses advanced techniques like lock-free concurrency to maximize efficiency and prevent UI freezing during I/O operations.
- Ease of Use: A full SQL engine is unnecessary for a local cache. A Key-Value approach (Key:
MessageID, Value:MessageBody) is sufficient and more performant regarding memory footprint.
-
DISCARDED ALTERNATIVES:
- File Persistence (JSON, CSV, XML): Although simple to implement, manual serialization/deserialization management and the absence of indices make this solution inefficient. Parsing complexity grows linearly with the number of messages (O(n)). Moreover, the lack of atomicity compromises data integrity in case of sudden crashes.
- SQLite: Despite SQLite being a lightweight relational database, the overhead of a full SQL engine is excessive for a simple sequential local cache. SQL operations require query parsing and planning, resulting in higher resource usage compared to an optimized Key-Value database like Sled.
To meet the requirement of CPU logging every 2 minutes without impacting chat performance:
- Implementation: A separate asynchronous task (managed by the Tokio runtime) queries system metrics using the
sysinfocrate. - Decoupling: This task runs independently of the main message handling loop, ensuring that writing the log file (a blocking I/O operation) does not slow down message reception or transmission.
Security was treated as a fundamental requirement, balancing robustness and usability.
- Secure Transport (TLS 1.3): All gRPC communication occurs over a TLS-encrypted channel. The
rustlslibrary was chosen over OpenSSL to guarantee memory safety (being written in pure Rust) and to avoid complex external dependencies, maintaining executable portability. - Authentication (JWT): The system uses JSON Web Tokens (JWT) for stateless authentication. After login, the server issues a signed token that the client attaches to the metadata of every subsequent gRPC request. This reduces memory load on the server (no sessions to maintain).
- Password Hashing (Argon2): Passwords are never stored in plain text. The Argon2 algorithm, winner of the Password Hashing Competition, is used. Being memory-hard, Argon2 makes brute-force attacks via GPU computationally impractical.
- Server-side Authorization: The client is never trusted. Every request (e.g., sending a message, joining a group) is validated by the server by checking the PostgreSQL database to see if the user has the necessary permissions, preventing unauthorized access via modified clients.
| Component | Technology | Role | Key Properties |
|---|---|---|---|
| Server | Rust + Tokio | Orchestrator | High Concurrency, Memory Safety |
| Protocol | gRPC (Protobuf) | Transport | Binary, Type-Safe, Low-Bandwidth |
| DB Server | PostgreSQL | Master Storage | ACID, Relational, Indexing |
| DB Client | Sled | Local Cache | Embedded, Lock-free, Pure Rust |
| Security | rustls / Argon2 | Protection | Memory Safe Crypto, GPU Resistent |
