[ADD/UNTESTED] Barebones State Resolution V1
Some checks failed
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Has been cancelled

NOTE: It is probably NOT in an usable state. Even if it is, it should
not be PR'd into Telodendria right now.

Feeling a bit burnt out, so I'll take a break. Feel free to issue PRs to
this branch to clean/weed out some mistakes or bugs, since I only made
sure that it built.
This commit is contained in:
lda 2024-05-15 20:15:49 +02:00
parent 3066a0e8a8
commit 15b884b04a
4 changed files with 311 additions and 82 deletions

View file

@ -174,7 +174,7 @@ RoomStateGet(Room * room)
database_state = DbJson(room->ref); database_state = DbJson(room->ref);
return StateDeserialise(database_state); return StateDeserialise(database_state);
} }
static HashMap * HashMap *
RoomStateGetID(Room * room, char *event_id) RoomStateGetID(Room * room, char *event_id)
{ {
HashMap *event, *state; HashMap *event, *state;
@ -195,11 +195,10 @@ RoomStateGetID(Room * room, char *event_id)
return state; return state;
} }
#define PrepareState(room, e, type, key, n) \ #define PrepareState(room, S, type, key, n) \
do \ do \
{ \ { \
state_point = RoomStateGetID(room, e); \ id_##n = StateGet(S, type, key); \
id_##n = StateGet(state_point, type, key); \
if (id_##n) \ if (id_##n) \
{ \ { \
goto finish; \ goto finish; \
@ -212,29 +211,24 @@ RoomStateGetID(Room * room, char *event_id)
} \ } \
while (0) while (0)
#define FinishState(name) \ #define FinishState(name) \
if (state_point) \
{ \
StateFree(state_point); \
} \
if (name) \ if (name) \
{ \ { \
JsonFree(name); \ JsonFree(name); \
} \ } \
return ret return ret
static bool static bool
RoomIsJoinRule(Room * room, char *e_id, char *jr) RoomIsJoinRule(Room * room, HashMap *state, char *jr)
{ {
HashMap *state_point;
HashMap *joinrule = NULL; HashMap *joinrule = NULL;
char *id_joinrule = NULL; char *id_joinrule = NULL;
bool ret = false; bool ret = false;
if (!room || !e_id || !jr) if (!room || !state || !jr)
{ {
return false; return false;
} }
PrepareState(room, e_id, "m.room.join_rules", "", joinrule); PrepareState(room, state, "m.room.join_rules", "", joinrule);
ret = StrEquals( ret = StrEquals(
JsonValueAsString(JsonGet(joinrule, 1, "join_rule")), JsonValueAsString(JsonGet(joinrule, 1, "join_rule")),
jr); jr);
@ -243,19 +237,18 @@ finish:
} }
/* Verifies if user has a specific membership before [e_id] in the room. */ /* Verifies if user has a specific membership before [e_id] in the room. */
static bool static bool
RoomUserHasMembership(Room * room, char *e_id, char *user, char *mbr) RoomUserHasMembership(Room * room, HashMap *state, char *user, char *mbr)
{ {
HashMap *state_point;
HashMap *membership = NULL; HashMap *membership = NULL;
char *id_membership = NULL; char *id_membership = NULL;
char *membership_value; char *membership_value;
bool ret = false; bool ret = false;
if (!room || !e_id || !user || !mbr) if (!room || !state || !user || !mbr)
{ {
return false; return false;
} }
PrepareState(room, e_id, "m.room.member", user, membership); PrepareState(room, state, "m.room.member", user, membership);
membership_value = membership_value =
JsonValueAsString(JsonGet(membership, 2, "content", "membership")); JsonValueAsString(JsonGet(membership, 2, "content", "membership"));
@ -292,18 +285,18 @@ ParsePL(JsonValue *v, int64_t def)
} }
/* Computes the smallest PL needed to do something somewhere */ /* Computes the smallest PL needed to do something somewhere */
static int64_t static int64_t
RoomMinPL(Room * room, char *e_id, char *type, char *act) RoomMinPL(Room * room, HashMap *state, char *type, char *act)
{ {
HashMap *state_point, *pl = NULL; HashMap *pl = NULL;
JsonValue *val; JsonValue *val;
char *id_pl; char *id_pl;
int64_t ret, def; int64_t ret, def;
if (!room || !e_id || !act) if (!room || !state || !act)
{ {
return 0; return 0;
} }
PrepareState(room, e_id, "m.room.power_levels", "", pl); PrepareState(room, state, "m.room.power_levels", "", pl);
/* Every other act has a minimum PL of 0 */ /* Every other act has a minimum PL of 0 */
def = 0; def = 0;
@ -331,20 +324,20 @@ finish:
} }
/* Finds the power level of an user before [e_id] was sent. */ /* Finds the power level of an user before [e_id] was sent. */
static int64_t static int64_t
RoomUserPL(Room * room, char *e_id, char *user) RoomUserPL(Room * room, HashMap *state, char *user)
{ {
HashMap *state_point, *pl = NULL; HashMap *pl = NULL;
char *id_pl; char *id_pl;
int64_t ret, def; int64_t ret, def;
if (!room || !e_id || !user) if (!room || !state || !user)
{ {
return 0; return 0;
} }
PrepareState(room, e_id, "m.room.power_levels", "", pl); PrepareState(room, state, "m.room.power_levels", "", pl);
def = RoomMinPL(room, e_id, NULL, "users_default"); def = RoomMinPL(room, state, NULL, "users_default");
ret = ParsePL(JsonGet(pl, 2, "users", user), def); ret = ParsePL(JsonGet(pl, 2, "users", user), def);
finish: finish:
@ -598,7 +591,7 @@ AuthoriseAliasV1(PduV1 pdu)
return true; return true;
} }
static bool static bool
AuthorizeInviteMembershipV1(Room * room, PduV1 pdu) AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, HashMap *state)
{ {
int64_t invite_level; int64_t invite_level;
int64_t pdu_level; int64_t pdu_level;
@ -608,10 +601,10 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu)
{ {
JsonValue *signed_val, *mxid, *token; JsonValue *signed_val, *mxid, *token;
HashMap *third_pi_obj = JsonValueAsObject(third_pi), *signed_obj; HashMap *third_pi_obj = JsonValueAsObject(third_pi), *signed_obj;
HashMap *state, *third_pi_event; HashMap *third_pi_event;
char *third_pi_id, *thirdpi_event_sender; char *third_pi_id, *thirdpi_event_sender;
/* Step 5.3.1.1: If target user is banned, reject. */ /* Step 5.3.1.1: If target user is banned, reject. */
if (RoomUserHasMembership(room, pdu.event_id, pdu.state_key, "ban")) if (RoomUserHasMembership(room, state, pdu.state_key, "ban"))
{ {
return false; return false;
} }
@ -642,16 +635,13 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu)
/* Step 5.3.1.5: If there is no m.room.third_party_invite event /* Step 5.3.1.5: If there is no m.room.third_party_invite event
* in the current room state with state_key matching token, reject. */ * in the current room state with state_key matching token, reject. */
state = RoomStateGetID(room, pdu.event_id);
if (!(third_pi_id = StateGet( if (!(third_pi_id = StateGet(
state, state,
"m.room.third_party_invite", JsonValueAsString(token)))) "m.room.third_party_invite", JsonValueAsString(token))))
{ {
StateFree(state);
return false; return false;
} }
third_pi_event = RoomEventFetch(room, third_pi_id); third_pi_event = RoomEventFetch(room, third_pi_id);
StateFree(state);
/* Step 5.3.1.6: If sender does not match sender of the /* Step 5.3.1.6: If sender does not match sender of the
* m.room.third_party_invite, reject. */ * m.room.third_party_invite, reject. */
@ -675,21 +665,21 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu)
/* Step 5.3.2: If the sender's current membership state is not join, /* Step 5.3.2: If the sender's current membership state is not join,
* reject. */ * reject. */
if (!RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join")) if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
{ {
return false; return false;
} }
/* Step 5.3.3: If target users current membership state is join or ban, reject. */ /* Step 5.3.3: If target users current membership state is join or ban, reject. */
if (RoomUserHasMembership(room, pdu.event_id, pdu.state_key, "join") || if (RoomUserHasMembership(room, state, pdu.state_key, "join") ||
RoomUserHasMembership(room, pdu.event_id, pdu.state_key, "join")) RoomUserHasMembership(room, state, pdu.state_key, "join"))
{ {
return false; return false;
} }
/* Step 5.3.4: If the sender's power level is greater than or equal to the /* Step 5.3.4: If the sender's power level is greater than or equal to the
* invite level, allow. */ * invite level, allow. */
invite_level = RoomMinPL(room, pdu.event_id, NULL, "invite"); invite_level = RoomMinPL(room, state, NULL, "invite");
pdu_level = RoomUserPL(room, pdu.event_id, pdu.sender); pdu_level = RoomUserPL(room, state, pdu.sender);
if (pdu_level >= invite_level) if (pdu_level >= invite_level)
{ {
return true; return true;
@ -698,31 +688,31 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu)
return false; return false;
} }
static bool static bool
AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu) AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, HashMap *state)
{ {
int64_t ban_level = RoomMinPL(room, pdu.event_id, NULL, "ban"); int64_t ban_level = RoomMinPL(room, state, NULL, "ban");
int64_t kick_level = RoomMinPL(room, pdu.event_id, NULL, "kick"); int64_t kick_level = RoomMinPL(room, state, NULL, "kick");
int64_t sender_level = RoomUserPL(room, pdu.event_id, pdu.sender); int64_t sender_level = RoomUserPL(room, state, pdu.sender);
int64_t target_level = RoomUserPL(room, pdu.state_key, pdu.sender); int64_t target_level = RoomUserPL(room, state, pdu.sender);
/* Step 5.4.1: If the sender matches state_key, allow if and only if /* Step 5.4.1: If the sender matches state_key, allow if and only if
* that user's current membership state is invite or join. */ * that user's current membership state is invite or join. */
if (StrEquals(pdu.sender, pdu.state_key)) if (StrEquals(pdu.sender, pdu.state_key))
{ {
return return
RoomUserHasMembership(room, pdu.event_id, pdu.sender, "invite") || RoomUserHasMembership(room, state, pdu.sender, "invite") ||
RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join"); RoomUserHasMembership(room, state, pdu.sender, "join");
} }
/* Step 5.4.2: If the sender's current membership state is not join, /* Step 5.4.2: If the sender's current membership state is not join,
* reject. */ * reject. */
if (!RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join")) if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
{ {
return false; return false;
} }
/* Step 5.4.3: If the target user's current membership state is ban, /* Step 5.4.3: If the target user's current membership state is ban,
* and the sender's power level is less than the ban level, reject. */ * and the sender's power level is less than the ban level, reject. */
if (RoomUserHasMembership(room, pdu.event_id, pdu.state_key, "ban") && if (RoomUserHasMembership(room, state, pdu.state_key, "ban") &&
sender_level < ban_level) sender_level < ban_level)
{ {
return false; return false;
@ -740,12 +730,12 @@ AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu)
return false; return false;
} }
static bool static bool
AuthorizeBanMembershipV1(Room * room, PduV1 pdu) AuthorizeBanMembershipV1(Room * room, PduV1 pdu, HashMap *state)
{ {
int64_t ban_pl, pdu_pl, target_pl; int64_t ban_pl, pdu_pl, target_pl;
/* Step 5.5.1: If the sender's current membership state is not join, reject. */ /* Step 5.5.1: If the sender's current membership state is not join, reject. */
if (!RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join")) if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
{ {
return false; return false;
} }
@ -753,9 +743,9 @@ AuthorizeBanMembershipV1(Room * room, PduV1 pdu)
/* Step 5.5.2: If the sender's power level is greater than or equal /* Step 5.5.2: If the sender's power level is greater than or equal
* to the ban level, and the target user's power level is less than * to the ban level, and the target user's power level is less than
* the sender's power level, allow. */ * the sender's power level, allow. */
ban_pl = RoomMinPL(room, pdu.event_id, NULL, "ban"); ban_pl = RoomMinPL(room, state, NULL, "ban");
pdu_pl = RoomUserPL(room, pdu.event_id, pdu.sender); pdu_pl = RoomUserPL(room, state, pdu.sender);
target_pl = RoomUserPL(room, pdu.state_key, pdu.sender); target_pl = RoomUserPL(room, state, pdu.sender);
if ((pdu_pl >= ban_pl) && (target_pl < pdu_pl)) if ((pdu_pl >= ban_pl) && (target_pl < pdu_pl))
{ {
return true; return true;
@ -765,7 +755,7 @@ AuthorizeBanMembershipV1(Room * room, PduV1 pdu)
return false; return false;
} }
static bool static bool
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu) AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, HashMap *state)
{ {
/* Step 5.2.1: If the only previous event is an m.room.create and the /* Step 5.2.1: If the only previous event is an m.room.create and the
* state_key is the creator, allow. */ * state_key is the creator, allow. */
@ -797,21 +787,21 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu)
return false; return false;
} }
/* Step 5.2.3: If the sender is banned, reject. */ /* Step 5.2.3: If the sender is banned, reject. */
if (RoomUserHasMembership(room, pdu.event_id, pdu.sender, "ban")) if (RoomUserHasMembership(room, state, pdu.sender, "ban"))
{ {
return false; return false;
} }
/* Step 5.2.4: If the join_rule is invite then allow if membership /* Step 5.2.4: If the join_rule is invite then allow if membership
* state is invite or join. */ * state is invite or join. */
if (RoomIsJoinRule(room, pdu.event_id, "invite") && if (RoomIsJoinRule(room, state, "invite") &&
(RoomUserHasMembership(room, pdu.event_id, pdu.sender, "invite") || (RoomUserHasMembership(room, state, pdu.sender, "invite") ||
RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join"))) RoomUserHasMembership(room, state, pdu.sender, "join")))
{ {
return true; return true;
} }
/* Step 5.2.5: If the join_rule is public, allow. */ /* Step 5.2.5: If the join_rule is public, allow. */
if (RoomIsJoinRule(room, pdu.event_id, "public")) if (RoomIsJoinRule(room, state, "public"))
{ {
return true; return true;
} }
@ -819,7 +809,7 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu)
return false; return false;
} }
static bool static bool
AuthoriseMemberV1(Room * room, PduV1 pdu) AuthoriseMemberV1(Room * room, PduV1 pdu, HashMap *state)
{ {
JsonValue *membership; JsonValue *membership;
char *membership_str; char *membership_str;
@ -840,7 +830,7 @@ AuthoriseMemberV1(Room * room, PduV1 pdu)
#define JumpIfMembership(mem, func) do { \ #define JumpIfMembership(mem, func) do { \
if (StrEquals(membership_str, mem)) \ if (StrEquals(membership_str, mem)) \
{ \ { \
return func(room, pdu); \ return func(room, pdu, state); \
} \ } \
} while (0) } while (0)
@ -861,19 +851,19 @@ AuthoriseMemberV1(Room * room, PduV1 pdu)
#undef JumpIfMembership #undef JumpIfMembership
} }
static bool static bool
AuthorisePowerLevelsV1(Room * room, PduV1 pdu) AuthorisePowerLevelsV1(Room * room, PduV1 pdu, HashMap *state)
{ {
/* Step 10.1: If the users property in content is not an object with /* 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 * keys that are valid user IDs with values that are integers
* (or a string that is an integer), reject. */ * (or a string that is an integer), reject. */
JsonValue *users = JsonGet(pdu.content, 1, "users"); JsonValue *users = JsonGet(pdu.content, 1, "users");
HashMap *users_o, *state, *prev_plevent; HashMap *users_o, *prev_plevent;
char *user_id, *prev_pl_id, *ev_type; char *user_id, *prev_pl_id, *ev_type;
JsonValue *power_level, *ev_obj; JsonValue *power_level, *ev_obj;
bool flag = true; bool flag = true;
int64_t userpl = RoomUserPL(room, pdu.event_id, pdu.sender); int64_t userpl = RoomUserPL(room, state, pdu.sender);
HashMap *event_obj; HashMap *event_obj;
if (JsonValueType(users) != JSON_OBJECT) if (JsonValueType(users) != JSON_OBJECT)
{ {
@ -915,10 +905,8 @@ AuthorisePowerLevelsV1(Room * room, PduV1 pdu)
/* Step 10.2: If there is no previous m.room.power_levels event /* Step 10.2: If there is no previous m.room.power_levels event
* in the room, allow. */ * in the room, allow. */
state = RoomStateGetID(room, pdu.event_id);
if (!(prev_pl_id = StateGet(state, "m.room.power_levels", ""))) if (!(prev_pl_id = StateGet(state, "m.room.power_levels", "")))
{ {
StateFree(state);
return true; return true;
} }
@ -1033,17 +1021,16 @@ AuthorisePowerLevelsV1(Room * room, PduV1 pdu)
#undef CheckPLNew #undef CheckPLNew
/* Step 10.8: Otherwise, allow. */ /* Step 10.8: Otherwise, allow. */
JsonFree(prev_plevent); JsonFree(prev_plevent);
StateFree(state);
return true; return true;
} }
bool bool
AuthoriseEventV1(Room * room, PduV1 pdu) RoomAuthoriseEventV1(Room * room, PduV1 pdu, HashMap *state)
{ {
HashMap *state, *create_event; HashMap *create_event;
char *create_event_id; char *create_event_id;
JsonValue *federate; JsonValue *federate;
int64_t pdu_pl = RoomUserPL(room, pdu.event_id, pdu.sender); int64_t pdu_pl = RoomUserPL(room, state, pdu.sender);
int64_t event_pl = RoomMinPL(room, pdu.event_id, "events", pdu.type); int64_t event_pl = RoomMinPL(room,state, "events", pdu.type);
/* Step 1: If m.room.create */ /* Step 1: If m.room.create */
if (StrEquals(pdu.type, "m.room.create")) if (StrEquals(pdu.type, "m.room.create"))
{ {
@ -1061,13 +1048,10 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
* the event does not match the sender domain of the create event, * the event does not match the sender domain of the create event,
* reject. * reject.
*/ */
state = RoomStateGet(room);
create_event_id = StateGet(state, "m.room.create", ""); create_event_id = StateGet(state, "m.room.create", "");
if (!state || !create_event_id) if (!state || !create_event_id)
{ {
/* At this point, [create_event_id] has to exist */ /* At this point, [create_event_id] has to exist */
StateFree(state); /* create_event_id is also freed. */
return false; return false;
} }
create_event = RoomEventFetch(room, create_event_id); create_event = RoomEventFetch(room, create_event_id);
@ -1095,10 +1079,10 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
/* Step 5: If type is m.room.member */ /* Step 5: If type is m.room.member */
if (StrEquals(pdu.type, "m.room.member")) if (StrEquals(pdu.type, "m.room.member"))
{ {
return AuthoriseMemberV1(room, pdu); return AuthoriseMemberV1(room, pdu, state);
} }
/* Step 6: If the sender's current membership state is not join, reject. */ /* Step 6: If the sender's current membership state is not join, reject. */
if (!RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join")) if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
{ {
return false; return false;
} }
@ -1107,7 +1091,7 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
{ {
/* Allow if and only if sender's current power level is greater than /* Allow if and only if sender's current power level is greater than
* or equal to the invite level */ * or equal to the invite level */
int64_t min_pl = RoomMinPL(room, pdu.event_id, NULL, "invite"); int64_t min_pl = RoomMinPL(room, state, NULL, "invite");
return pdu_pl >= min_pl; return pdu_pl >= min_pl;
} }
@ -1132,13 +1116,13 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
/* Step 10: If type is m.room.power_levels */ /* Step 10: If type is m.room.power_levels */
if (StrEquals(pdu.type, "m.room.power_levels")) if (StrEquals(pdu.type, "m.room.power_levels"))
{ {
return AuthorisePowerLevelsV1(room, pdu); return AuthorisePowerLevelsV1(room, pdu, state);
} }
/* Step 11: If type is m.room.redaction */ /* Step 11: If type is m.room.redaction */
if (StrEquals(pdu.type, "m.room.redaction")) if (StrEquals(pdu.type, "m.room.redaction"))
{ {
int64_t min_pl = RoomMinPL(room, pdu.event_id, NULL, "redact"); int64_t min_pl = RoomMinPL(room, state, NULL, "redact");
/* Step 11.1: If the sender's power level is greater than or equal /* Step 11.1: If the sender's power level is greater than or equal
* to the redact level, allow. */ * to the redact level, allow. */

View file

@ -29,15 +29,199 @@
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <string.h>
#include <Event.h> #include <Event.h>
#include <Room.h> #include <Room.h>
static HashMap * int
StateResolveV1(Array * states) V1Cmp(void *a, void *b)
{ {
(void) states; HashMap *e1 = a, *e2 = b;
return NULL; int64_t depth1 =
JsonValueAsInteger(JsonGet(e1, 1, "depth"));
int64_t depth2 =
JsonValueAsInteger(JsonGet(e2, 1, "depth"));
if (depth1 > depth2)
{
return 1;
}
else if (depth1 < depth2)
{
return -1;
}
else
{
char *e1id =
JsonValueAsString(JsonGet(e1, 1, "event_id"));
char *e2id =
JsonValueAsString(JsonGet(e2, 1, "event_id"));
unsigned char *sha1 = Sha1(e1id);
unsigned char *sha2 = Sha1(e2id);
char *str1 = ShaToHex(sha1);
char *str2 = ShaToHex(sha2);
int ret = strcmp(str1, str2) * -1;
Free(str1);
Free(str2);
Free(sha1);
Free(sha2);
/* Descending */
return ret;
}
}
static HashMap *
StateResolveV1(Room * room, Array * states)
{
HashMap *R = HashMapCreate();
HashMap *conflicts = HashMapCreate();
Array *events, *types, *conflicting;
size_t i;
ssize_t j;
char *type, *key, *event_id;
for (i = 0; i < ArraySize(states); i++)
{
HashMap *state = ArrayGet(states, i);
char *tuple;
while (HashMapIterate(state, &tuple, (void **) &event_id))
{
if (HashMapGet(R, tuple))
{
Array *arr;
/* Conflicts! */
HashMapDelete(R, tuple);
arr = HashMapGet(conflicts, tuple);
if (!arr)
{
arr = ArrayCreate();
}
ArrayAdd(arr, RoomEventFetch(room, event_id));
HashMapSet(conflicts, tuple, arr);
}
else
{
/* Add to R */
HashMapSet(R, tuple, event_id);
}
}
}
/* 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);
HashMap *first;
Array *state_keys;
events = ArrayCreate();
while (StateIterate(conflicts, &type, &key, (void **) &conflicting))
{
if (StrEquals(type, t))
{
for (j = 0; j < (ssize_t) ArraySize(conflicting); j++)
{
HashMap *event = ArrayGet(conflicting, j);
ArrayAdd(events, event);
}
}
Free(type);
Free(key);
}
ArraySort(events, V1Cmp);
/* Add first event. */
first = ArrayDelete(events, 0);
StateSet(
R,
JsonValueAsString(JsonGet(first, 1, "type")),
JsonValueAsString(JsonGet(first, 1, "state_key")),
JsonValueAsString(JsonGet(first, 1, "event_id")));
for (j = 0; j < (ssize_t) ArraySize(events); j++)
{
HashMap *event = ArrayGet(events, j);
PduV1 pdu;
char *msg;
PduV1FromJson(event, &pdu, &msg);
if (RoomAuthoriseEventV1(room, pdu, R))
{
StateSet(R, pdu.type, pdu.state_key, pdu.event_id);
}
else
{
PduV1Free(&pdu);
break;
}
(void) msg;
PduV1Free(&pdu);
}
ArrayFree(events);
/* Delete all elements within a key. */
state_keys = ArrayCreate();
while (StateIterate(conflicts, &type, &key, (void **) &event_id))
{
if (StrEquals(type, t))
{
ArrayAdd(state_keys, key);
}
Free(type);
}
for (j = 0; j < (ssize_t) ArraySize(state_keys); j++)
{
char *state_key = ArrayGet(state_keys, j);
StateSet(conflicts, t, state_key, NULL);
Free(state_key);
}
Free(state_keys);
}
ArrayFree(types);
while (StateIterate(conflicts, &type, &key, (void **) &conflicting))
{
ArraySort(conflicting, V1Cmp);
for (j = ArraySize(conflicting) - 1; j >= 0; j--)
{
HashMap *event = ArrayGet(events, j);
PduV1 pdu;
char *msg;
PduV1FromJson(event, &pdu, &msg);
if (RoomAuthoriseEventV1(room, pdu, R))
{
StateSet(R, pdu.type, pdu.state_key, pdu.event_id);
PduV1Free(&pdu);
break;
}
(void) msg;
PduV1Free(&pdu);
}
Free(type);
Free(key);
}
while (StateIterate(conflicts, &type, &key, (void **) &conflicting))
{
for (i = 0; i < ArraySize(conflicting); i++)
{
JsonFree(ArrayGet(conflicting, i));
}
ArrayFree(conflicting);
}
HashMapFree(conflicts);
return R;
} }
static HashMap * static HashMap *
@ -83,11 +267,29 @@ StateResolve(Room * room, HashMap * event)
switch (RoomVersionGet(room)) switch (RoomVersionGet(room))
{ {
case 1: case 1:
return StateResolveV1(states); return StateResolveV1(room, states);
default: default:
return StateResolveV2(states); return StateResolveV2(states);
} }
} }
bool StateIterate(HashMap *state, char **type, char **key, void **event)
{
char *tuple;
bool ret;
if (!state || !type || !key || !event)
{
return false;
}
ret = HashMapIterate(state, &tuple, event);
tuple = StrDuplicate(tuple);
*(strchr(tuple, ',')) = '\0';
*type = tuple;
*key = StrDuplicate(tuple + strlen(tuple) + 1);
return ret;
}
char * char *
StateGet(HashMap *state, char *type, char *key) StateGet(HashMap *state, char *type, char *key)
{ {
@ -105,6 +307,27 @@ StateGet(HashMap *state, char *type, char *key)
return ret; return ret;
} }
void void
StateSet(HashMap *state, char *type, char *key, char *event)
{
char *full_string, *old;
if (!state || !type || !key)
{
return;
}
full_string = StrConcat(3, type, ",", key);
old = HashMapDelete(state, full_string);
if (old)
{
Free(old);
}
if (event)
{
HashMapSet(state, full_string, StrDuplicate(event));
}
Free(full_string);
}
void
StateFree(HashMap *state) StateFree(HashMap *state)
{ {
char *full; char *full;

View file

@ -41,6 +41,7 @@
#include <Cytoplasm/Db.h> #include <Cytoplasm/Db.h>
#include <Schema/RoomCreateRequest.h> #include <Schema/RoomCreateRequest.h>
#include <Schema/PduV1.h>
#include <User.h> #include <User.h>
@ -98,6 +99,12 @@ extern int RoomVersionGet(Room *);
* client events. * client events.
*/ */
extern HashMap * RoomStateGet(Room *); extern HashMap * RoomStateGet(Room *);
/**
* Resolves the room's state before a specific point,
* like
* .Fn RoomStateGet .
*/
extern HashMap * RoomStateGetID(Room *, char *);
/** /**
* Get a list of the most recent events in the * Get a list of the most recent events in the
@ -138,4 +145,10 @@ extern HashMap * RoomEventSend(Room *, HashMap *);
*/ */
extern HashMap * RoomEventFetch(Room *, char *); extern HashMap * RoomEventFetch(Room *, char *);
/**
* Verifies whenever an event(as a PDUv1) is
* authorised by a room.
*/
extern bool RoomAuthoriseEventV1(Room *, PduV1, HashMap *);
#endif /* TELODENDRIA_ROOM_H */ #endif /* TELODENDRIA_ROOM_H */

View file

@ -47,7 +47,16 @@ extern char *StateGet(HashMap *, char *, char *);
/** /**
* Set a state tuple to a value. * Set a state tuple to a value.
*/ */
extern char *StateSet(HashMap *, char *, char *, char *); extern void StateSet(HashMap *, char *, char *, char *);
/**
* Iterates through a statemap, with (type, key) -> event.
* The type and keys are stored on the heap, and will need
* to be freed.
* This function behaves like
* .Fn HashMapIterate .
*/
extern bool StateIterate(HashMap *, char **, char **, void **);
/** /**
* Compute the room state before the specified event was sent. * Compute the room state before the specified event was sent.