[MOD] Revamping the state a bit
Some checks are pending
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run

This commit is contained in:
LDA 2024-08-21 11:02:28 +02:00
parent 15f4de2a25
commit b719a16d9f
5 changed files with 261 additions and 178 deletions

View file

@ -35,6 +35,7 @@
#include <Cytoplasm/Base64.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Db.h>
#include <Schema/RoomCreateRequest.h>
@ -430,7 +431,7 @@ RoomVersionGet(Room * room)
return room ? room->version : 0;
}
HashMap *
State *
RoomStateGet(Room * room)
{
HashMap *database_state;
@ -446,10 +447,11 @@ RoomStateGet(Room * room)
database_state = DbJson(room->state_ref);
return StateDeserialise(database_state);
}
HashMap *
State *
RoomStateGetID(Room * room, char *event_id)
{
HashMap *event, *state;
HashMap *event;
State *state;
if (!room || !event_id)
{
return NULL;
@ -490,7 +492,7 @@ RoomStateGetID(Room * room, char *event_id)
} \
return ret
static bool
RoomIsJoinRule(Room * room, HashMap *state, char *jr)
RoomIsJoinRule(Room * room, State *state, char *jr)
{
HashMap *joinrule = NULL;
char *id_joinrule = NULL;
@ -510,7 +512,7 @@ finish:
}
/* Verifies if user has a specific membership before [e_id] in the room. */
static bool
RoomUserHasMembership(Room * room, HashMap *state, char *user, char *mbr)
RoomUserHasMembership(Room * room, State *state, char *user, char *mbr)
{
HashMap *membership = NULL;
char *id_membership = NULL;
@ -559,7 +561,7 @@ ParsePL(JsonValue *v, int64_t def)
}
/* Computes the smallest PL needed to do something somewhere */
static int64_t
RoomMinPL(Room * room, HashMap *state, char *type, char *act)
RoomMinPL(Room * room, State *state, char *type, char *act)
{
HashMap *pl = NULL;
JsonValue *val;
@ -599,7 +601,7 @@ finish:
/* Finds the power level of an user before [e_id] was sent. */
/* TODO: The creator should have PL100 by default. */
static int64_t
RoomUserPL(Room * room, HashMap *state, char *user)
RoomUserPL(Room * room, State *state, char *user)
{
HashMap *pl = NULL;
char *id_pl;
@ -919,7 +921,7 @@ AuthoriseAliasV1(PduV1 pdu)
return true;
}
static bool
AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, HashMap *state)
AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state)
{
int64_t invite_level;
int64_t pdu_level;
@ -1018,7 +1020,7 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, HashMap *state)
return false;
}
static bool
AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, HashMap *state)
AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state)
{
int64_t ban_level = RoomMinPL(room, state, NULL, "ban");
int64_t kick_level = RoomMinPL(room, state, NULL, "kick");
@ -1061,7 +1063,7 @@ AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, HashMap *state)
return false;
}
static bool
AuthorizeBanMembershipV1(Room * room, PduV1 pdu, HashMap *state)
AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state)
{
int64_t ban_pl, pdu_pl, target_pl;
@ -1086,7 +1088,7 @@ AuthorizeBanMembershipV1(Room * room, PduV1 pdu, HashMap *state)
return false;
}
static bool
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, HashMap *state)
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state)
{
/* Step 5.2.1: If the only previous event is an m.room.create and the
* state_key is the creator, allow. */
@ -1144,7 +1146,7 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, HashMap *state)
return false;
}
static bool
AuthoriseMemberV1(Room * room, PduV1 pdu, HashMap *state)
AuthoriseMemberV1(Room * room, PduV1 pdu, State *state)
{
JsonValue *membership;
char *membership_str;
@ -1186,7 +1188,7 @@ AuthoriseMemberV1(Room * room, PduV1 pdu, HashMap *state)
#undef JumpIfMembership
}
static bool
AuthorisePowerLevelsV1(Room * room, PduV1 pdu, HashMap *state)
AuthorisePowerLevelsV1(Room * room, PduV1 pdu, State *state)
{
/* Step 10.1: If the users property in content is not an object with
* keys that are valid user IDs with values that are integers
@ -1362,7 +1364,7 @@ AuthorisePowerLevelsV1(Room * room, PduV1 pdu, HashMap *state)
return true;
}
bool
RoomAuthoriseEventV1(Room * room, PduV1 pdu, HashMap *state)
RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state)
{
HashMap *create_event;
char *create_event_id;
@ -1563,12 +1565,13 @@ RoomEventSendV1(Room * room, HashMap * event)
PduV1 pdu = { 0 };
HashMap *pdu_object = NULL;
bool client_event, valid = false;
HashMap *state = NULL;
State *state = NULL;
client_event = !PopulateEventV1(room, event, &pdu, RoomGetCreator(room));
pdu_object = PduV1ToJson(&pdu);
state = StateResolve(room, pdu_object);
if (client_event)
{
char *ev_id;
@ -1851,7 +1854,7 @@ RoomAddEventV1(Room *room, PduV1 pdu)
}
{
HashMap *state;
State *state;
char *type, *state_key, *event_id;
pdu_json = PduV1ToJson(&pdu);
@ -2133,7 +2136,7 @@ RoomGetDB(Room *room)
bool
RoomContainsUser(Room *room, char *user)
{
HashMap *state;
State *state;
bool ret;
if (!room || !user)
{
@ -2151,7 +2154,7 @@ RoomContainsUser(Room *room, char *user)
bool
RoomCanJoin(Room *room, char *user)
{
HashMap *state;
State *state;
HashMap *joinRule = NULL;
char *joinRuleV;
bool ret;

View file

@ -184,7 +184,7 @@ ROUTE_IMPL(RouteSync, path, argp)
char *firstEvent = NULL;
char *type, *key, *id;
HashMap *joinedObj;
HashMap *state;
State *state;
Array *el;
size_t j;
Room *r;

View file

@ -27,6 +27,7 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Base64.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
@ -36,6 +37,21 @@
#include <Event.h>
#include <Room.h>
struct State {
/* A hashmap of event IDs for a state */
HashMap *table;
};
static State *
InitialiseState(void)
{
State *ret = Malloc(sizeof(*ret));
ret->table = HashMapCreate();
return ret;
}
int
V1Cmp(void *a, void *b)
{
@ -76,30 +92,60 @@ V1Cmp(void *a, void *b)
return ret;
}
}
static HashMap *
StateResolveV1(Room * room, Array * states)
static char *
EncodeStateTuple(char *type, char *stateKey)
{
size_t typeLen = type ? strlen(type) : 0;
size_t stateKeyLen = stateKey ? strlen(stateKey) : 0;
size_t length = typeLen + 1 + stateKeyLen + 1;
char *tuple = Malloc(length);
char *base64;
memset(tuple, '\0', length);
memcpy(tuple, type, typeLen);
memcpy(tuple + typeLen + 1, stateKey, stateKeyLen);
base64 = Base64Encode((const char *) tuple, length);
Free(tuple);
return base64;
}
static void
DecodeStateTuple(char *base64, char **key, char **val)
{
size_t base64Size = base64 ? strlen(base64) : 0;
char *decodeBuffer = NULL;
if (!base64 || !key || !val)
{
return;
}
decodeBuffer = Base64Decode((const char *) base64, base64Size);
*key = StrDuplicate(decodeBuffer);
*val = StrDuplicate(decodeBuffer + strlen(decodeBuffer) + 1);
Free(decodeBuffer);
}
static void
BuildBaseAndConflictV1(Room *room, Array *states, State *R, HashMap *conflicts)
{
HashMap *R = HashMapCreate();
HashMap *conflicts = HashMapCreate();
Array *events = NULL, *types = NULL, *conflicting = NULL;
size_t i;
ssize_t j;
char *type, *key, *event_id;
char *type = NULL, *key = NULL;
for (i = 0; i < ArraySize(states); i++)
{
HashMap *state = ArrayGet(states, i);
char *tuple;
while (HashMapIterate(state, &tuple, (void **) &event_id))
char *event_id;
State *state = ArrayGet(states, i);
while (StateIterate(state, &type, &key, (void **) &event_id))
{
if (HashMapGet(R, tuple))
char *tuple = EncodeStateTuple(type, key);
if (StateGet(R, type, key) || HashMapGet(conflicts, tuple))
{
Array *arr;
HashMap *hm;
/* Conflicts! */
HashMapDelete(R, tuple);
StateSet(R, type, key, NULL);
arr = HashMapGet(conflicts, tuple);
if (!arr)
{
@ -112,30 +158,33 @@ StateResolveV1(Room * room, Array * states)
else
{
/* Add to R */
HashMapSet(R, tuple, StrDuplicate(event_id));
StateSet(R, type, key, event_id);
}
Free(tuple);
Free(type);
Free(key);
}
}
/* R and conflicts are now configured */
types = ArrayCreate();
ArrayAdd(types, "m.room.power_levels");
ArrayAdd(types, "m.room.join_rules");
ArrayAdd(types, "m.room.member");
for (i = 0; i < ArraySize(types); i++)
{
char *t = ArrayGet(types, i);
}
static void
FixoutConflictV1(char *t, Room *room, State *R, HashMap *conflicts)
{
HashMap *first;
Array *state_keys;
Array *state_keys, *conflicting, *events;
char *tuple;
size_t i;
events = ArrayCreate();
while (StateIterate(conflicts, &type, &key, (void **) &conflicting))
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
{
char *type, *key;
DecodeStateTuple(tuple, &type, &key);
if (StrEquals(type, t))
{
for (j = 0; j < (ssize_t) ArraySize(conflicting); j++)
for (i = 0; i < ArraySize(conflicting); i++)
{
HashMap *event = ArrayGet(conflicting, j);
HashMap *event = ArrayGet(conflicting, i);
ArrayAdd(events, event);
}
}
@ -149,12 +198,13 @@ StateResolveV1(Room * room, Array * states)
R,
JsonValueAsString(JsonGet(first, 1, "type")),
JsonValueAsString(JsonGet(first, 1, "state_key")),
JsonValueAsString(JsonGet(first, 1, "event_id")));
JsonValueAsString(JsonGet(first, 1, "event_id"))
);
JsonFree(first);
for (j = 0; j < (ssize_t) ArraySize(events); j++)
for (i = 0; i < ArraySize(events); i++)
{
HashMap *event = ArrayGet(events, j);
HashMap *event = ArrayGet(events, i);
PduV1 pdu;
char *msg;
@ -174,36 +224,70 @@ StateResolveV1(Room * room, Array * states)
JsonFree(event);
}
ArrayFree(events);
/* Delete all elements within a key. */
state_keys = ArrayCreate();
while (StateIterate(conflicts, &type, &key, (void **) &event_id))
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
{
char *type, *key;
DecodeStateTuple(tuple, &type, &key);
if (StrEquals(type, t))
{
ArrayAdd(state_keys, key);
}
else
{
Free(key);
}
Free(type);
}
for (j = 0; j < (ssize_t) ArraySize(state_keys); j++)
for (i = 0; i < ArraySize(state_keys); i++)
{
char *state_key = ArrayGet(state_keys, j);
StateSet(conflicts, t, state_key, NULL);
char *state_key = ArrayGet(state_keys, i);
char *tuple = EncodeStateTuple(t, state_key);
Array *conflict = HashMapDelete(conflicts, tuple);
/* All of the other values are already freed */
ArrayFree(conflict);
Free(state_key);
Free(tuple);
}
ArrayFree(state_keys);
}
ArrayFree(types);
}
while (StateIterate(conflicts, &type, &key, (void **) &conflicting))
static State *
StateResolveV1(Room * room, Array * states)
{
State *R = InitialiseState();
HashMap *conflicts = HashMapCreate();
Array *events = NULL, *conflicting = NULL;
char *type, *key, *tuple;
BuildBaseAndConflictV1(room, states, R, conflicts);
/* R and conflicts are now configured */
FixoutConflictV1("m.room.power_levels", room, R, conflicts);
FixoutConflictV1("m.room.join_rules", room, R, conflicts);
FixoutConflictV1("m.room.member", room, R, conflicts);
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
{
ssize_t i;
DecodeStateTuple(tuple, &type, &key);
ArraySort(conflicting, V1Cmp);
for (j = ArraySize(conflicting) - 1; j >= 0; j--)
for (i = ArraySize(conflicting) - 1; i >= 0; i--)
{
HashMap *event = ArrayGet(events, j);
PduV1 pdu;
HashMap *event = ArrayGet(events, i);
PduV1 pdu = { 0 };
char *msg;
PduV1FromJson(event, &pdu, &msg);
if (!PduV1FromJson(event, &pdu, &msg))
{
continue;
}
if (RoomAuthoriseEventV1(room, pdu, R))
{
@ -217,8 +301,10 @@ StateResolveV1(Room * room, Array * states)
Free(type);
Free(key);
}
while (HashMapIterate(conflicts, &type, (void **) &conflicting))
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
{
size_t i;
for (i = 0; i < ArraySize(conflicting); i++)
{
JsonFree(ArrayGet(conflicting, i));
@ -230,14 +316,14 @@ StateResolveV1(Room * room, Array * states)
return R;
}
static HashMap *
static State *
StateResolveV2(Array * states)
{
(void) states;
return NULL;
}
static HashMap *
static State *
StateFromPrevs(Room *room, Array *states)
{
switch (RoomVersionGet(room))
@ -249,7 +335,7 @@ StateFromPrevs(Room *room, Array *states)
}
}
HashMap *
State *
StateResolve(Room * room, HashMap * event)
{
Array *states;
@ -257,7 +343,7 @@ StateResolve(Room * room, HashMap * event)
Array *prevEvents;
HashMap *ret_state;
State *ret_state;
char *room_id, *event_id;
@ -300,7 +386,7 @@ StateResolve(Room * room, HashMap * event)
{
HashMap *prevEvent =
RoomEventFetch(room, JsonValueAsString(ArrayGet(prevEvents, i)));
HashMap *state = StateResolve(room, prevEvent);
State *state = StateResolve(room, prevEvent);
if (HashMapGet(prevEvent, "state_key"))
{
@ -308,7 +394,8 @@ StateResolve(Room * room, HashMap * event)
state,
JsonValueAsString(HashMapGet(prevEvent, "type")),
JsonValueAsString(HashMapGet(prevEvent, "state_key")),
JsonValueAsString(HashMapGet(prevEvent, "event_id")));
JsonValueAsString(HashMapGet(prevEvent, "event_id"))
);
}
ArrayAdd(states, state);
@ -319,7 +406,7 @@ StateResolve(Room * room, HashMap * event)
for (i = 0; i < ArraySize(states); i++)
{
HashMap *state = ArrayGet(states, i);
State *state = ArrayGet(states, i);
StateFree(state);
}
ArrayFree(states);
@ -335,13 +422,13 @@ StateResolve(Room * room, HashMap * event)
return ret_state;
}
HashMap *
State *
StateCurrent(Room *room)
{
Array *prevEvents;
Array *states;
size_t i;
HashMap *ret;
State *ret;
if (!room)
{
return NULL;
@ -353,8 +440,7 @@ StateCurrent(Room *room)
{
HashMap *event =
JsonValueAsObject(ArrayGet(prevEvents, i));
HashMap *state = StateResolve(room, event);
State *state = StateResolve(room, event);
if (HashMapGet(event, "state_key"))
{
@ -362,7 +448,8 @@ StateCurrent(Room *room)
state,
JsonValueAsString(HashMapGet(event, "type")),
JsonValueAsString(HashMapGet(event, "state_key")),
JsonValueAsString(HashMapGet(event, "event_id")));
JsonValueAsString(HashMapGet(event, "event_id"))
);
}
ArrayAdd(states, state);
@ -372,7 +459,7 @@ StateCurrent(Room *room)
for (i = 0; i < ArraySize(states); i++)
{
HashMap *state = ArrayGet(states, i);
State *state = ArrayGet(states, i);
StateFree(state);
}
ArrayFree(states);
@ -380,90 +467,79 @@ StateCurrent(Room *room)
return ret;
}
/* TODO: USING A ',' DELIMITED HASHMAP IS A _BAD_ IDEA!
* I'll need to change these functions, and some things in the room
* implementation, thankfully _that_ was abstracted away, so I don't have
* to think about it too much... */
bool
StateIterate(HashMap *state, char **type, char **key, void **event)
StateIterate(State *state, char **type, char **key, void **event)
{
char *tuple;
bool ret;
char *tuple;
if (!state || !type || !key || !event)
{
return false;
}
ret = HashMapIterate(state, &tuple, event);
ret = HashMapIterate(state->table, &tuple, event);
if (ret)
{
tuple = StrDuplicate(tuple);
*(strchr(tuple, ',')) = '\0';
*type = tuple;
*key = StrDuplicate(tuple + strlen(tuple) + 1);
DecodeStateTuple(tuple, type, key);
}
return ret;
}
char *
StateGet(HashMap *state, char *type, char *key)
StateGet(State *state, char *type, char *key)
{
char *full_string;
char *tableKey;
char *ret;
if (!state || !type || !key)
{
return NULL;
}
full_string = StrConcat(3, type, ",", key);
ret = HashMapGet(state, full_string);
Free(full_string);
tableKey = EncodeStateTuple(type, key);
ret = HashMapGet(state->table, tableKey);
Free(tableKey);
return ret;
}
void
StateSet(HashMap *state, char *type, char *key, char *event)
StateSet(State *state, char *type, char *key, char *event)
{
char *full_string, *old;
char *tableKey;
if (!state || !type || !key)
{
return;
}
full_string = StrConcat(3, type, ",", key);
old = HashMapDelete(state, full_string);
if (old)
{
Free(old);
}
tableKey = EncodeStateTuple(type, key);
Free(HashMapDelete(state->table, tableKey));
if (event)
{
HashMapSet(state, full_string, StrDuplicate(event));
HashMapSet(state->table, tableKey, StrDuplicate(event));
}
Free(full_string);
Free(tableKey);
}
void
StateFree(HashMap *state)
StateFree(State *state)
{
char *full;
char *event_id;
char *tuple;
char *eventID;
if (!state)
{
return;
}
while (HashMapIterate(state, &full, (void **) &event_id))
while (HashMapIterate(state->table, &tuple, (void **) &eventID))
{
Free(event_id);
Free(eventID);
}
HashMapFree(state);
HashMapFree(state->table);
Free(state);
}
HashMap *
State *
StateDeserialise(HashMap *json_state)
{
HashMap *raw_state;
State *raw_state;
char *state_type;
JsonValue *state_keys;
@ -473,7 +549,7 @@ StateDeserialise(HashMap *json_state)
return NULL;
}
raw_state = HashMapCreate();
raw_state = InitialiseState();
while (HashMapIterate(json_state, &state_type, (void **) &state_keys))
{
@ -484,18 +560,15 @@ StateDeserialise(HashMap *json_state)
while (HashMapIterate(state_keys_obj, &state_key, (void **) &event_id))
{
char *eid_string = JsonValueAsString(event_id);
char *key_name = StrConcat(3, state_type, ",", state_key);
HashMapSet(raw_state, key_name, StrDuplicate(eid_string));
Free(key_name);
StateSet(raw_state, state_type, state_key, eid_string);
}
}
return raw_state;
}
HashMap *
StateSerialise(HashMap *rawState)
StateSerialise(State *rawState)
{
HashMap *returned;
char *type, *key, *event;

View file

@ -51,6 +51,8 @@
*/
typedef struct Room Room;
#include <State.h>
/**
* Create a new room in the given database using the given
* RoomCreateRequest.
@ -104,14 +106,14 @@ extern int RoomVersionGet(Room *);
* which is used to select auth events on incoming
* client events.
*/
extern HashMap * RoomStateGet(Room *);
extern State * RoomStateGet(Room *);
/**
* Resolves the room's state before a specific point,
* (with the event hashmap taking priority),
* like
* .Fn RoomStateGet .
*/
extern HashMap * RoomStateGetID(Room *, char *);
extern State * RoomStateGetID(Room *, char *);
/**
* Get a list of the most recent events in the
@ -168,7 +170,7 @@ extern HashMap * RoomEventClientify(HashMap *);
* Verifies whenever an event(as a PDUv1) is
* authorised by a room.
*/
extern bool RoomAuthoriseEventV1(Room *, PduV1, HashMap *);
extern bool RoomAuthoriseEventV1(Room *, PduV1, State *);
/**
* Gets the room's creator as a ServerPart. This value should

View file

@ -37,17 +37,22 @@
#include <Cytoplasm/HashMap.h>
/**
* An opaque structure to hold information about the state.
*/
typedef struct State State;
#include <Room.h>
/**
* Retrieve the value of a state tuple.
*/
extern char *StateGet(HashMap *, char *, char *);
extern char *StateGet(State *, char *, char *);
/**
* Set a state tuple to a value.
*/
extern void StateSet(HashMap *, char *, char *, char *);
extern void StateSet(State *, char *, char *, char *);
/**
* Iterates through a statemap, with (type, key) -> event.
@ -56,22 +61,22 @@ extern void StateSet(HashMap *, char *, char *, char *);
* This function behaves like
* .Fn HashMapIterate .
*/
extern bool StateIterate(HashMap *, char **, char **, void **);
extern bool StateIterate(State *, char **, char **, void **);
/**
* Compute the room state before the specified event was sent.
*/
extern HashMap * StateResolve(Room *, HashMap *);
extern State * StateResolve(Room *, HashMap *);
/**
* Computes the current state from the room's leaves.
*/
extern HashMap * StateCurrent(Room *);
extern State * StateCurrent(Room *);
/**
* Frees an entire state table from the heap.
*/
extern void StateFree(HashMap *);
extern void StateFree(State *);
/**
* Deserialises a state map from JSON to the internal format
@ -81,11 +86,11 @@ extern void StateFree(HashMap *);
* and should be freed with
* .Fn StateFree .
*/
extern HashMap * StateDeserialise(HashMap *);
extern State * StateDeserialise(HashMap *);
/**
* Serialises a state map from the internal format to JSON
* used for the database, for example
*/
extern HashMap * StateSerialise(HashMap *);
extern HashMap * StateSerialise(State *);
#endif /* TELODENDRIA_STATE_H */