Initial commit

This commit is contained in:
2026-05-15 21:42:15 -07:00
commit ba35808572
8 changed files with 965 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
use uuid::Uuid;
use crate::message::{MessageId,Message};
use crate::state::{StateType,StateValue,StatePath,StatePermission,StatePermissionKey};
use crate::{RoomId,Group,Role,User,GroupPower};
use serde::{Deserialize, Serialize};
//StatePath [String]
//octal is fine for now
//maybe do ACL late???
//StatePermissions (u8,u8,u8)
//RoomId [i64]
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct TaggedClientMessage {
message: ClientMessage,
#[serde(with = "serde_bytes")]
signature: Box<[u8]>,
user: User
}
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct SignedClientMessage {
message: ClientMessage,
// Should I enforce this being a ecdsa signature?
#[serde(with = "serde_bytes")]
signature: Box<[u8]>
}
#[derive(Serialize,Deserialize,Clone,Debug)]
pub enum ClientMessage {
Auth{
username: String,
password: String
},
// Maybe ask for email too? Or a potential invite code
UserCreate {
username: String,
password: String,
},
// Used to require accounts to complete some kind of challenge. Simplest
// is giving a password/invite code to create an account or join a room
ChallengeAnswer {
response: String
},
// TODO I still dont have key management commands
// Should it be one message type or mutiple?
Message {
body: String,
},
// Private message/invite mechanism
MessagePost {
body: String,
user: User
},
// Replace the body of the message with a new one
MessageEdit {
body: String,
id: MessageId
},
MessageDelete {
id: MessageId
},
// State Actions
StateCreate {
path: StatePath,
ty: StateType,
permissions: Option<Vec<StatePermission>>
},
StateWrite {
path: StatePath,
content: StateValue
},
StateDelete {
path: StatePath,
},
StateAppend {
path: StatePath,
content: StateValue
},
StateMove {
path: StatePath,
target: StatePath,
},
StateRead {
path: StatePath
},
PermissionAdd {
permission: StatePermission
},
PermissionDelete {
permission: StatePermissionKey
},
// Groups really should have a way to add permissions by user
// specifically for who can join or invite others
//
// Maybe make a group -> role -> member hierarchy?
//
//
// Could always do this through a bot that owns a group??
GroupCreate {
group: Group,
users: Vec<User>
},
// Only the creator of a group or a server admin can delete groups
GroupDelete {
group: Group
},
// Only the creator of a group or a server admin can delete groups
// same with adding, though there should be a way to add group officers
// at some point
GroupUserAdd {
group: Group,
users: Vec<User>
},
GroupUserRemove {
group: Group,
users: Vec<User>
},
GroupRoleCreate {
group: Group,
role: Role
},
GroupRoleDelete {
group: Group,
role: Role
},
GroupRoleUserAdd {
group: Group,
role: Role,
users: Vec<User>
},
GroupRoleUserRemove {
group: Group,
role: Role,
users: Vec<User>
},
// Should work like discord roles
// Can control who can invite to the group
// Can be used with permissions to make rooms that are private for individual roles
GroupRolePowerAdd {
group: Group,
role: Role,
power: GroupPower
},
GroupRolePowerRemove {
group: Group,
role: Role,
power: GroupPower
},
// Returns an ID to use for message sending
// The server can potentially use the current username to associate media uploads
// with users
MediaUpload {
#[serde(with = "serde_bytes")]
bytes: Vec<u8>
},
// Join and subscribe
Join {
room_id: RoomId,
},
SubscribeMessages {
room_id: RoomId,
},
SubscribeState {
room_id: RoomId,
state: StatePath
},
FetchMessages {
count: u64,
end: MessageId,
}
}
#[derive(Serialize,Deserialize)]
enum ServerError {
InvalidPermission,
// Server can specify the required challenge
ChallengeInvitecode,
}
#[derive(Serialize,Deserialize)]
enum ServerMessage {
// Returned on fetch or naturally from subscribe
Messages(Vec<Message>),
MediaUploaded(Uuid),
Error(ServerError),
// Returned both on read and from subscribe
State(StateValue),
Ok,
}
+60
View File
@@ -0,0 +1,60 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::state::{StatePath,StateValue,StatePermissionKey};
use crate::message::Message;
use crate::{RoomId,User};
use crate::client::TaggedClientMessage;
// How do remote subscriptions work?
// Server pushes subscription to remote
// remote keeps track of list, filters on message broadcast
// forwards message
// local server sends over local channel
// local server checks for message ack
// if no acks for N times then subscription is cancelled
// NOTE: should acks be sent by the client??
// NOTE 2: Should they be sent by the server to avoid traffic multiplication attacks?
#[derive(Serialize,Deserialize)]
pub enum ServerRequest {
SubscribeMessages {
room_id: RoomId,
user: User,
},
UnsubscribeMessages {
room_id: RoomId
},
SubscribeState {
room_id: RoomId,
user: User,
state: StatePath
},
UnsubscribeState {
room_id: RoomId,
state: StatePath
},
FetchRemoteMedia {
id: Uuid
},
// Check to see if a user is in any of the given groups on the remote server
CheckGroups {
user: User,
groups: Vec<StatePermissionKey>
},
//NOTE: It doesn't make sense to forward every kind of message
// But duplicating work here doesn't make sense
ForwardedMessage(TaggedClientMessage)
}
#[derive(Serialize,Deserialize)]
pub enum ServerResponse {
Messages(Vec<Message>),
Error(FederationError),
State(StateValue),
Media(Uuid,#[serde(with = "serde_bytes")] Vec<u8>),
ValidGroups(User,Vec<StatePermissionKey>)
}
#[derive(Serialize,Deserialize)]
pub enum FederationError {
InvalidPermission,
}
+38
View File
@@ -0,0 +1,38 @@
use serde::{Deserialize,Serialize};
pub mod client;
pub mod federation;
pub mod state;
pub mod message;
// Room coordinates and originating server
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct RoomId{
coordinates: Vec<i64>,
server: Option<String>
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct User {
name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here
server: String
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct Group {
name: String,
// Should be a fqdn probably, dunno if its worth enforcing that here
server: String
}
// Might want to standardize this, idk which powers would be useful though
// should be things like who can manage roles, group members, and more specific
// stuff like who can post images or join different rooms
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct GroupPower(String);
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub struct Role {
name: String
}
+15
View File
@@ -0,0 +1,15 @@
use crate::User;
use time::OffsetDateTime;
use serde::{Deserialize, Serialize};
//Monotonically increasing messageID
#[derive(Serialize,Deserialize,Debug,Clone)]
pub struct MessageId(u64);
#[derive(Serialize,Deserialize,Debug,Clone)]
pub struct Message {
body: String,
signature: Box<[u8]>,
sender: User,
timestamp: OffsetDateTime
}
+37
View File
@@ -0,0 +1,37 @@
use serde::{Deserialize,Serialize};
use crate::{User,Group,Role};
#[derive(Serialize,Deserialize,Clone,Debug)]
pub enum StateValue {
String(String),
Binary(Vec<u8>)
}
#[derive(Serialize,Deserialize,Debug,Clone)]
pub enum StateType {
Directory,
String,
Binary
}
#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)]
pub enum StatePermissionKey {
User(User),
Group(Group),
Role(Group,Role),
//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)]
pub enum StatePermissionValue {
None,
Read,
ReadWrite,
// Can only set value, does not allow appending
Write
}
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct StatePermission(StatePermissionKey,StatePermissionValue);
#[derive(Serialize,Deserialize,Clone,Debug)]
pub struct StatePath(Vec<String>);