From 9ffa37d7a788c8f899e4fdf295d118be5594599c Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 17 May 2024 13:37:34 +0200 Subject: [PATCH] [ADD/UNTESTED] Start implementing event sending This is NOT perfect. This *will* need changes before it gets pushed. --- src/Room.c | 204 ++++++++++++++++++++++++++++++++++++++++----- src/include/Room.h | 13 +++ 2 files changed, 194 insertions(+), 23 deletions(-) diff --git a/src/Room.c b/src/Room.c index afdd9ae..efdf526 100644 --- a/src/Room.c +++ b/src/Room.c @@ -52,7 +52,11 @@ struct Room { Db *db; - DbRef *ref; + + DbRef *state_ref; + DbRef *leaves_ref; /* Reference to the leaf list */ + + ServerPart creator; char *id; int version; @@ -70,8 +74,9 @@ Room * RoomCreate(Db * db, User *user, RoomCreateRequest * req) { Room *room; - char *version_string; + char *version_string, *full_creator; int version_num = 1; + HashMap *json; if (!db || !req || !user) { return NULL; @@ -88,9 +93,17 @@ RoomCreate(Db * db, User *user, RoomCreateRequest * req) room = Malloc(sizeof(Room)); room->db = db; room->id = GenerateRoomId(); /* TODO: Check config. */ + room->creator.hostname = NULL; + room->creator.port = NULL; room->version = version_num; - room->ref = DbCreate(db, 3, "rooms", room->id, "state"); + room->state_ref = DbCreate(db, 3, "rooms", room->id, "state"); + room->leaves_ref = DbCreate(db, 3, "rooms", room->id, "leaves"); + json = DbJson(room->leaves_ref); + JsonSet(json, JsonValueArray(ArrayCreate()), 1, "leaves"); + full_creator = ParserRecomposeServerPart(room->creator); + JsonSet(json, JsonValueString(full_creator), 1, "creator"); + Free(full_creator); /* TODO: Populate room with information */ return room; @@ -99,7 +112,8 @@ RoomCreate(Db * db, User *user, RoomCreateRequest * req) Room * RoomLock(Db * db, char *id) { - DbRef *ref; + DbRef *state_ref, *leaves_ref; + HashMap *json; Room *room; if (!db || !id) @@ -107,9 +121,10 @@ RoomLock(Db * db, char *id) return NULL; } - ref = DbLock(db, 3, "rooms", id, "state"); + state_ref = DbLock(db, 3, "rooms", id, "state"); + leaves_ref = DbLock(db, 3, "rooms", id, "leaves"); - if (!ref) + if (!state_ref || !leaves_ref) { return NULL; } @@ -117,14 +132,21 @@ RoomLock(Db * db, char *id) room = Malloc(sizeof(Room)); if (!room) { - DbUnlock(db, ref); + DbUnlock(db, state_ref); + DbUnlock(db, leaves_ref); return NULL; } room->db = db; - room->ref = ref; + room->state_ref = state_ref; + room->leaves_ref = leaves_ref; room->id = StrDuplicate(id); + json = DbJson(room->leaves_ref); + ParseServerPart( + JsonValueAsString(JsonGet(json, 1, "creator")), + &room->creator); + return room; } @@ -132,7 +154,8 @@ int RoomUnlock(Room * room) { Db *db; - DbRef *ref; + DbRef *state_ref; + DbRef *leaves_ref; if (!room) { @@ -140,12 +163,16 @@ RoomUnlock(Room * room) } db = room->db; - ref = room->ref; + state_ref = room->state_ref; + leaves_ref = room->leaves_ref; Free(room->id); Free(room); - return DbUnlock(db, ref); + ServerPartFree(room->creator); + + return DbUnlock(db, state_ref) && + DbUnlock(db, leaves_ref); } char * @@ -171,7 +198,7 @@ RoomStateGet(Room * room) /* TODO: Consider caching the deserialised result, as doing that on a * large state would probably eat up a lot of time! */ - database_state = DbJson(room->ref); + database_state = DbJson(room->state_ref); return StateDeserialise(database_state); } HashMap * @@ -345,9 +372,12 @@ finish: } static bool -PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu) +PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu, ServerPart serv) { char *unused; + Array *prev_events; + size_t i; + CommonID cid; if (PduV1FromJson(event, pdu, &unused)) { return true; @@ -362,9 +392,32 @@ PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu) pdu->origin_server_ts = UtilTsMillis(); pdu->content = JsonDuplicate(event); /* Copy the original JSON data */ + /* Create a random event ID for this PDU. + * TODO: Optionally verify whenever it's already used, but that + * would be unlikely considering the lengths of event IDs. */ + cid.sigil = '$'; + cid.local = StrRandom(32); + cid.server.hostname = StrDuplicate(serv.hostname); + cid.server.port = serv.port ? StrDuplicate(serv.port) : NULL; + pdu->event_id = ParserRecomposeCommonID(cid); + CommonIDFree(cid); + + + /* Fill prev_events with actual event data. + * Note that we don't actually *clear* out these from our list, as + * that should be done later. */ + pdu->prev_events = ArrayCreate(); + prev_events = RoomPrevEventsGet(room); + for (i = 0; i < ArraySize(prev_events); i++) + { + HashMap *event = ArrayGet(prev_events, i); + char *event_id = JsonValueAsString(JsonGet(event, 1, "event_id")); + + ArrayAdd(pdu->prev_events, JsonValueString(event_id)); + } + /* TODO: Signature and alldat. */ - (void) room; return false; } static bool @@ -762,23 +815,24 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, HashMap *state) Array *prev = pdu.prev_events; if (ArraySize(prev) == 1) { - char *prev_id = ArrayGet(prev, 0); + /* Interperet prev properly, as a list of JsonObjects. */ + char *prev_id = JsonValueAsString(ArrayGet(prev, 0)); char *ignored; - HashMap *prev = RoomEventFetch(room, prev_id); + HashMap *prev_event = RoomEventFetch(room, prev_id); PduV1 prev_pdu; - if (prev && PduV1FromJson(prev, &prev_pdu, &ignored)) + if (prev && PduV1FromJson(prev_event, &prev_pdu, &ignored)) { if (StrEquals(prev_pdu.type, "m.room.create") && StrEquals(prev_pdu.sender, pdu.state_key)) { PduV1Free(&prev_pdu); - JsonFree(prev); + JsonFree(prev_event); return true; } PduV1Free(&prev_pdu); } - JsonFree(prev); + JsonFree(prev_event); } /* Step 5.2.2: If the sender does not match state_key, reject. */ @@ -1168,15 +1222,56 @@ RoomHashEventV1(PduV1 pdu) JsonFree(json); return sha; } +static bool +EventFits(HashMap *pdu) +{ + int size = CanonicalJsonEncode(pdu, NULL); + JsonValue *key; + + /* Main PDU length is 65536 bytes */ + if (size > 65536) + { + return false; + } +#define VerifyKey(k,s) do \ + { \ + if ((key = JsonGet(pdu, 1, k)) && \ + (strlen(JsonValueAsString(key)) > s)) \ + { \ + return false; \ + } \ + } \ + while (0) + VerifyKey("sender", 255); + VerifyKey("room_id", 255); + VerifyKey("state_key", 255); + VerifyKey("type", 255); + VerifyKey("event_id", 255); + + return true; +#undef VerifyKey +} +static bool +EventFitsV1(PduV1 pdu) +{ + HashMap *hm; + bool ret; + + hm = PduV1ToJson(&pdu); + ret = EventFits(hm); + + JsonFree(hm); + return ret; +} static HashMap * RoomEventSendV1(Room * room, HashMap * event) { PduV1 pdu = { 0 }; HashMap *pdu_object = NULL; - bool client_event; + bool client_event, valid = false; HashMap *state = NULL; - client_event = PopulateEventV1(room, event, &pdu); + client_event = PopulateEventV1(room, event, &pdu, RoomGetCreator(room)); pdu_object = PduV1ToJson(&pdu); state = StateResolve(room, pdu_object); /* Compute the state @@ -1239,7 +1334,23 @@ RoomEventSendV1(Room * room, HashMap * event) pdu.hashes.sha256 = RoomHashEventV1(pdu); #undef AddState } - /* TODO: Do a size check! */ + /* TODO: For PDU events, we should verify their hashes. */ + if (!EventFitsV1(pdu)) + { + /* Reject this event as it is too large. */ + goto finish; + } + if (!RoomAuthoriseEventV1(room, pdu, state)) + { + /* Reject this event as the current state does not allow it. + * TODO: Make the auth check function return a string showing the + * error status to the user. */ + goto finish; + } + RoomAddEventV1(room, pdu); + valid = true; + +finish: if (state) { JsonFree(state); @@ -1247,9 +1358,14 @@ RoomEventSendV1(Room * room, HashMap * event) if (pdu_object) { JsonFree(pdu_object); + pdu_object = NULL; + } + if (valid) + { + pdu_object = PduV1ToJson(&pdu); } PduV1Free(&pdu); - return NULL; + return pdu_object; } static HashMap * RoomEventSendV3(Room * room, HashMap * event) @@ -1322,3 +1438,45 @@ finish: Free(safe_id); return ret; } + +Array * +RoomPrevEventsGet(Room *room) +{ + HashMap *json; + if (!room) + { + return NULL; + } + json = DbJson(room->leaves_ref); + return JsonValueAsArray(JsonGet(json, 1, "leaves")); +} +ServerPart +RoomGetCreator(Room *room) +{ + ServerPart ret = { 0 }; + if (!room) + { + return ret; + } + return room->creator; +} +bool RoomAddEventV1(Room *room, PduV1 pdu) +{ + DbRef *event_ref; + char *safe_id; + if (!room || room->version >= 3) + { + return false; + } + /* TODO: Update leaf events. */ + safe_id = CreateSafeID(pdu.event_id); + event_ref = DbLock(room->db, 4, "rooms", room->id, "events", safe_id); + Free(safe_id); + + DbJsonSet(event_ref, PduV1ToJson(&pdu)); + DbUnlock(room->db, event_ref); + + /* TODO: Store DAG relationships, somehow. */ + + return true; +} diff --git a/src/include/Room.h b/src/include/Room.h index 390ca6a..94c6794 100644 --- a/src/include/Room.h +++ b/src/include/Room.h @@ -43,6 +43,7 @@ #include #include +#include #include /** @@ -151,4 +152,16 @@ extern HashMap * RoomEventFetch(Room *, char *); */ extern bool RoomAuthoriseEventV1(Room *, PduV1, HashMap *); +/** + * Gets the room's creator as a ServerPart. This value should + * not be freed, as it lives alongside the room itself + */ +extern ServerPart RoomGetCreator(Room *); + +/** + * Puts a PDUv1 into the event list, while updating the leaf + * list. + */ +extern bool RoomAddEventV1(Room *, PduV1); + #endif /* TELODENDRIA_ROOM_H */