Lots of changes

Notable ones are that now every message has a field for the target
server, and that messages have an "optional" ID  in that they dont have it
from client -> server and they do have it from server -> client.
This commit is contained in:
2026-05-24 22:56:15 -07:00
parent a3f5470549
commit c2b7575ff4
5 changed files with 193 additions and 42 deletions
+88 -26
View File
@@ -1,7 +1,7 @@
use uuid::Uuid;
use crate::message::{MessageId,TaggedMessage,VerificationError,Relevance};
use crate::state::{StateType,StateValue,StatePath,StatePermission,StatePermissionKey};
use crate::{RoomId,Group,Role,User,GroupPower};
use crate::state::{self,StateType,StateValue,StatePath,StatePermission,StatePermissionKey};
use crate::{RoomId,Group,Role,User,GroupPower,ServerAddr};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
@@ -13,19 +13,19 @@ use time::OffsetDateTime;
#[derive(Serialize,Deserialize,Clone,Debug)]
/// This exists solely to allow the DB to finish tagging the message. It is every field of a
/// message except for the ID which can be created using a Postgres serial type
pub struct InsertableClientMessage {
pub message: ClientMessage,
#[serde(with="time::serde::timestamp")]
pub client_timestamp: OffsetDateTime,
#[serde(with="time::serde::timestamp")]
pub server_timestamp: OffsetDateTime,
#[serde(with = "serde_bytes")]
pub signature: Box<[u8]>,
pub user: User
}
//#[derive(Serialize,Deserialize,Clone,Debug)]
///// This exists solely to allow the DB to finish tagging the message. It is every field of a
///// message except for the ID which can be created using a Postgres serial type
//pub struct InsertableClientMessage {
// pub message: ClientMessage,
// #[serde(with="time::serde::timestamp")]
// pub client_timestamp: OffsetDateTime,
// #[serde(with="time::serde::timestamp")]
// pub server_timestamp: OffsetDateTime,
// #[serde(with = "serde_bytes")]
// pub signature: Box<[u8]>,
// pub user: User
//}
#[derive(Serialize,Deserialize,Clone,Debug)]
@@ -39,17 +39,26 @@ pub struct SignedClientMessage {
// Can I reserialize the message and check the signature that way??
// That would be very brittle but there isn't a good way to extract the
// bytes using serde. I'll write something better at some point
//
// Should this be optional?
#[serde(with = "serde_bytes")]
pub signature: Box<[u8]>
pub signature: Box<[u8]>,
// Which server should process the request. Most messages are forwardable but some are not,
// like auth requests and media uploads.
//
// Setting this to None makes it a local message
pub target: Option<ServerAddr>
}
impl SignedClientMessage {
pub fn tag(self, username: User) -> InsertableClientMessage {
InsertableClientMessage {
pub fn tag(self, username: User, servername: ServerAddr) -> TaggedMessage {
TaggedMessage {
message: self.message,
client_timestamp: self.timestamp,
server_timestamp: OffsetDateTime::now_utc(),
signature: self.signature,
user: username,
target: self.target.unwrap_or(servername)
}
}
pub fn verify(&self) -> Result<bool,VerificationError> {
@@ -59,7 +68,6 @@ impl SignedClientMessage {
#[derive(Serialize,Deserialize,Clone,Debug)]
pub enum ClientMessage {
Auth{
username: String,
password: String
@@ -79,7 +87,13 @@ pub enum ClientMessage {
// Should it be one message type or mutiple?
Message {
body: String,
room_id: RoomId
room_id: RoomId,
// Can I overload this to have an optional ID? And reject any
// incoming messages that specify an ID? How else do I return an ID
// only for this message
//
// This is a hacky way to do it but idk how else to
id: Option<MessageId>
},
// Private message/invite mechanism
MessagePost {
@@ -100,8 +114,8 @@ pub enum ClientMessage {
StateCreate {
room_id: RoomId,
path: StatePath,
ty: StateType,
permissions: Option<Vec<StatePermission>>
content: StateValue,
permissions: Option<state::PermissionTable>
},
StateWrite {
room_id: RoomId,
@@ -127,9 +141,17 @@ pub enum ClientMessage {
path: StatePath
},
PermissionAdd {
room_id: RoomId,
path: StatePath,
permission: StatePermission
},
PermissionRead {
room_id: RoomId,
path: StatePath,
},
PermissionDelete {
room_id: RoomId,
path: StatePath,
permission: StatePermissionKey
},
// Groups really should have a way to add permissions by user
@@ -197,7 +219,10 @@ pub enum ClientMessage {
bytes: Vec<u8>
},
// Join and subscribe
Join {
RoomJoin {
room_id: RoomId,
},
RoomCreate {
room_id: RoomId,
},
SubscribeMessages {
@@ -213,11 +238,34 @@ pub enum ClientMessage {
}
}
impl ClientMessage<> {
pub fn is_forwardable(&self) -> bool {
use ClientMessage::*;
match self {
MediaUpload{bytes: _}
| Auth{
username: _,
password: _
}
| UserCreate {
username: _,
password: _,
}
// In the future challenges might be forwardable but right now they are
// only used for signups and auth is local.
| ChallengeAnswer {
response: _
} => false,
_ => true
}
}
pub fn get_relevance(&self) -> Option<Relevance> {
use ClientMessage::*;
Some(match self {
Message {
body: _,
id: _,
room_id
// These don't necessarily need cloned, it might make sense to make an owned
// version of Relevance and a reference version of it
@@ -256,14 +304,26 @@ impl ClientMessage<> {
}
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize,Deserialize,Debug)]
pub enum ServerError {
InvalidPermission,
// Server can specify the required challenge
ChallengeInvitecode,
ChallengeInviteCode,
NotAuthenticated,
AlreadyAuthenticated,
NotInChallenge
AuthenticationFailed,
NotInChallenge,
NotJoined(RoomId),
RoomExists(RoomId),
RoomNotFound(RoomId),
ValueNotFound(StatePath),
MismatchedTypes,
DeleteNotEmpty(StatePath),
RemoveLastOwner,
MessageNotForwardable,
UserAlreadyExists,
Generic
}
@@ -276,6 +336,8 @@ pub enum ServerMessage {
Error(ServerError),
// Returned on read
State(StatePath,StateValue),
// Returned on permission read
StatePermission(StatePath,crate::state::PermissionTable),
// Returned on subscribe, forwards state change message to client
StateChange(TaggedMessage),
OkMessage(MessageId),
+3 -1
View File
@@ -50,10 +50,12 @@ pub enum ServerResponse {
Error(FederationError),
State(StateValue),
Media(Uuid,#[serde(with = "serde_bytes")] Vec<u8>),
ValidGroups(User,Vec<StatePermissionKey>)
ValidGroups(User,Vec<StatePermissionKey>),
Ok
}
#[derive(Serialize,Deserialize)]
pub enum FederationError {
InvalidPermission,
ServerError(crate::client::ServerError)
}
+11 -6
View File
@@ -8,22 +8,23 @@ pub mod message;
// Room coordinates and originating server
#[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
pub struct RoomId{
coordinates: Vec<i64>,
server: Option<String>
pub coordinates: Vec<i64>,
//pub server: Option<String>
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct User {
name: String,
pub name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here
server: String
pub server: String
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct Group {
name: String,
pub name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here
server: String
// Dont need probably because this is already covered in the target field
//pub server: String
}
// Might want to standardize this, idk which powers would be useful though
@@ -36,3 +37,7 @@ pub struct GroupPower(String);
pub struct Role {
name: String
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct ServerAddr(pub String);
+20 -2
View File
@@ -1,7 +1,7 @@
use crate::RoomId;
use crate::client::ClientMessage;
use crate::state::StatePath;
use crate::User;
use crate::{User,ServerAddr};
use time::OffsetDateTime;
use serde::{Deserialize, Serialize};
@@ -16,6 +16,7 @@ pub struct MessageId(u64);
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct TaggedMessage {
// How do I return message IDs
pub message: ClientMessage,
#[serde(with="time::serde::timestamp")]
pub client_timestamp: OffsetDateTime,
@@ -23,7 +24,8 @@ pub struct TaggedMessage {
pub server_timestamp: OffsetDateTime,
#[serde(with = "serde_bytes")]
pub signature: Box<[u8]>,
pub user: User
pub user: User,
pub target: ServerAddr
}
impl TaggedMessage {
pub fn verify(&self) -> Result<bool,VerificationError> {
@@ -42,6 +44,22 @@ pub enum Relevance {
Post(User)
}
//#[derive(Serialize,Deserialize,Clone,Debug)]
//pub struct Message {
// pub body: String,
// pub room_id: RoomId,
// pub target: ServerAddr,
// pub message_id: MessageId,
// #[serde(with="time::serde::timestamp")]
// pub client_timestamp: OffsetDateTime,
// #[serde(with="time::serde::timestamp")]
// pub server_timestamp: OffsetDateTime,
// #[serde(with = "serde_bytes")]
// pub signature: Box<[u8]>,
// pub user: User,
//
//}
//#[derive(Serialize,Deserialize,Debug,Clone)]
//pub struct Message {
// body: String,
+71 -7
View File
@@ -1,9 +1,25 @@
use serde::{Deserialize,Serialize};
use std::collections::{HashMap,HashSet};
use crate::{User,Group,Role};
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize,Deserialize,Clone,Debug,PartialEq)]
pub enum StateValue {
String(String),
Binary(Vec<u8>)
Binary(Vec<u8>),
// A directory in a readable format to allow for something like `ls`
// on clients
Directory(HashMap<String,PermissionTable>),
}
impl StateValue {
pub fn push_other(&mut self, other: &StateValue) -> Result<(),()> {
match (self,other) {
(StateValue::String(s),StateValue::String(o)) => s.push_str(o),
(StateValue::Binary(b),StateValue::Binary(o)) => b.extend_from_slice(o),
_ => return Err(())
}
Ok(())
}
}
#[derive(Serialize,Deserialize,Debug,Clone)]
pub enum StateType {
@@ -18,20 +34,68 @@ pub enum StatePermissionKey {
User(User),
Group(Group),
Role(Group,Role),
// Only the server can edit these. Potentially users with admin permissions
// too.
Server,
// Give everyone a permission. Useful for making a file server readable
Everyone,
// Give permissions to the room operator/admin. This is kept track of
// in the room state. Can operators remove other operators?
Operator,
//This is a string for now but could get compiled later into an actual regex
//should be able to match on name and server
UserRegex(String)
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,Eq)]
pub enum StatePermissionValue {
None,
Read,
ReadWrite,
// Can only set value, does not allow appending
Write
Write,
Owner
}
impl StatePermissionValue {
// Binary relation
// Maybe could have overloaded a comparison operator for this
//
// Owner
// |
// |
// ReadWrite
// / \
// Read Write
pub fn allows(&self, other: &StatePermissionValue) -> bool {
use StatePermissionValue::*;
match (self,other) {
(Read,Read) => true,
(ReadWrite,Read) => true,
(Write,Write) => true,
(ReadWrite,Write) => true,
// Owner allows everything
(Owner,_) => true,
_ => false
}
}
}
#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,Eq)]
pub struct StatePermission(pub StatePermissionKey,pub StatePermissionValue);
#[derive(Serialize,Deserialize,Clone,Debug,PartialEq)]
pub struct PermissionTable(pub HashMap<StatePermissionKey,StatePermissionValue>);
impl PermissionTable {
pub fn get_allowed(&self, needed: &StatePermissionValue) -> HashSet<StatePermissionKey> {
let mut set = HashSet::new();
for (key,perm) in self.0.iter() {
if perm.allows(&needed) {
set.insert(key.clone());
}
}
set
}
}
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct StatePermission(StatePermissionKey,StatePermissionValue);
#[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
pub struct StatePath(Vec<String>);
pub struct StatePath(pub Vec<String>);