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 uuid::Uuid;
use crate::message::{MessageId,TaggedMessage,VerificationError,Relevance}; use crate::message::{MessageId,TaggedMessage,VerificationError,Relevance};
use crate::state::{StateType,StateValue,StatePath,StatePermission,StatePermissionKey}; use crate::state::{self,StateType,StateValue,StatePath,StatePermission,StatePermissionKey};
use crate::{RoomId,Group,Role,User,GroupPower}; use crate::{RoomId,Group,Role,User,GroupPower,ServerAddr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::OffsetDateTime; use time::OffsetDateTime;
@@ -13,19 +13,19 @@ use time::OffsetDateTime;
#[derive(Serialize,Deserialize,Clone,Debug)] //#[derive(Serialize,Deserialize,Clone,Debug)]
/// This exists solely to allow the DB to finish tagging the message. It is every field of a ///// 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 ///// message except for the ID which can be created using a Postgres serial type
pub struct InsertableClientMessage { //pub struct InsertableClientMessage {
pub message: ClientMessage, // pub message: ClientMessage,
#[serde(with="time::serde::timestamp")] // #[serde(with="time::serde::timestamp")]
pub client_timestamp: OffsetDateTime, // pub client_timestamp: OffsetDateTime,
#[serde(with="time::serde::timestamp")] // #[serde(with="time::serde::timestamp")]
pub server_timestamp: OffsetDateTime, // pub server_timestamp: OffsetDateTime,
#[serde(with = "serde_bytes")] // #[serde(with = "serde_bytes")]
pub signature: Box<[u8]>, // pub signature: Box<[u8]>,
pub user: User // pub user: User
} //}
#[derive(Serialize,Deserialize,Clone,Debug)] #[derive(Serialize,Deserialize,Clone,Debug)]
@@ -39,17 +39,26 @@ pub struct SignedClientMessage {
// Can I reserialize the message and check the signature that way?? // 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 // 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 // bytes using serde. I'll write something better at some point
//
// Should this be optional?
#[serde(with = "serde_bytes")] #[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 { impl SignedClientMessage {
pub fn tag(self, username: User) -> InsertableClientMessage { pub fn tag(self, username: User, servername: ServerAddr) -> TaggedMessage {
InsertableClientMessage { TaggedMessage {
message: self.message, message: self.message,
client_timestamp: self.timestamp, client_timestamp: self.timestamp,
server_timestamp: OffsetDateTime::now_utc(), server_timestamp: OffsetDateTime::now_utc(),
signature: self.signature, signature: self.signature,
user: username, user: username,
target: self.target.unwrap_or(servername)
} }
} }
pub fn verify(&self) -> Result<bool,VerificationError> { pub fn verify(&self) -> Result<bool,VerificationError> {
@@ -59,7 +68,6 @@ impl SignedClientMessage {
#[derive(Serialize,Deserialize,Clone,Debug)] #[derive(Serialize,Deserialize,Clone,Debug)]
pub enum ClientMessage { pub enum ClientMessage {
Auth{ Auth{
username: String, username: String,
password: String password: String
@@ -79,7 +87,13 @@ pub enum ClientMessage {
// Should it be one message type or mutiple? // Should it be one message type or mutiple?
Message { Message {
body: String, 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 // Private message/invite mechanism
MessagePost { MessagePost {
@@ -100,8 +114,8 @@ pub enum ClientMessage {
StateCreate { StateCreate {
room_id: RoomId, room_id: RoomId,
path: StatePath, path: StatePath,
ty: StateType, content: StateValue,
permissions: Option<Vec<StatePermission>> permissions: Option<state::PermissionTable>
}, },
StateWrite { StateWrite {
room_id: RoomId, room_id: RoomId,
@@ -127,9 +141,17 @@ pub enum ClientMessage {
path: StatePath path: StatePath
}, },
PermissionAdd { PermissionAdd {
room_id: RoomId,
path: StatePath,
permission: StatePermission permission: StatePermission
}, },
PermissionRead {
room_id: RoomId,
path: StatePath,
},
PermissionDelete { PermissionDelete {
room_id: RoomId,
path: StatePath,
permission: StatePermissionKey permission: StatePermissionKey
}, },
// Groups really should have a way to add permissions by user // Groups really should have a way to add permissions by user
@@ -197,7 +219,10 @@ pub enum ClientMessage {
bytes: Vec<u8> bytes: Vec<u8>
}, },
// Join and subscribe // Join and subscribe
Join { RoomJoin {
room_id: RoomId,
},
RoomCreate {
room_id: RoomId, room_id: RoomId,
}, },
SubscribeMessages { SubscribeMessages {
@@ -213,11 +238,34 @@ pub enum ClientMessage {
} }
} }
impl 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> { pub fn get_relevance(&self) -> Option<Relevance> {
use ClientMessage::*; use ClientMessage::*;
Some(match self { Some(match self {
Message { Message {
body: _, body: _,
id: _,
room_id room_id
// These don't necessarily need cloned, it might make sense to make an owned // These don't necessarily need cloned, it might make sense to make an owned
// version of Relevance and a reference version of it // 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 { pub enum ServerError {
InvalidPermission, InvalidPermission,
// Server can specify the required challenge // Server can specify the required challenge
ChallengeInvitecode, ChallengeInviteCode,
NotAuthenticated, NotAuthenticated,
AlreadyAuthenticated, 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), Error(ServerError),
// Returned on read // Returned on read
State(StatePath,StateValue), State(StatePath,StateValue),
// Returned on permission read
StatePermission(StatePath,crate::state::PermissionTable),
// Returned on subscribe, forwards state change message to client // Returned on subscribe, forwards state change message to client
StateChange(TaggedMessage), StateChange(TaggedMessage),
OkMessage(MessageId), OkMessage(MessageId),
+3 -1
View File
@@ -50,10 +50,12 @@ pub enum ServerResponse {
Error(FederationError), Error(FederationError),
State(StateValue), State(StateValue),
Media(Uuid,#[serde(with = "serde_bytes")] Vec<u8>), Media(Uuid,#[serde(with = "serde_bytes")] Vec<u8>),
ValidGroups(User,Vec<StatePermissionKey>) ValidGroups(User,Vec<StatePermissionKey>),
Ok
} }
#[derive(Serialize,Deserialize)] #[derive(Serialize,Deserialize)]
pub enum FederationError { pub enum FederationError {
InvalidPermission, InvalidPermission,
ServerError(crate::client::ServerError)
} }
+11 -6
View File
@@ -8,22 +8,23 @@ pub mod message;
// Room coordinates and originating server // Room coordinates and originating server
#[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)] #[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
pub struct RoomId{ pub struct RoomId{
coordinates: Vec<i64>, pub coordinates: Vec<i64>,
server: Option<String> //pub server: Option<String>
} }
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)] #[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct User { pub struct User {
name: String, pub name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here // 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)] #[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct Group { pub struct Group {
name: String, pub name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here // 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 // Might want to standardize this, idk which powers would be useful though
@@ -36,3 +37,7 @@ pub struct GroupPower(String);
pub struct Role { pub struct Role {
name: String 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::RoomId;
use crate::client::ClientMessage; use crate::client::ClientMessage;
use crate::state::StatePath; use crate::state::StatePath;
use crate::User; use crate::{User,ServerAddr};
use time::OffsetDateTime; use time::OffsetDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -16,6 +16,7 @@ pub struct MessageId(u64);
#[derive(Serialize,Deserialize,Clone,Debug)] #[derive(Serialize,Deserialize,Clone,Debug)]
pub struct TaggedMessage { pub struct TaggedMessage {
// How do I return message IDs
pub message: ClientMessage, pub message: ClientMessage,
#[serde(with="time::serde::timestamp")] #[serde(with="time::serde::timestamp")]
pub client_timestamp: OffsetDateTime, pub client_timestamp: OffsetDateTime,
@@ -23,7 +24,8 @@ pub struct TaggedMessage {
pub server_timestamp: OffsetDateTime, pub server_timestamp: OffsetDateTime,
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
pub signature: Box<[u8]>, pub signature: Box<[u8]>,
pub user: User pub user: User,
pub target: ServerAddr
} }
impl TaggedMessage { impl TaggedMessage {
pub fn verify(&self) -> Result<bool,VerificationError> { pub fn verify(&self) -> Result<bool,VerificationError> {
@@ -42,6 +44,22 @@ pub enum Relevance {
Post(User) 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)] //#[derive(Serialize,Deserialize,Debug,Clone)]
//pub struct Message { //pub struct Message {
// body: String, // body: String,
+71 -7
View File
@@ -1,9 +1,25 @@
use serde::{Deserialize,Serialize}; use serde::{Deserialize,Serialize};
use std::collections::{HashMap,HashSet};
use crate::{User,Group,Role}; use crate::{User,Group,Role};
#[derive(Serialize,Deserialize,Clone,Debug)] #[derive(Serialize,Deserialize,Clone,Debug,PartialEq)]
pub enum StateValue { pub enum StateValue {
String(String), 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)] #[derive(Serialize,Deserialize,Debug,Clone)]
pub enum StateType { pub enum StateType {
@@ -18,20 +34,68 @@ pub enum StatePermissionKey {
User(User), User(User),
Group(Group), Group(Group),
Role(Group,Role), 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 //This is a string for now but could get compiled later into an actual regex
//should be able to match on name and server //should be able to match on name and server
UserRegex(String) UserRegex(String)
} }
#[derive(Serialize,Deserialize,Clone,Debug)] #[derive(Serialize,Deserialize,Clone,Debug,PartialEq,Eq)]
pub enum StatePermissionValue { pub enum StatePermissionValue {
None, None,
Read, Read,
ReadWrite, ReadWrite,
// Can only set value, does not allow appending // 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)] #[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
pub struct StatePath(Vec<String>); pub struct StatePath(pub Vec<String>);