0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-05-16 01:43:49 +02:00

Compare commits

...

86 commits

Author SHA1 Message Date
Jason Volk 0624b69246 modules/web_hook: Add post-push handler for reacting to push events. 2023-05-01 12:45:37 -07:00
Jason Volk e5ae70c521 modules/web_hook: Add requests for workflows and dispatching. 2023-05-01 12:45:37 -07:00
Jason Volk 759871ce8e ircd::versions: Minor cleanup instance_list related. 2023-05-01 12:39:06 -07:00
Jason Volk 3c9a4f8c57 ircd:Ⓜ️ Assertions for non-empty id constructions. 2023-05-01 10:48:07 -07:00
Jason Volk f6b3b8b758 ircd:Ⓜ️:event::append: Add branch for bundled relations; add m.replace bundle. 2023-05-01 10:48:07 -07:00
Jason Volk 4fe85805bf ircd:Ⓜ️:relates: Add conf item for non-type coarse control of sorting field. 2023-05-01 10:48:07 -07:00
Jason Volk b6cb1180f7 ircd:Ⓜ️:user::keys: Basis for verification cross-signatures between users. 2023-05-01 10:48:07 -07:00
Jason Volk 741304271e ircd:Ⓜ️:event::append: Optimize opts, eliminate double-indirection; various reorg. 2023-05-01 10:48:07 -07:00
Jason Volk cd2b92b8fe modules/client/keys/claim: Restore when_any() loop to smooth remote handling. 2023-04-29 13:57:39 -07:00
Jason Volk feb4ac1fd3 ircd:Ⓜ️:event::append: Move static objects into class. 2023-04-29 13:57:39 -07:00
Jason Volk d4ba215a3b modules/client/user: Relax matching user_id in URL to requestor for bridges. 2023-04-29 13:57:39 -07:00
Jason Volk 8a705f77a9 ircd::db: Fixes for RocksDB 8.1.1 interface changes. 2023-04-29 13:57:37 -07:00
Jason Volk f86fddc3a3 configure: Error on -Wabstract-final-class (clang) 2023-04-28 13:39:35 -07:00
Jason Volk 8eb10c5d1a modules/client/rooms/event: Replace raw event response with client format. 2023-04-28 13:04:06 -07:00
Jason Volk a7a3275817 modules/client/keys/claim: Handle local users without loopbacking. 2023-04-27 20:41:18 -07:00
Jason Volk 82feeb5274 ircd:Ⓜ️:user::keys: Centralize key claiming for user device and algorithm. 2023-04-27 20:41:18 -07:00
Jason Volk 8af781c5d7 modules/client/keys/query: Handle local users without loopbacking. 2023-04-27 20:41:18 -07:00
Jason Volk 29f3ddfc3e ircd:Ⓜ️:user::devices: Upgrade to polymorphic closure_bool. 2023-04-27 20:41:18 -07:00
Jason Volk 0b888b662a ircd:Ⓜ️:user::keys: Further abstract callstack through type string argument. 2023-04-27 20:41:18 -07:00
Jason Volk bbed809975 ircd:Ⓜ️:user::keys: Consolidate cross signing keys update. 2023-04-27 20:41:18 -07:00
Jason Volk f9aeae5516 ircd:Ⓜ️ Add user_signing_key to signing_key_update for completeness. 2023-04-27 20:41:18 -07:00
Jason Volk 9301980f9d ircd:Ⓜ️:user::keys: Implement m.signing_key_update broadcast interface. 2023-04-27 20:41:18 -07:00
Jason Volk ca80d66e85 ircd:Ⓜ️:user::devices: Complete the m.device_list_update broadcast w/ keys; reinterface. 2023-04-27 20:41:18 -07:00
Jason Volk e7ad503f8c modules/federation/user_keys_claim: Implement claimancy; check user exists; minor cleanup. 2023-04-27 17:18:17 -07:00
Jason Volk f70d837258 modules/client/keys/claim: Enforce remote authority over results; relax log level. 2023-04-27 17:18:17 -07:00
Jason Volk 6f5121dc6a modules/client/keys/query: Refactor receive handling for properly composed response. 2023-04-27 17:18:17 -07:00
Jason Volk 91a8fcbe43 ircd::ctx::future: Add what() exception peeking convenience to interface. 2023-04-27 11:36:31 -07:00
Jason Volk b4b26484ec modules/console: Add wildcard for all devices to user devices update cmd. 2023-04-26 19:27:04 -07:00
Jason Volk faf796a56b ircd:Ⓜ️:pretty: Increase widths; improve formatting for stateline. 2023-04-26 19:27:04 -07:00
Jason Volk 48e0755a79 modules/web_hook: Search for reactions by contents rather than endswith. 2023-04-26 19:27:04 -07:00
Jason Volk 5f5be52fa9 ircd::net::dns::cache: Fix case sensitivities on several key mappings. (956aa7682f) 2023-04-26 19:27:04 -07:00
Jason Volk 5f0b98e5d1 modules/console: Add well_known matrix client diagnostic. 2023-04-26 19:27:04 -07:00
Jason Volk 62a1c850cc ircd::rest: Add get overload w/ default opts. 2023-04-26 19:27:04 -07:00
Jason Volk e874e28473 modules/console: Add raw override argument to fed user keys query and device cmds. 2023-04-26 14:50:26 -07:00
Jason Volk 55f332c821 modules: Replace any http::CREATED with http::OK. (5a06c006) 2023-04-26 14:50:15 -07:00
Jason Volk 44a4f33e45 modules/federation/user_devices: Add signed keys results to response. 2023-04-26 14:50:14 -07:00
Jason Volk 04025af961 ircd:Ⓜ️:user::keys: Abstract key appendix; attach signatures to all keys. 2023-04-26 14:50:14 -07:00
Jason Volk d1b0722169 ircd:Ⓜ️:user::keys: Move amalgamated keys and sigs output into central interface. 2023-04-26 14:50:14 -07:00
Jason Volk 0943cfd69c modules/federation/user_keys_query: Merge additional signatures into master key object. 2023-04-24 16:37:52 -07:00
Jason Volk 8b0cf48578 modules/client/keys/signatures: Re-schematize signatures uploaded for keys and devices. 2023-04-24 16:37:49 -07:00
Jason Volk 8eb4de920c modules: Rename cross_signing types out of device namespace. 2023-04-24 14:21:15 -07:00
Giovanni Bottaro 5a06c0066e modules/client/user/filter: Set the HTTP response status code according to Matrix spec 2023-04-24 14:02:24 -07:00
Jason Volk 4ebc9fefaf modules/client/rooms/relations: Condition path param decode on non-empty args. 2023-04-24 14:02:24 -07:00
Jason Volk 5f3398bf52 github/workflows/docker: Add manual dispatch arguments. 2023-04-24 14:02:24 -07:00
Jason Volk dabc8b4304 modules/federation/user_keys_query: Cross signatures. 2023-04-22 22:04:59 -07:00
Jason Volk d80f29b65a ircd::json::stack::member: Conversion constructions from other member categories. 2023-04-22 21:44:55 -07:00
Jason Volk 6c3420afbc modules/client/sync/device_lists: Fix structure; indicate changes to own device. 2023-04-22 00:21:29 -07:00
Jason Volk 6072229dcc modules/client/sync: Stub device_unused_fallback_key_types. 2023-04-22 00:21:29 -07:00
Jason Volk 956aa7682f ircd::net::dns::resolver: Fix case sensitivity of the tag host. 2023-04-22 00:21:29 -07:00
Jason Volk 238cc10489 ircd:Ⓜ️:rooms: Implement matrix-org/matrix-spec-proposals#3827 2023-04-21 21:40:50 -07:00
Jason Volk 1a032b28b7 ircd:Ⓜ️:room: Add boolean query for room type. 2023-04-21 21:40:50 -07:00
Jason Volk f08b9e85cd ircd:Ⓜ️:createroom: Bump the default version. 2023-04-21 21:40:50 -07:00
Jason Volk 9ce44aadd5 modules/client/devices: Add de facto user_id to response. 2023-04-21 17:45:57 -07:00
Jason Volk 78d6e4ce03 modules/client/capabilities: List additional capabilities. 2023-04-21 17:42:27 -07:00
Jason Volk 882629ab53 modules/federation/user_devices: Fix property name; additional for the loopback. 2023-04-21 17:42:27 -07:00
Jason Volk 8ade7c814e modules/client/room_keys: Add missing count and etag to responses. 2023-04-20 18:22:02 -07:00
Jason Volk 4d5d99ab2c modules/client/room_keys: Unify version and keys into single module. 2023-04-20 16:51:09 -07:00
Jason Volk 502a03a8cf modules/console: Add redact state convenience; fix missing newline. 2023-04-20 13:34:47 -07:00
Jason Volk 1e42fa16a7 modules/client/room_keys/keys: Add missing DELETE for all rooms and sessions. 2023-04-20 13:34:47 -07:00
Jason Volk 0a151a3a90 modules/client/room_keys/keys: Fix sessions request and response structure.
closes #142
2023-04-20 13:34:44 -07:00
Jason Volk fa46231e3a modules/client/room_keys/keys: Add util to split state_key parts. 2023-04-20 13:34:23 -07:00
Jason Volk 336026dde6 modules/client/keys/signatures/upload: Fix inhibition to interactive verification.
closes #162
2023-04-20 13:33:08 -07:00
Jason Volk d5df04a183 modules/federation/user_keys_query: Add missing user_signing_keys on the loopback. 2023-04-20 00:54:01 -07:00
Jason Volk 080748f0af modules/client/keys/signatures/upload: Fix POST interpretation. 2023-04-19 15:01:24 -07:00
Jason Volk 321ea3d641 modules/client/room_keys/version: Implement missing PUT replacement functionality. 2023-04-19 15:01:24 -07:00
Jason Volk d41926bc6f modules/client/versions: Add more version. 2023-04-19 14:56:11 -07:00
Jason Volk 58476f59c3 modules/console: Overload parameters of room events cmd to filter by type. 2023-04-19 14:56:11 -07:00
Jason Volk 8dc50db2dc modules/console: Add user devices delete cmd. 2023-04-19 14:56:11 -07:00
Jason Volk 2122412c6d modules/console: Split user tokens clear into separate cmd. 2023-04-19 14:56:11 -07:00
Jason Volk 15cb6bfdca ircd:Ⓜ️🆔 de facto device id. 2023-04-19 14:56:11 -07:00
Jason Volk fa0365fa31 ircd:Ⓜ️ Add convenience interface for querying m.replace relation. 2023-04-18 20:11:29 -07:00
Jason Volk 02e09728a5 ircd::rest: Add option to supply temporary/headers buffer in lieu of allocating. 2023-04-18 20:11:29 -07:00
Jason Volk dc13381822 ircd::json: Reduce replace() overloads to single linked procedure. 2023-04-18 20:11:29 -07:00
Jason Volk 22b9cf515c ircd::json: Add overload to insert multiple members. 2023-04-18 19:43:33 -07:00
Jason Volk a10992b813 ircd::json: Split back into tool.h 2023-04-18 19:43:33 -07:00
Jason Volk c77df219b5 ircd::json: Optimize json::type() ABI.
ircd::json: Fix indentation; minor cleanup.
2023-04-17 19:31:44 -07:00
Jason Volk 55a73624d2 ircd:🆑 MESA_GLSL_CACHE_DISABLE is deprecated. 2023-04-16 19:33:31 -07:00
Jason Volk cab2b4c822 github/workflows/docker: Tweak concurrency group; prime images fail fast; comments/cleanup. 2023-04-16 15:42:20 -07:00
Jason Volk 79f5e4fd8d modules/web_hook: Add hook/shot request suite. 2023-04-16 15:42:20 -07:00
Jason Volk 4c06793980 ircd::db: Make SeekForPrev() reachable via internal callstack; remove cruft. 2023-04-16 15:42:20 -07:00
Jason Volk ef27ae50dc ircd::db: Fix workaround for reverse prefix iteration. 2023-04-16 15:42:20 -07:00
Jason Volk 6e7d63ce6d ircd::db: Assert valid iterator prior to all relevant operations. 2023-04-16 15:42:18 -07:00
Jason Volk 73a3a4cb35 docker: Add --no-cache on built stage to force rebuild on runners. 2023-04-13 22:22:11 -07:00
Jason Volk b40c21545a modules/m_command: Add watch support for server-side command line. 2023-04-13 22:21:38 -07:00
Jason Volk b4e75dfdf0 modules/console: Allow wildcard for all users in user read cmd. 2023-04-13 22:21:38 -07:00
Jason Volk ba6030f4ce modules/console: Improve formatting for feds head output when missing local event. 2023-04-13 22:21:19 -07:00
91 changed files with 3635 additions and 1309 deletions

View file

@ -1,31 +1,45 @@
#
# Build docker images
#
name: Docker Images
env:
ctor_id: ${{ vars.DOCKER_ID }}
ctor_url: https://github.com/${{github.repository}}
on:
push:
branches: [master]
workflow_dispatch:
inputs:
features:
type: string
description: JSON array of feature-set names to build images for.
distros:
type: string
description: JSON array of operating system distros to build for.
machines:
type: string
description: JSON array of machines to build for.
toolchains:
type: string
description: JSON array of compiler toolchains to build for.
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
env:
ctor_id: ${{vars.DOCKER_ID}}
ctor_url: https://github.com/${{github.repository}}
jobs:
# Build the base-feature intermediate images (cached and not shipped).
base:
uses: ./.github/workflows/docker_prime.yml
with:
id: ${{github.env.ctor_id}}
url: ${{github.env.ctor_url}}
features: '["base"]'
distros: ${{vars.DOCKER_DISTROS}}
machines: ${{vars.DOCKER_MACHINES}}
distros: ${{github.event.inputs.distros || vars.DOCKER_DISTROS}}
machines: ${{github.event.inputs.machines || vars.DOCKER_MACHINES}}
test: ${{contains(github.events.push.commits[0].message, '[ci test]')}}
# Build the full-feature intermediate images (cached and not shipped).
full:
uses: ./.github/workflows/docker_prime.yml
needs: [base]
@ -33,20 +47,21 @@ jobs:
id: ${{github.env.ctor_id}}
url: ${{github.env.ctor_url}}
features: '["full"]'
distros: ${{vars.DOCKER_DISTROS}}
machines: ${{vars.DOCKER_MACHINES}}
distros: ${{github.event.inputs.distros || vars.DOCKER_DISTROS}}
machines: ${{github.event.inputs.machines || vars.DOCKER_MACHINES}}
test: ${{contains(github.events.push.commits[0].message, '[ci test]')}}
# Build the leaf images (shipped and not cached)
built:
needs: [base, full]
runs-on: ${{matrix.machine}}
strategy:
fail-fast: false
matrix:
feature: ${{fromJSON(vars.DOCKER_FEATURES)}}
distro: ${{fromJSON(vars.DOCKER_DISTROS)}}
machine: ${{fromJSON(vars.DOCKER_MACHINES)}}
toolchain: ${{fromJSON(vars.DOCKER_TOOLCHAINS)}}
feature: ${{fromJSON(github.event.inputs.features || vars.DOCKER_FEATURES)}}
distro: ${{fromJSON(github.event.inputs.distros || vars.DOCKER_DISTROS)}}
machine: ${{fromJSON(github.event.inputs.machines || vars.DOCKER_MACHINES)}}
toolchain: ${{fromJSON(github.event.inputs.toolchains || vars.DOCKER_TOOLCHAINS)}}
exclude:
- distro: alpine-3.17
toolchain: gcc-10 # n/a on distro version

View file

@ -1,3 +1,9 @@
#
# Build intermediate images
#
# Called to build lower-layer images which other images depend on. These are
# cached for use by the next layer but not shipped to users.
#
name: Docker Images Prime
on:
@ -5,32 +11,38 @@ on:
inputs:
id:
type: string
description: Dockerhub acct/repo identity.
url:
type: string
description: Git repository for checkout.
features:
type: string
description: JSON array of feature-set names to build images for.
distros:
type: string
description: JSON array of operating system distros to build for.
machines:
type: string
description: JSON array of machines to build for.
test:
type: boolean
default: false
required: false
description: Echo all docker commands rather than invoking them.
concurrency:
group: ${{github.workflow}}-${{inputs.features}}
cancel-in-progress: false
env:
ctor_id: ${{inputs.id}}
ctor_url: ${{inputs.url}}
concurrency:
group: ${{github.run_id}}
cancel-in-progress: true
jobs:
prime:
runs-on: ${{matrix.machine}}
strategy:
fail-fast: false
fail-fast: true
matrix:
feature: ${{fromJSON(inputs.features)}}
distro: ${{fromJSON(inputs.distros)}}

View file

@ -988,6 +988,7 @@ dnl Compiler specific
AM_COND_IF([CLANG],
[
RB_MAYBE_CWARN([-Werror=return-stack-address], charybdis_cv_c_gcc_w_error_return_stack_address)
RB_MAYBE_CWARN([-Werror=abstract-final-class], charybdis_cv_c_gcc_w_error_abstract_final_class)
RB_MAYBE_CWARN([-Wundefined-reinterpret-cast], charybdis_cv_c_gcc_w_undefined_reinterpret_cast)
RB_MAYBE_CWARN([-Wconditional-uninitialized], charybdis_cv_c_gcc_w_conditional_uninitialized)

View file

@ -131,7 +131,7 @@ build()
# Leaf build; unique to each iteration.
tag="$ctor_acct/$ctor_repo:${distro}-${feature}-built-${toolchain}-${machine}"
arg="$args -t $tag $BASEDIR/${dist_name}/built"
arg="$args --no-cache -t $tag $BASEDIR/${dist_name}/built"
eval "$cmd build $arg"
if test $? -ne 0; then return 1; fi
if test $stage = "built"; then return 0; fi

View file

@ -46,6 +46,8 @@ struct ircd::ctx::future
bool valid() const { return !is(state(), future_state::INVALID); }
bool operator!() const { return !valid(); }
std::exception_ptr eptr() const { return state().eptr; }
string_view what() const { return util::what(eptr()); }
explicit operator T() { return get(); }
template<class U, class time_point> friend bool wait_until(const future<U> &, const time_point &, std::nothrow_t);
@ -75,6 +77,8 @@ struct ircd::ctx::future<void>
bool valid() const { return !is(state(), future_state::INVALID); }
bool operator!() const { return !valid(); }
std::exception_ptr eptr() const { return state().eptr; }
string_view what() const { return util::what(eptr()); }
template<class U, class time_point> friend bool wait_until(const future<U> &, const time_point &, std::nothrow_t);
template<class U, class time_point> friend void wait_until(const future<U> &, const time_point &);

View file

@ -39,20 +39,7 @@ namespace ircd::json
#include "strung.h"
#include "tuple/tuple.h"
#include "stack/stack.h"
// Convenience toolset for higher level operations.
namespace ircd::json
{
strung append(const array &, const string_view &val);
strung prepend(const array &, const string_view &val);
void merge(stack::object &out, const vector &);
strung remove(const object &, const string_view &key);
strung remove(const object &, const size_t &index);
strung insert(const object &, const member &);
strung replace(const object &, const member &);
strung replace(const object &, const members &);
}
#include "tool.h"
// Exports to ircd::
namespace ircd

View file

@ -41,6 +41,10 @@ struct ircd::json::stack::member
member(stack &s, const string_view &name, const json::value &);
template<class... T> member(object &po, const string_view &name, const json::tuple<T...> &t);
template<class... T> member(stack &s, const string_view &name, const json::tuple<T...> &t);
explicit member(object &, const json::object::member &);
explicit member(stack &, const json::object::member &);
explicit member(object &, const json::member &);
explicit member(stack &, const json::member &);
member() = default;
member(const member &) = delete;
member(member &&) noexcept;

View file

@ -105,28 +105,28 @@ template<>
inline ircd::json::stack::array &
ircd::json::stack::stack::top<ircd::json::stack::array>(stack &s)
{
return array::top(s);
return array::top(s);
}
template<>
inline const ircd::json::stack::array &
ircd::json::stack::stack::top<ircd::json::stack::array>(const stack &s)
{
return array::top(s);
return array::top(s);
}
template<>
inline ircd::json::stack::object &
ircd::json::stack::stack::top<ircd::json::stack::object>(stack &s)
{
return object::top(s);
return object::top(s);
}
template<>
inline const ircd::json::stack::object &
ircd::json::stack::stack::top<ircd::json::stack::object>(const stack &s)
{
return object::top(s);
return object::top(s);
}
template<>

44
include/ircd/json/tool.h Normal file
View file

@ -0,0 +1,44 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#pragma once
#define HAVE_IRCD_JSON_TOOL_H
// Convenience toolset for higher level operations.
namespace ircd::json
{
strung append(const array &, const string_view &val);
strung prepend(const array &, const string_view &val);
void merge(stack::object &out, const vector &);
strung remove(const object &, const string_view &key);
strung remove(const object &, const size_t &index);
strung insert(const object &, const members &);
strung insert(const object &, const member &);
strung replace(const object &, const members &);
strung replace(const object &, const member &);
}
inline ircd::json::strung
ircd::json::replace(const object &s,
const json::member &m)
{
return replace(s, json::members{m});
}
inline ircd::json::strung
ircd::json::insert(const object &s,
const json::member &m)
{
return insert(s, json::members{m});
}

View file

@ -29,26 +29,18 @@ namespace ircd::json
/// not use the strict overload.
IRCD_OVERLOAD(strict)
// Determine the type
enum type type(const string_view &);
enum type type(const string_view &, std::nothrow_t);
enum type type(const string_view &, strict_t);
enum type type(const string_view &, strict_t, std::nothrow_t);
// Query if type
bool type(const string_view &, const enum type &, strict_t);
bool type(const string_view &, const enum type &);
// Utils
string_view reflect(const enum type &);
[[gnu::pure]] string_view reflect(const enum type) noexcept;
extern const string_view literal_null;
extern const string_view literal_true;
extern const string_view literal_false;
extern const string_view empty_string;
extern const string_view empty_object;
extern const string_view empty_array;
extern const int64_t undefined_number;
// Determine the type w/ strict correctness (full scan)
[[gnu::pure]] bool type(const string_view &, const enum type, strict_t) noexcept;
[[gnu::pure]] enum type type(const string_view &, strict_t, std::nothrow_t) noexcept;
enum type type(const string_view &, strict_t);
// Determine the type quickly
[[gnu::pure]] bool type(const string_view &, const enum type) noexcept;
[[gnu::pure]] enum type type(const string_view &, std::nothrow_t) noexcept;
enum type type(const string_view &);
}
enum ircd::json::type

View file

@ -36,7 +36,15 @@ namespace ircd::json
void valid(const string_view &);
std::string why(const string_view &);
struct stats extern stats;
extern const string_view literal_null;
extern const string_view literal_true;
extern const string_view literal_false;
extern const string_view empty_string;
extern const string_view empty_object;
extern const string_view empty_array;
extern const int64_t undefined_number;
extern struct stats stats;
}
/// Statistics counter access; unfortunately these cannot participate as

View file

@ -53,11 +53,14 @@ struct ircd::m::signing_key_update
/// Required. The user ID whose cross-signing keys have changed.
json::property<name::user_id, json::string>,
/// Cross signing key
/// Master signing key
json::property<name::master_key, json::object>,
/// Cross signing key
json::property<name::self_signing_key, json::object>
/// Self signing key
json::property<name::self_signing_key, json::object>,
/// User signing key (local only)
json::property<name::user_signing_key, json::object>
>
{
using super_type::tuple;

View file

@ -29,6 +29,29 @@ struct ircd::m::event::append
{
struct opts;
private:
static const event::keys::exclude exclude_keys;
static const event::keys default_keys;
static conf::item<std::string> exclude_types;
static conf::item<bool> info;
static log::log log;
bool is_ignored(const event &, const opts &) const;
bool is_redacted(const event &, const opts &) const;
bool is_invisible(const event &, const opts &) const;
bool is_excluded(const event &, const opts &) const;
bool bundle_replace(json::stack::object &, const event &, const opts &);
void _relations(json::stack::object &, const event &, const opts &);
void _age(json::stack::object &, const event &, const opts &);
void _txnid(json::stack::object &, const event &, const opts &);
void _prev_state(json::stack::object &, const event &, const opts &);
void _unsigned(json::stack::object &, const event &, const opts &);
bool members(json::stack::object &, const event &, const opts &);
bool object(json::stack::array &, const event &, const opts &);
public:
append(json::stack::object &, const event &, const opts &);
append(json::stack::object &, const event &);
append(json::stack::array &, const event &, const opts &);
@ -39,11 +62,11 @@ struct ircd::m::event::append
/// can provide the best result.
struct ircd::m::event::append::opts
{
const event::idx *event_idx {nullptr};
const string_view *client_txnid {nullptr};
const id::user *user_id {nullptr};
const room *user_room {nullptr};
const int64_t *room_depth {nullptr};
event::idx event_idx {0};
string_view client_txnid;
id::user user_id;
id::room user_room_id;
int64_t room_depth {-1L};
const event::keys *keys {nullptr};
const m::event_filter *event_filter {nullptr};
long age {std::numeric_limits<long>::min()};
@ -51,6 +74,8 @@ struct ircd::m::event::append::opts
bool query_prev_state {true};
bool query_redacted {true};
bool query_visible {false};
bool bundle_all {false};
bool bundle_replace {false};
};
inline
@ -64,3 +89,23 @@ ircd::m::event::append::append(json::stack::object &o,
const event &e)
:append{o, e, {}}
{}
inline
ircd::m::event::append::append(json::stack::array &array,
const event &event,
const opts &opts)
:returns<bool>{[this, &array, &event, &opts]
{
return object(array, event, opts);
}}
{}
inline
ircd::m::event::append::append(json::stack::object &object,
const event &event,
const opts &opts)
:returns<bool>{[this, &object, &event, &opts]
{
return members(object, event, opts);
}}
{}

View file

@ -89,6 +89,7 @@ namespace ircd::m
#include "direct_to_device.h"
#include "visible.h"
#include "redacted.h"
#include "replaced.h"
#include "feds.h"
#include "app.h"
#include "bridge.h"

View file

@ -221,4 +221,5 @@ struct ircd::m::name
static constexpr const char *const usage {"usage"};
static constexpr const char *const master_key {"master_key"};
static constexpr const char *const self_signing_key {"self_signing_key"};
static constexpr const char *const user_signing_key {"user_signing_key"};
};

View file

@ -44,9 +44,11 @@ struct ircd::m::relates
const event::idx &, const json::object &, const m::relates_to &
>;
static conf::item<std::string> latest_column;
event::refs refs;
bool match_sender {false};
bool prefetch_depth {false};
bool prefetch_latest {false};
bool prefetch_sender {false};
private:

80
include/ircd/m/replaced.h Normal file
View file

@ -0,0 +1,80 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#pragma once
#define HAVE_IRCD_M_REPLACED_H
namespace ircd::m
{
struct replaced;
}
class ircd::m::replaced
:public returns<event::idx>
{
m::relates relates;
public:
IRCD_OVERLOAD(latest);
replaced(const event::idx &, latest_t);
replaced(const event::idx &);
replaced(const event::id &, latest_t);
replaced(const event::id &);
explicit replaced(const event &, latest_t);
explicit replaced(const event &);
};
inline
ircd::m::replaced::replaced(const event &event)
:replaced{event.event_id}
{}
inline
ircd::m::replaced::replaced(const event &event,
latest_t)
:replaced{event.event_id, latest}
{}
inline
ircd::m::replaced::replaced(const event::id &event_id)
:replaced{index(std::nothrow, event_id)}
{}
inline
ircd::m::replaced::replaced(const event::id &event_id,
latest_t)
:replaced{index(std::nothrow, event_id), latest}
{}
inline
ircd::m::replaced::replaced(const event::idx &event_idx)
:relates
{
.refs = event_idx,
.match_sender = true,
}
{
this->returns::ret = relates.has("m.replace")? -1UL: 0UL;
}
inline
ircd::m::replaced::replaced(const event::idx &event_idx,
latest_t)
:relates
{
.refs = event_idx,
.match_sender = true,
}
{
this->returns::ret = relates.latest("m.replace");
}

View file

@ -31,6 +31,7 @@ namespace ircd::m
bool exists(const id::room_alias &, const bool &remote = false);
bool internal(const id::room &);
bool federated(const id::room &);
bool type(const id::room &, const string_view &);
bool creator(const id::room &, const id::user &);
bool contains(const id::room &, const event::idx &);
bool membership(const room &, const id::user &, const string_view & = "join");
@ -186,9 +187,11 @@ struct ircd::m::room
const vm::copts *const &copts,
const event::fetch::opts *const &fopts = nullptr) noexcept;
room(const id &room_id = {},
room(const id &room_id,
const event::fetch::opts *const &fopts = nullptr) noexcept;
room() = default;
// Index of create event
static event::idx index(const id &, std::nothrow_t);
static event::idx index(const id &);
@ -228,7 +231,9 @@ ircd::m::room::room(const id &room_id,
,event_id{event_id? event::id{event_id} : event::id{}}
,copts{copts}
,fopts{fopts}
{}
{
assert(room_id);
}
inline
ircd::m::room::room(const id &room_id,
@ -238,7 +243,9 @@ noexcept
:room_id{room_id}
,copts{copts}
,fopts{fopts}
{}
{
assert(room_id);
}
inline
ircd::m::room::room(const id &room_id,
@ -246,7 +253,9 @@ ircd::m::room::room(const id &room_id,
noexcept
:room_id{room_id}
,fopts{fopts}
{}
{
assert(room_id);
}
inline ircd::m::room::operator
const ircd::m::room::id &()

View file

@ -48,6 +48,9 @@ struct ircd::m::rooms::opts
/// Room alias prefix search
string_view room_alias;
/// Room type search
string_view room_type;
/// user::rooms convenience
id::user user_id;

View file

@ -13,8 +13,10 @@
struct ircd::m::user::devices
{
struct send;
using closure = std::function<void (const event::idx &, const string_view &)>;
using closure_bool = std::function<bool (const event::idx &, const string_view &)>;
using closure_bool = util::function_bool<const event::idx &, const string_view &>;
m::user user;
@ -36,9 +38,17 @@ struct ircd::m::user::devices
///TODO: XXX junk
static std::map<std::string, long> count_one_time_keys(const m::user &, const string_view &);
static bool update(const device_list_update &);
static bool send(json::iov &content);
devices(const m::user &user)
:user{user}
{}
};
/// Broadcast m.device_list_update.
///
struct ircd::m::user::devices::send
{
send(const m::user::devices &,
const m::id::device &,
const string_view = {});
};

128
include/ircd/m/user/keys.h Normal file
View file

@ -0,0 +1,128 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#pragma once
#define HAVE_IRCD_M_USER_KEYS_H
struct ircd::m::user::keys
{
struct send;
static string_view make_sigs_state_key(const mutable_buffer &, const string_view &tgt, const string_view &src);
static std::tuple<string_view, string_view> unmake_sigs_state_key(const string_view &) noexcept;
m::user::room user_room;
void attach_sigs(json::stack::object &, const json::object &, const user::id &) const;
bool attach_sigs(json::stack::object &, const event::idx &, const user::id &) const;
void append_sigs(json::stack::object &, const json::object &, const user::id &) const;
void append_keys(json::stack::object &, const json::object &, const user::id &) const;
bool append_keys(json::stack::object &, const event::idx &, const user::id &) const;
public:
bool has_device(const string_view &) const;
bool has_cross(const string_view &type) const;
bool has_cross_master() const;
bool has_cross_self() const;
bool has_cross_user() const;
void device(json::stack::object &, const string_view &device_id) const;
void cross(json::stack::object &, const string_view &type) const;
void cross_master(json::stack::object &) const;
void cross_self(json::stack::object &) const;
void cross_user(json::stack::object &) const;
bool claim(json::stack::object &, const string_view &device_id, const string_view &algo) const;
void update(const m::signing_key_update &) const;
keys(const m::user &user)
:user_room{user}
{}
};
struct ircd::m::user::keys::send
{
send(const m::user::keys &,
const string_view = {});
};
inline void
ircd::m::user::keys::cross_user(json::stack::object &out)
const
{
return cross(out, "ircd.cross_signing.user");
}
inline void
ircd::m::user::keys::cross_self(json::stack::object &out)
const
{
return cross(out, "ircd.cross_signing.self");
}
inline void
ircd::m::user::keys::cross_master(json::stack::object &out)
const
{
return cross(out, "ircd.cross_signing.master");
}
inline void
ircd::m::user::keys::cross(json::stack::object &out,
const string_view &type)
const
{
const auto event_idx
{
user_room.get(std::nothrow, type, "")
};
append_keys(out, event_idx, user_room.user.user_id);
}
inline bool
ircd::m::user::keys::has_cross_user()
const
{
return has_cross("ircd.cross_signing.user");
}
inline bool
ircd::m::user::keys::has_cross_self()
const
{
return has_cross("ircd.cross_signing.self");
}
inline bool
ircd::m::user::keys::has_cross_master()
const
{
return has_cross("ircd.cross_signing.master");
}
inline bool
ircd::m::user::keys::has_cross(const string_view &type)
const
{
return user_room.has(type, "");
}
inline bool
ircd::m::user::keys::has_device(const string_view &device_id)
const
{
const m::user::devices devices
{
user_room.user
};
return devices.has(device_id, "keys");
}

View file

@ -52,6 +52,7 @@ struct ircd::m::user
struct tokens;
struct devices;
struct reading;
struct keys;
using id = m::id::user;
using closure = std::function<void (const user &)>;
@ -73,13 +74,17 @@ struct ircd::m::user
event::id::buf deoper();
event::id::buf oper();
user(const id &user_id)
:user_id{user_id}
{}
user(const id &user_id);
user() = default;
};
inline
ircd::m::user::user(const id &user_id)
:user_id{user_id}
{
assert(user_id);
}
inline ircd::m::user::operator
const ircd::m::user::id &()
const
@ -105,3 +110,4 @@ const
#include "tokens.h"
#include "devices.h"
#include "reading.h"
#include "keys.h"

View file

@ -132,11 +132,11 @@ ircd::net::dns::tag::tag(const hostport &hp,
{
this->hp.host =
{
hostbuf, copy(hostbuf, hp.host)
tolower(hostbuf, hp.host)
};
this->hp.service =
{
servicebuf, copy(servicebuf, hp.service)
tolower(servicebuf, hp.service)
};
}

View file

@ -40,6 +40,7 @@ struct ircd::rest::get
:request
{
get(const mutable_buffer &out, const rfc3986::uri &, opts);
get(const mutable_buffer &out, const rfc3986::uri &);
get(const rfc3986::uri &, opts);
};
@ -100,6 +101,10 @@ struct ircd::rest::opts
/// receiving dynamic content. Supply an empty unique_buffer instance.
unique_const_buffer *out {nullptr};
/// Optionally supply the temporary buffer for headers in/out in lieu of
/// any internally allocated.
mutable_buffer buf;
/// Timeout for the yielding/synchronous calls of this interface.
seconds timeout {20s};
@ -177,6 +182,15 @@ ircd::rest::get::get(const rfc3986::uri &uri,
}
{}
inline
ircd::rest::get::get(const mutable_buffer &out,
const rfc3986::uri &uri)
:get
{
out, uri, opts{}
}
{}
inline
ircd::rest::get::get(const mutable_buffer &out,
const rfc3986::uri &uri,

View file

@ -73,16 +73,9 @@ struct ircd::versions
~versions() noexcept;
};
namespace ircd
{
template<>
decltype(versions::allocator)
instance_list<versions>::allocator;
template<>
decltype(versions::list)
instance_list<versions>::list;
}
template<>
decltype(ircd::versions::list)
ircd::instance_list<ircd::versions>::list;
inline ircd::versions::operator
const long &()

View file

@ -192,8 +192,8 @@ ircd::cl::envs
{ "default", "true" },
},
{
{ "name", "MESA_GLSL_CACHE_DISABLE" },
{ "default", "true" },
{ "name", "MESA_SHADER_CACHE_DISABLE" },
{ "default", "true" },
},
{
{ "name", "AMD_DEBUG" },

View file

@ -2358,15 +2358,20 @@ ircd::db::seek(domain::const_iterator_base &it,
{
switch(p)
{
// This is inefficient as per RocksDB's prefix impl.
case pos::BACK:
{
// This is inefficient as per RocksDB's prefix impl. unknown why
// a seek to NEXT is still needed after walking back one.
char buf[512];
string_view key;
assert(bool(it)); do
{
assert(size(it.it->key()) <= sizeof(buf));
key = string_view(buf, copy(buf, slice(it.it->key())));
}
while(seek(it, pos::NEXT));
if(seek(it, pos::PREV))
seek(it, pos::NEXT);
return bool(it);
assert(key);
return seek(it, key);
}
default:
@ -5003,24 +5008,23 @@ ircd::db::_seek(const vector_view<_read_op> &op,
namespace ircd::db
{
static rocksdb::Iterator &_seek_(rocksdb::Iterator &, const pos &);
static rocksdb::Iterator &_seek_(rocksdb::Iterator &, const string_view &);
static rocksdb::Iterator &_seek_lower_(rocksdb::Iterator &, const string_view &);
static rocksdb::Iterator &_seek_upper_(rocksdb::Iterator &, const string_view &);
static bool _seek(database::column &, const pos &, const rocksdb::ReadOptions &, rocksdb::Iterator &it);
static bool _seek(database::column &, const string_view &, const rocksdb::ReadOptions &, rocksdb::Iterator &it);
static rocksdb::Iterator &_seek_(rocksdb::Iterator &, const string_view &, const bool lte);
static bool _seek(database::column &, const pos &, const rocksdb::ReadOptions &, rocksdb::Iterator &it, const bool lte);
static bool _seek(database::column &, const string_view &, const rocksdb::ReadOptions &, rocksdb::Iterator &it, const bool lte);
}
std::unique_ptr<rocksdb::Iterator>
ircd::db::seek(column &column,
const string_view &key,
const gopts &opts)
const gopts &opts,
const bool lte)
{
database &d(column);
database::column &c(column);
std::unique_ptr<rocksdb::Iterator> ret;
const auto ropts(make_opts(opts));
seek(c, key, ropts, ret);
seek(c, key, ropts, ret, lte);
return ret;
}
@ -5029,7 +5033,8 @@ bool
ircd::db::seek(database::column &c,
const pos &p,
const rocksdb::ReadOptions &opts,
std::unique_ptr<rocksdb::Iterator> &it)
std::unique_ptr<rocksdb::Iterator> &it,
const bool lte)
{
const ctx::uninterruptible ui;
const ctx::stack_usage_assertion sua;
@ -5041,31 +5046,33 @@ ircd::db::seek(database::column &c,
it.reset(d.d->NewIterator(opts, cf));
}
return _seek(c, p, opts, *it);
return _seek(c, p, opts, *it, lte);
}
bool
ircd::db::_seek(database::column &c,
const string_view &p,
const rocksdb::ReadOptions &opts,
rocksdb::Iterator &it)
rocksdb::Iterator &it,
const bool lte)
try
{
util::timer timer{util::timer::nostart};
if constexpr(RB_DEBUG_DB_SEEK)
timer = util::timer{};
_seek_(it, p);
_seek_(it, p, lte);
database &d(*c.d);
if constexpr(RB_DEBUG_DB_SEEK)
log::debug
{
log, "[%s] %lu:%lu SEEK %s %s in %ld$us '%s'",
log, "[%s] %lu:%lu SEEK[%s] %s %s in %ld$us '%s'",
name(d),
sequence(d),
sequence(opts.snapshot),
valid(it)? "VALID" : "INVALID",
lte? "LTE"_sv: "GTE"_sv,
valid(it)? "VALID"_sv: "INVALID"_sv,
it.status().ok()? "OK"s: it.status().ToString(),
timer.at<microseconds>().count(),
name(c)
@ -5078,11 +5085,12 @@ catch(const error &e)
const database &d(*c.d);
log::critical
{
log, "[%s][%s] %lu:%lu SEEK key :%s",
log, "[%s][%s] %lu:%lu SEEK[%s] key :%s",
name(d),
name(c),
sequence(d),
sequence(opts.snapshot),
lte? "LTE"_sv: "GTE"_sv,
e.what(),
};
@ -5093,7 +5101,8 @@ bool
ircd::db::_seek(database::column &c,
const pos &p,
const rocksdb::ReadOptions &opts,
rocksdb::Iterator &it)
rocksdb::Iterator &it,
const bool)
try
{
bool valid_it;
@ -5115,7 +5124,7 @@ try
sequence(d),
sequence(opts.snapshot),
reflect(p),
valid_it? "VALID" : "INVALID",
valid_it? "VALID"_sv: "INVALID"_sv,
it.status().ok()? "OK"s: it.status().ToString(),
timer.at<microseconds>().count(),
name(c)
@ -5134,41 +5143,27 @@ catch(const error &e)
sequence(d),
sequence(opts.snapshot),
reflect(p),
it.Valid()? "VALID" : "INVALID",
it.Valid()? "VALID"_sv: "INVALID"_sv,
e.what(),
};
throw;
}
/// Seek to entry NOT GREATER THAN key. That is, equal to or less than key
rocksdb::Iterator &
ircd::db::_seek_lower_(rocksdb::Iterator &it,
const string_view &sv)
{
assert(!ctx::interruptible());
it.SeekForPrev(slice(sv));
return it;
}
/// Seek to entry NOT LESS THAN key. That is, equal to or greater than key
rocksdb::Iterator &
ircd::db::_seek_upper_(rocksdb::Iterator &it,
const string_view &sv)
{
assert(!ctx::interruptible());
it.Seek(slice(sv));
return it;
}
/// Defaults to _seek_upper_ because it has better support from RocksDB.
rocksdb::Iterator &
ircd::db::_seek_(rocksdb::Iterator &it,
const string_view &sv)
const string_view &sv,
const bool lte)
{
return _seek_upper_(it, sv);
assert(!ctx::interruptible());
if(lte)
it.SeekForPrev(slice(sv));
else
it.Seek(slice(sv));
return it;
}
rocksdb::Iterator &
@ -5179,11 +5174,24 @@ ircd::db::_seek_(rocksdb::Iterator &it,
switch(p)
{
case pos::NEXT: it.Next(); break;
case pos::PREV: it.Prev(); break;
case pos::FRONT: it.SeekToFirst(); break;
case pos::BACK: it.SeekToLast(); break;
default:
case pos::NEXT:
assert(valid(it));
it.Next();
break;
case pos::PREV:
assert(valid(it));
it.Prev();
break;
case pos::FRONT:
it.SeekToFirst();
break;
case pos::BACK:
it.SeekToLast();
break;
case pos::END:
{
it.SeekToLast();
@ -5192,6 +5200,10 @@ ircd::db::_seek_(rocksdb::Iterator &it,
break;
}
default:
assert(false);
break;
}
return it;

View file

@ -134,10 +134,8 @@ namespace ircd::db
static void valid_eq_or_throw(const rocksdb::Iterator &, const string_view &);
// [GET] iterator seek suite
template<class pos> static bool seek(database::column &, const pos &, const rocksdb::ReadOptions &, std::unique_ptr<rocksdb::Iterator> &it);
static std::unique_ptr<rocksdb::Iterator> seek(column &, const gopts &);
static std::unique_ptr<rocksdb::Iterator> seek(column &, const string_view &key, const gopts &);
static std::vector<row::value_type> seek(database &, const gopts &);
template<class pos> static bool seek(database::column &, const pos &, const rocksdb::ReadOptions &, std::unique_ptr<rocksdb::Iterator> &it, const bool lte = false);
static std::unique_ptr<rocksdb::Iterator> seek(column &, const string_view &key, const gopts &, const bool lte = false);
static std::pair<string_view, string_view> operator*(const rocksdb::Iterator &);
// [GET] read suite
@ -226,7 +224,9 @@ ircd::db::database::cache final
#else
Status Insert(const Slice &key, void *value, size_t charge, deleter, Handle **, Priority) noexcept override;
#endif
#ifdef IRCD_DB_HAS_CACHE_ITEMHELPER
#if defined(IRCD_DB_HAS_CACHE_ASYNC)
Handle *Lookup(const Slice &key, const CacheItemHelper *, CreateContext *, Priority, Statistics *) noexcept override;
#elif defined(IRCD_DB_HAS_CACHE_ITEMHELPER)
Handle *Lookup(const Slice &key, const CacheItemHelper *, CreateContext *, Priority, bool, Statistics *) noexcept override;
#else
Handle *Lookup(const Slice &key, Statistics *) noexcept override;
@ -265,6 +265,9 @@ ircd::db::database::cache final
#ifdef IRCD_DB_HAS_CACHE_ITEMHELPER
const CacheItemHelper *GetCacheItemHelper(Handle *) const noexcept override;
#endif
#ifdef IRCD_DB_HAS_CACHE_ASYNC
Handle *CreateStandalone(const Slice &, ObjectPtr, const CacheItemHelper *, size_t, bool) noexcept override;
#endif
cache(database *const &,
std::shared_ptr<struct database::stats>,

View file

@ -3321,7 +3321,14 @@ noexcept
return ret;
}
#ifdef IRCD_DB_HAS_CACHE_ITEMHELPER
#if defined(IRCD_DB_HAS_CACHE_ASYNC)
rocksdb::Cache::Handle *
ircd::db::database::cache::Lookup(const Slice &key,
const CacheItemHelper *const helper,
CreateContext *const cc,
Priority pri,
Statistics *const statistics)
#elif defined(IRCD_DB_HAS_CACHE_ITEMHELPER)
rocksdb::Cache::Handle *
ircd::db::database::cache::Lookup(const Slice &key,
const CacheItemHelper *const helper,
@ -3355,7 +3362,9 @@ noexcept
auto *const &ret
{
#ifdef IRCD_DB_HAS_CACHE_ITEMHELPER
#if defined(IRCD_DB_HAS_CACHE_ASYNC)
c->Lookup(key, helper, cc, pri, statistics)
#elif defined(IRCD_DB_HAS_CACHE_ITEMHELPER)
c->Lookup(key, helper, cc, pri, wait, statistics)
#else
c->Lookup(key, s)
@ -3559,6 +3568,20 @@ const noexcept
}
#endif
#if defined(IRCD_DB_HAS_CACHE_ASYNC)
rocksdb::Cache::Handle *
ircd::db::database::cache::CreateStandalone(const Slice &key,
ObjectPtr ptr,
const CacheItemHelper *const helper,
size_t charge,
bool allow_uncharged)
noexcept
{
assert(bool(c));
return c->CreateStandalone(key, ptr, helper, charge, allow_uncharged);
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// database::compaction_filter

View file

@ -193,3 +193,9 @@
|| (ROCKSDB_MAJOR == 8 && ROCKSDB_MINOR == 0 && ROCKSDB_PATCH >= 0)
#define IRCD_DB_HAS_CACHE_WRAPPER
#endif
#if ROCKSDB_MAJOR > 8 \
|| (ROCKSDB_MAJOR == 8 && ROCKSDB_MINOR > 1) \
|| (ROCKSDB_MAJOR == 8 && ROCKSDB_MINOR == 1 && ROCKSDB_PATCH >= 1)
#define IRCD_DB_HAS_CACHE_ASYNC
#endif

View file

@ -604,11 +604,11 @@ ircd::json::replace(const object &s,
{
[](const json::members &r, const object::member &m)
{
return std::any_of(begin(r), end(r), [&m]
(const json::member &r)
{
return string_view{r.first} == m.first;
});
for(const auto &[k, v] : r)
if(string_view{k} == m.first)
return true;
return false;
}
};
@ -634,33 +634,9 @@ ircd::json::replace(const object &s,
};
}
ircd::json::strung
ircd::json::replace(const object &s,
const json::member &m_)
{
if(unlikely(!empty(s) && type(s) != type::OBJECT))
throw type_error
{
"Cannot replace member into JSON of type %s",
reflect(type(s))
};
size_t mctr {0};
auto &mb(member_buffer);
for(const object::member &m : object{s})
if(m.first != string_view{m_.first})
mb.at(mctr++) = member{m};
mb.at(mctr++) = m_;
return strung
{
mb.data(), mb.data() + mctr
};
}
ircd::json::strung
ircd::json::insert(const object &s,
const json::member &m)
const json::members &m)
{
if(unlikely(!empty(s) && type(s) != type::OBJECT))
throw type_error
@ -674,7 +650,9 @@ ircd::json::insert(const object &s,
for(const object::member &m : object{s})
mb.at(mctr++) = member{m};
mb.at(mctr++) = m;
for(const auto &_m : m)
mb.at(mctr++) = _m;
return strung
{
mb.data(), mb.data() + mctr
@ -1734,6 +1712,42 @@ ircd::json::stack::member::member(object &po,
s->append(string_view{tmp, size_t(data(buf) - tmp)});
}
ircd::json::stack::member::member(stack &s,
const json::member &m)
:member
{
stack::top<object>(s), m
}
{
}
ircd::json::stack::member::member(object &po,
const json::member &m)
:member
{
po, string_view{m.first}, m.second
}
{
}
ircd::json::stack::member::member(stack &s,
const json::object::member &om)
:member
{
stack::top<object>(s), om
}
{
}
ircd::json::stack::member::member(object &po,
const json::object::member &om)
:member
{
po, om.first, om.second
}
{
}
ircd::json::stack::member::member(stack &s,
const string_view &name,
const json::value &value)
@ -4754,7 +4768,8 @@ namespace ircd::json::parser
bool
ircd::json::type(const string_view &buf,
const enum type &type)
const enum type type)
noexcept
{
const bool ret
{
@ -4780,6 +4795,7 @@ ircd::json::type(const string_view &buf)
enum ircd::json::type
ircd::json::type(const string_view &buf,
std::nothrow_t)
noexcept
{
enum type ret;
if(!parser::parse(begin(buf), end(buf), parser::type_parse, ret))
@ -4842,8 +4858,9 @@ namespace ircd::json::parser
bool
ircd::json::type(const string_view &buf,
const enum type &type,
const enum type type,
strict_t)
noexcept
{
const bool ret
{
@ -4871,6 +4888,7 @@ enum ircd::json::type
ircd::json::type(const string_view &buf,
strict_t,
std::nothrow_t)
noexcept
{
enum type ret;
if(!parser::parse(begin(buf), end(buf), parser::type_parse_strict, ret))
@ -4880,7 +4898,8 @@ ircd::json::type(const string_view &buf,
}
ircd::string_view
ircd::json::reflect(const enum type &type)
ircd::json::reflect(const enum type type)
noexcept
{
switch(type)
{
@ -4891,8 +4910,6 @@ ircd::json::reflect(const enum type &type)
case STRING: return "STRING";
}
throw type_error
{
"Unknown type %x", uint(type)
};
assert(false);
return "STRING";
}

View file

@ -191,10 +191,10 @@ bool
ircd::net::dns::cache::operator==(const waiter &a, const waiter &b)
noexcept
{
return
a.opts.qtype == b.opts.qtype &&
a.key && b.key &&
a.key == b.key;
return true
&& a.opts.qtype == b.opts.qtype
&& a.key && b.key
&& a.key == b.key;
}
bool
@ -227,7 +227,7 @@ ircd::net::dns::cache::waiter::waiter(const hostport &hp,
{
opts.qtype == 33?
make_SRV_key(keybuf, hp, opts):
strlcpy(keybuf, host(hp))
tolower(keybuf, host(hp))
}
{
this->opts.srv = {};

View file

@ -14,13 +14,25 @@ ircd::rest::request::request(const rfc3986::uri &uri,
if(!opts.remote)
opts.remote = net::hostport{uri};
const unique_mutable_buffer buf
const bool need_alloc
{
empty(opts.sout.head) || empty(opts.sin.head)?
16_KiB: 0_KiB
empty(opts.buf)
&& (empty(opts.sout.head) || empty(opts.sin.head))
};
const unique_mutable_buffer _buf
{
need_alloc? 16_KiB: 0_KiB
};
if(!empty(_buf))
opts.buf = _buf;
window_buffer window
{
opts.buf
};
window_buffer window{buf};
if(empty(opts.sout.head))
{
assert(opts.remote);
@ -80,13 +92,25 @@ ircd::rest::request::request(const mutable_buffer &out,
if(!opts.remote)
opts.remote = net::hostport{uri};
const unique_mutable_buffer buf
const bool need_alloc
{
empty(opts.sout.head) || empty(opts.sin.head)?
16_KiB: 0_KiB
empty(opts.buf)
&& (empty(opts.sout.head) || empty(opts.sin.head))
};
const unique_mutable_buffer _buf
{
need_alloc? 16_KiB: 0_KiB
};
if(!empty(_buf))
opts.buf = _buf;
window_buffer window
{
opts.buf
};
window_buffer window{buf};
if(empty(opts.sout.head))
{
assert(opts.remote);

View file

@ -13,12 +13,12 @@
//
template<>
decltype(ircd::util::instance_list<ircd::versions>::allocator)
decltype(ircd::versions::allocator)
ircd::util::instance_list<ircd::versions>::allocator
{};
template<>
decltype(ircd::util::instance_list<ircd::versions>::list)
decltype(ircd::versions::list)
ircd::util::instance_list<ircd::versions>::list
{
allocator

View file

@ -137,6 +137,7 @@ libircd_matrix_la_SOURCES += user_devices.cc
libircd_matrix_la_SOURCES += user_events.cc
libircd_matrix_la_SOURCES += user_filter.cc
libircd_matrix_la_SOURCES += user_ignores.cc
libircd_matrix_la_SOURCES += user_keys.cc
libircd_matrix_la_SOURCES += user_mitsein.cc
libircd_matrix_la_SOURCES += user_notifications.cc
libircd_matrix_la_SOURCES += user_profile.cc

View file

@ -8,31 +8,25 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
namespace ircd::m
{
extern const event::keys::exclude event_append_exclude_keys;
extern const event::keys event_append_default_keys;
extern conf::item<std::string> event_append_exclude_types;
extern conf::item<bool> event_append_info;
extern log::log event_append_log;
}
decltype(ircd::m::event_append_log)
ircd::m::event_append_log
[[gnu::visibility("hidden")]]
decltype(ircd::m::event::append::log)
ircd::m::event::append::log
{
"m.event.append"
};
decltype(ircd::m::event_append_info)
ircd::m::event_append_info
[[gnu::visibility("hidden")]]
decltype(ircd::m::event::append::info)
ircd::m::event::append::info
{
{ "name", "ircd.m.event.append.info" },
{ "default", false },
{ "persist", false },
};
decltype(ircd::m::event_append_exclude_types)
ircd::m::event_append_exclude_types
[[gnu::visibility("hidden")]]
decltype(ircd::m::event::append::exclude_types)
ircd::m::event::append::exclude_types
{
{ "name", "ircd.m.event.append.exclude.types" },
{ "default", "org.matrix.dummy_event" },
@ -42,8 +36,9 @@ ircd::m::event_append_exclude_types
/// to the client. This mask is applied only if the caller of event::append{}
/// did not supply their mask to apply. It is also inferior to the user's
/// filter if supplied.
decltype(ircd::m::event_append_exclude_keys)
ircd::m::event_append_exclude_keys
[[gnu::visibility("hidden")]]
decltype(ircd::m::event::append::exclude_keys)
ircd::m::event::append::exclude_keys
{
"auth_events",
"hashes",
@ -53,16 +48,17 @@ ircd::m::event_append_exclude_keys
"signatures",
};
decltype(ircd::m::event_append_default_keys)
ircd::m::event_append_default_keys
[[gnu::visibility("hidden")]]
decltype(ircd::m::event::append::default_keys)
ircd::m::event::append::default_keys
{
event_append_exclude_keys
event::append::exclude_keys
};
ircd::m::event::append::append(json::stack::array &array,
const event &event_,
bool
ircd::m::event::append::object(json::stack::array &array,
const event &event,
const opts &opts)
:returns<bool>{[&]
{
assert(array.s);
json::stack::checkpoint cp
@ -70,29 +66,24 @@ ircd::m::event::append::append(json::stack::array &array,
*array.s
};
json::stack::object object
json::stack::object _object
{
array
};
const bool ret
{
append
{
object, event_, opts
}
members(_object, event, opts)
};
cp.committing(ret);
return ret;
}}
{
}
ircd::m::event::append::append(json::stack::object &object,
const event &event,
const opts &opts)
:returns<bool>{[&]
bool
ircd::m::event::append::members(json::stack::object &out,
const event &event,
const opts &opts)
{
// Assertions that the event being appended has some required fields. This
// is a central butt-end test of data coming through the system to here.
@ -101,150 +92,35 @@ ircd::m::event::append::append(json::stack::object &object,
assert(defined(json::get<"sender"_>(event)));
//assert(json::get<"origin_server_ts"_>(event));
//assert(json::get<"origin_server_ts"_>(event) != json::undefined_number);
#if defined(RB_DEBUG)
if(unlikely(!defined(json::get<"type"_>(event))))
return false;
if constexpr(RB_DEBUG_LEVEL)
{
if(unlikely(!defined(json::get<"type"_>(event))))
return false;
if(unlikely(!defined(json::get<"sender"_>(event))))
return false;
#endif
if(unlikely(!defined(json::get<"sender"_>(event))))
return false;
}
if(opts.event_filter && !m::match(*opts.event_filter, event))
return false;
const auto &not_types
{
event_append_exclude_types
};
if(!opts.event_filter && token_exists(not_types, ' ', json::get<"type"_>(event)))
{
log::debug
{
log, "Not sending event %s because type '%s' excluded by configuration.",
string_view{event.event_id},
json::get<"type"_>(event),
};
if(is_excluded(event, opts))
return false;
}
if(opts.query_visible && opts.user_id && !visible(event, *opts.user_id))
{
log::debug
{
log, "Not sending event %s because not visible to %s.",
string_view{event.event_id},
string_view{*opts.user_id},
};
if(is_invisible(event, opts))
return false;
}
const bool has_event_idx
{
opts.event_idx && *opts.event_idx
};
const bool is_state
{
defined(json::get<"state_key"_>(event))
};
const bool query_redacted
{
has_event_idx &&
opts.query_redacted &&
!is_state &&
(!opts.room_depth || *opts.room_depth > json::get<"depth"_>(event))
};
if(query_redacted && m::redacted(*opts.event_idx))
{
log::debug
{
log, "Not sending event %s because redacted.",
string_view{event.event_id},
};
if(is_redacted(event, opts))
return false;
}
const bool has_user
{
opts.user_id && opts.user_room
};
const bool check_ignores
{
has_user && !is_state
};
if(check_ignores && *opts.user_id != json::get<"sender"_>(event))
{
const m::user::ignores ignores
{
*opts.user_id
};
if(ignores.enforce("events") && ignores.has(json::get<"sender"_>(event)))
{
log::debug
{
log, "Not sending event %s because %s is ignored by %s",
string_view{event.event_id},
json::get<"sender"_>(event),
string_view{*opts.user_id}
};
return false;
}
}
const bool sender_is_user
{
has_user && json::get<"sender"_>(event) == *opts.user_id
};
const bool has_client_txnid
{
opts.client_txnid && *opts.client_txnid
};
const auto txnid_idx
{
!has_client_txnid && sender_is_user && opts.query_txnid?
opts.user_room->get(std::nothrow, "ircd.client.txnid", event.event_id):
0UL
};
const bool query_prev_state
{
opts.query_prev_state && has_event_idx && is_state
};
const auto prev_state_idx
{
query_prev_state?
room::state::prev(*opts.event_idx):
0UL
};
#if defined(RB_DEBUG) && 0
if(!has_client_txnid && !txnid_idx && sender_is_user && opts.query_txnid)
log::dwarning
{
log, "Could not find transaction_id for %s from %s in %s",
string_view{event.event_id},
json::get<"sender"_>(event),
json::get<"room_id"_>(event)
};
#endif
if(is_ignored(event, opts))
return false;
// For v3+ events
if(!json::get<"event_id"_>(event))
json::stack::member
{
object, "event_id", event.event_id
out, "event_id", event.event_id
};
// Get the list of properties to send to the client so we can strip
@ -254,11 +130,11 @@ ircd::m::event::append::append(json::stack::object &object,
{
opts.keys?
*opts.keys:
event_append_default_keys
default_keys
};
// Append the event members
for_each(event, [&keys, &object]
for_each(event, [&keys, &out]
(const auto &key, const auto &val_)
{
if(!keys.has(key) && key != "redacts"_sv)
@ -274,70 +150,77 @@ ircd::m::event::append::append(json::stack::object &object,
json::stack::member
{
object, key, val
out, key, val
};
return true;
});
json::stack::object unsigned_
{
object, "unsigned"
};
_unsigned(out, event, opts);
const json::value age
{
// When the opts give an explicit age, use it.
opts.age != std::numeric_limits<long>::min()?
opts.age:
// If we have depth information, craft a value based on the
// distance to the head depth; if this is 0 in riot the event will
// "stick" at the bottom of the timeline. This may be advantageous
// in the future but for now we make sure the result is non-zero.
json::get<"depth"_>(event) >= 0 && opts.room_depth && *opts.room_depth >= 0L?
(*opts.room_depth + 1 - json::get<"depth"_>(event)) + 1:
// We don't have depth information, so we use the origin_server_ts.
// It is bad if it conflicts with other appends in the room which
// did have depth information.
!opts.room_depth && json::get<"origin_server_ts"_>(event)?
ircd::time<milliseconds>() - json::get<"origin_server_ts"_>(event):
// Finally, this special value will eliminate the age altogether
// during serialization.
json::undefined_number
};
json::stack::member
{
unsigned_, "age", age
};
if(has_client_txnid)
json::stack::member
if(unlikely(info))
log::info
{
unsigned_, "transaction_id", *opts.client_txnid
log, "%s %s idx:%lu in %s depth:%ld txnid:%s %s,%s",
string_view{opts.user_id},
string_view{event.event_id},
opts.event_idx,
json::get<"room_id"_>(event),
json::get<"depth"_>(event),
opts.client_txnid,
json::get<"type"_>(event),
json::get<"state_key"_>(event),
};
if(txnid_idx)
m::get(std::nothrow, txnid_idx, "content", [&unsigned_]
(const json::object &content)
{
json::stack::member
{
unsigned_, "transaction_id", unquote(content.get("transaction_id"))
};
});
return true;
}
void
ircd::m::event::append::_unsigned(json::stack::object &out,
const event &event,
const opts &opts)
{
json::stack::object object
{
out, "unsigned"
};
_age(object, event, opts);
_txnid(object, event, opts);
_relations(object, event, opts);
if(defined(json::get<"state_key"_>(event)))
_prev_state(object, event, opts);
}
void
ircd::m::event::append::_prev_state(json::stack::object &out,
const event &event,
const opts &opts)
{
assert(defined(json::get<"state_key"_>(event)));
const bool query_prev_state
{
true
&& opts.event_idx
&& opts.query_prev_state
};
const auto prev_state_idx
{
query_prev_state?
room::state::prev(opts.event_idx):
0UL
};
if(prev_state_idx)
{
m::get(std::nothrow, prev_state_idx, "content", [&unsigned_]
m::get(std::nothrow, prev_state_idx, "content", [&out]
(const json::object &content)
{
json::stack::member
{
unsigned_, "prev_content", content
out, "prev_content", content
};
});
@ -348,7 +231,7 @@ ircd::m::event::append::append(json::stack::object &object,
json::stack::member
{
unsigned_, "replaces_state", json::value
out, "replaces_state", json::value
{
replaces_state_id?
string_view{replaces_state_id}:
@ -356,24 +239,279 @@ ircd::m::event::append::append(json::stack::object &object,
}
};
}
}
if(unlikely(event_append_info))
log::info
void
ircd::m::event::append::_txnid(json::stack::object &out,
const event &event,
const opts &opts)
{
const bool sender_is_user
{
json::get<"sender"_>(event) == opts.user_id
};
const bool query_txnid
{
true
&& !opts.client_txnid
&& opts.query_txnid
&& opts.user_room_id
&& sender_is_user
};
const auto txnid_idx
{
query_txnid?
m::room(opts.user_room_id).get(std::nothrow, "ircd.client.txnid", event.event_id):
0UL
};
if constexpr(RB_DEBUG_LEVEL)
{
const bool missing_txnid
{
event_append_log, "%s %s idx:%lu in %s depth:%ld txnid:%s idx:%lu age:%ld %s,%s",
opts.user_id? string_view{*opts.user_id} : string_view{},
string_view{event.event_id},
opts.event_idx? *opts.event_idx : 0UL,
json::get<"room_id"_>(event),
json::get<"depth"_>(event),
has_client_txnid? *opts.client_txnid : string_view{},
txnid_idx,
int64_t(age),
json::get<"type"_>(event),
json::get<"state_key"_>(event),
true
&& !opts.client_txnid
&& !txnid_idx
&& sender_is_user
&& opts.query_txnid
};
return true;
}}
{
if(unlikely(missing_txnid))
log::dwarning
{
log, "Could not find transaction_id for %s from %s in %s",
string_view{event.event_id},
json::get<"sender"_>(event),
json::get<"room_id"_>(event)
};
}
if(opts.client_txnid)
json::stack::member
{
out, "transaction_id", opts.client_txnid
};
else if(txnid_idx)
m::get(std::nothrow, txnid_idx, "content", [&out]
(const json::object &content)
{
json::stack::member
{
out, "transaction_id", content.get("transaction_id")
};
});
}
void
ircd::m::event::append::_age(json::stack::object &out,
const event &event,
const opts &opts)
{
const json::value age
{
// When the opts give an explicit age, use it.
opts.age != std::numeric_limits<long>::min()?
opts.age:
// If we have depth information, craft a value based on the
// distance to the head depth; if this is 0 in riot the event will
// "stick" at the bottom of the timeline. This may be advantageous
// in the future but for now we make sure the result is non-zero.
json::get<"depth"_>(event) >= 0 && opts.room_depth >= 0L?
(opts.room_depth + 1 - json::get<"depth"_>(event)) + 1:
// We don't have depth information, so we use the origin_server_ts.
// It is bad if it conflicts with other appends in the room which
// did have depth information.
opts.room_depth < 0 && json::get<"origin_server_ts"_>(event)?
ircd::time<milliseconds>() - json::get<"origin_server_ts"_>(event):
// Finally, this special value will eliminate the age altogether
// during serialization.
json::undefined_number
};
json::stack::member
{
out, "age", age
};
}
void
ircd::m::event::append::_relations(json::stack::object &out,
const event &event,
const opts &opts)
{
assert(out.s);
json::stack::checkpoint cp
{
*out.s, false
};
json::stack::object object
{
out, "m.relations"
};
bool commit
{
cp.committing()
};
if(opts.bundle_all || opts.bundle_replace)
commit |= bundle_replace(object, event, opts);
cp.committing(commit);
}
bool
ircd::m::event::append::bundle_replace(json::stack::object &out,
const event &event,
const opts &opts)
{
const m::replaced replaced
{
opts.event_idx, m::replaced::latest
};
const event::idx &replace_idx
{
replaced
};
if(likely(!replace_idx))
return false;
const m::event::fetch replace
{
std::nothrow, replace_idx
};
if(unlikely(!replace.valid))
return false;
json::stack::object object
{
out, "m.replace"
};
object.append(replace);
return true;
}
bool
ircd::m::event::append::is_excluded(const event &event,
const opts &opts)
const
{
const auto &not_types
{
exclude_types
};
const bool ret
{
true
&& !opts.event_filter
&& token_exists(not_types, ' ', json::get<"type"_>(event))
};
if(ret)
log::debug
{
log, "Not sending event %s because type '%s' excluded by configuration.",
string_view{event.event_id},
json::get<"type"_>(event),
};
return ret;
}
bool
ircd::m::event::append::is_invisible(const event &event,
const opts &opts)
const
{
const bool ret
{
true
&& opts.query_visible
&& opts.user_id
&& !visible(event, opts.user_id)
};
if(ret)
log::debug
{
log, "Not sending event %s because not visible to %s.",
string_view{event.event_id},
string_view{opts.user_id},
};
return ret;
}
bool
ircd::m::event::append::is_redacted(const event &event,
const opts &opts)
const
{
const bool ret
{
true
&& opts.event_idx
&& opts.query_redacted
&& !defined(json::get<"state_key"_>(event))
&& opts.room_depth > json::get<"depth"_>(event)
&& m::redacted(opts.event_idx)
};
if(ret)
log::debug
{
log, "Not sending event %s because redacted.",
string_view{event.event_id},
};
return ret;
}
bool
ircd::m::event::append::is_ignored(const event &event,
const opts &opts)
const
{
const bool check_ignores
{
true
&& !defined(json::get<"state_key"_>(event))
&& opts.user_id
&& opts.user_room_id
&& opts.user_id != json::get<"sender"_>(event)
};
if(!check_ignores)
return false;
const m::user::ignores ignores
{
opts.user_id
};
if(ignores.enforce("events") && ignores.has(json::get<"sender"_>(event)))
{
log::debug
{
log, "Not sending event %s because %s is ignored by %s",
string_view{event.event_id},
json::get<"sender"_>(event),
string_view{opts.user_id}
};
return true;
}
return false;
}

View file

@ -132,6 +132,13 @@ ircd::m::id::parser
,"event_id version 4"
};
// de-facto device id
const rule<> device_id
{
device_sigil >> localpart
,"device_id (de facto)"
};
/// (Appendix 4.1) Server Name
/// A homeserver is uniquely identified by its server name. This value
/// is used in a number of identifiers, as described below. The server
@ -159,6 +166,7 @@ ircd::m::id::parser
(prefix >> ':' >> server_name)
| event_id_v4
| event_id_v3
| device_id
,"mxid"
};
@ -584,7 +592,12 @@ ircd::m::id::id(const enum sigil &sigil,
static const auto &dict{rand::dict::alnum};
const mutable_buffer dst{tmp_buf[0], 10};
name = rand::string(dst, dict);
break;
return fmt::sprintf
{
buf, "%c%s",
char(sigil),
name,
};
}
default:

View file

@ -132,8 +132,7 @@ ircd::m::module_names
"client_keys_query",
"client_keys_signatures_upload",
"client_keys_device_signing_upload",
"client_room_keys_version",
"client_room_keys_keys",
"client_room_keys",
"client_presence",
"client_groups",
"client_joined_groups",
@ -158,6 +157,7 @@ ircd::m::module_names
"client_sync_account_data",
"client_sync_device_lists",
"client_sync_device_one_time_keys_count",
"client_sync_device_unused_fallback_key_types",
"client_sync_groups",
"client_sync_presence",
"client_sync_to_device",

View file

@ -200,3 +200,4 @@ constexpr const char *const ircd::m::name::m_in_reply_to;
constexpr const char *const ircd::m::name::usage;
constexpr const char *const ircd::m::name::master_key;
constexpr const char *const ircd::m::name::self_signing_key;
constexpr const char *const ircd::m::name::user_signing_key;

View file

@ -365,8 +365,8 @@ ircd::m::pretty_stateline(std::ostream &out,
<< std::right << " [ "
<< std::setw(30) << type
<< std::left << " | "
<< std::setw(50) << state_key
<< std::left << " ]" << flags << " "
<< std::setw(50) << trunc(state_key, 50)
<< std::left << "]" << flags << " "
<< std::setw(10) << event_idx
<< std::left << " "
<< std::setw(72) << string_view{event.event_id}
@ -382,10 +382,10 @@ ircd::m::pretty_stateline(std::ostream &out,
<< std::right << " "
<< std::setw(9) << json::get<"depth"_>(event)
<< std::right << " [ "
<< std::setw(40) << type
<< std::setw(50) << type
<< std::left << " | "
<< std::setw(56) << state_key
<< std::left << " ]" << flags << " "
<< std::setw(60) << trunc(state_key, 60)
<< std::left << "]" << flags << " "
<< std::setw(10) << event_idx
<< ' '
<< std::left << trunc(content, 80)

View file

@ -8,6 +8,13 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
decltype(ircd::m::relates::latest_column)
ircd::m::relates::latest_column
{
{ "name", "ircd.m.relates.latest_column" },
{ "default", "origin_server_ts" },
};
bool
ircd::m::relates::prefetch(const string_view &type)
const
@ -21,8 +28,8 @@ const
refs.for_each(dbs::ref::M_RELATES, [this, &ret]
(const auto &event_idx, const auto &)
{
if(this->prefetch_depth)
ret |= m::prefetch(event_idx, "depth");
if(this->prefetch_latest)
ret |= m::prefetch(event_idx, string_view{latest_column});
if(this->prefetch_sender || this->match_sender)
ret |= m::prefetch(event_idx, "sender");
@ -87,6 +94,11 @@ ircd::m::relates::latest(const string_view &type,
uint *const at)
const
{
const string_view &column
{
latest_column
};
if(at)
*at = -1;
@ -97,10 +109,10 @@ const
(const event::idx &event_idx, const json::object &, const m::relates_to &)
noexcept
{
int64_t depth{0};
if((depth = m::get(std::nothrow, event_idx, "depth", depth)) > best)
int64_t val{0};
if((val = m::get(std::nothrow, event_idx, column, val)) > best)
{
best = depth;
best = val;
ret = event_idx;
if(at)

View file

@ -892,6 +892,32 @@ ircd::m::join_rule(const room &room,
return join_rule(buf, room) == rule;
}
bool
ircd::m::type(const room::id &room_id,
const string_view &type_)
{
const m::room room
{
room_id
};
const auto event_idx
{
room.get(std::nothrow, "m.room.create", "")
};
return m::query(std::nothrow, event_idx, "content", false, [&type_]
(const json::object &content)
{
const json::string &type
{
content.get("type")
};
return type == type_;
});
}
bool
ircd::m::creator(const room::id &room_id,
const user::id &user_id)

View file

@ -31,7 +31,7 @@ decltype(ircd::m::createroom::version_default)
ircd::m::createroom::version_default
{
{ "name", "ircd.m.createroom.version_default" },
{ "default", "5" },
{ "default", "6" },
};
decltype(ircd::m::createroom::spec_presets)

View file

@ -54,15 +54,14 @@ ircd::m::replace(room::message &msg,
const event::idx &event_idx)
{
// Find the latest edit of this; otherwise stick with the original.
const m::relates relates
const m::replaced replaced
{
.refs = event_idx,
.match_sender = true,
event_idx, m::replaced::latest
};
const event::idx replace_idx
const event::idx &replace_idx
{
relates.latest("m.replace")
replaced
};
if(!replace_idx)

View file

@ -138,6 +138,10 @@ ircd::m::rooms::for_each(const opts &opts,
if(!join_rule(room, opts.join_rule))
return;
if(opts.room_type)
if(!m::type(room_id, opts.room_type))
return;
if(opts.server && opts.request_node_id && my_host(opts.server))
if(!room::aliases(room_id).has_server(opts.server))
return;

View file

@ -8,26 +8,81 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
bool
ircd::m::user::devices::send(json::iov &content)
ircd::m::user::devices::send::send(const m::user::devices &devices,
const m::id::device &device_id,
const string_view room_id)
try
{
assert(content.has("user_id"));
assert(content.has("device_id"));
assert(device_id);
const auto &user_id
{
devices.user.user_id
};
const bool deleted
{
devices.has(device_id)
};
const m::user::keys user_keys
{
user_id
};
const bool has_keys
{
!deleted && user_keys.has_device(device_id)
};
const unique_mutable_buffer keys_buf
{
has_keys? 4_KiB: 0_KiB
};
json::stack keys{keys_buf};
if(has_keys)
{
json::stack::object top{keys};
user_keys.device(top, device_id);
}
// Triggers a devices request from the remote; also see
// modules/federation/user_devices.cc
const long &stream_id
{
1L
};
static const auto stream_id{1L};
json::iov event;
json::iov event, content;
const json::iov::push push[]
{
{ event, { "type", "m.device_list_update" } },
{ event, { "sender", content.at("user_id") } },
{ event, { "sender", user_id } },
{ content, { "deleted", deleted } },
{ content, { "device_id", device_id } },
{ content, { "stream_id", stream_id } },
{ content, { "user_id", user_id } },
};
const json::iov::push push_keys
{
content, has_keys,
{
"keys", [&keys]
{
return keys.completed();
}
}
};
// For diagnostic purposes; usually not defined.
const json::iov::push push_room_id
{
event, m::valid(m::id::ROOM, room_id),
{
"room_id", [&room_id]
{
return room_id;
}
}
};
m::vm::copts opts;
@ -39,8 +94,6 @@ try
{
event, content, opts
};
return true;
}
catch(const ctx::interrupted &)
{
@ -51,12 +104,10 @@ catch(const std::exception &e)
log::error
{
m::log, "Send m.device_list_update for '%s' belonging to %s :%s",
content.at("device_id"),
content.at("user_id"),
string_view{device_id},
string_view{devices.user.user_id},
e.what(),
};
return false;
}
bool
@ -185,21 +236,11 @@ const
m::redact(user_room, user_room.user, event_id, "deleted")
};
if(!my(user))
return true;
json::iov content;
const json::iov::push push[]
{
{ content, { "user_id", user.user_id } },
{ content, { "device_id", id } },
{ content, { "deleted", true } },
};
const bool broadcasted
{
user::devices::send(content)
};
if(my(user))
user::devices::send
{
*this, id
};
return true;
}

429
matrix/user_keys.cc Normal file
View file

@ -0,0 +1,429 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
ircd::m::user::keys::send::send(const m::user::keys &user_keys,
const string_view room_id)
try
{
const auto &user_id
{
user_keys.user_room.user.user_id
};
const unique_mutable_buffer keys_buf[2]
{
{ 4_KiB },
{ 4_KiB },
};
json::stack keys[2]
{
{ keys_buf[0] },
{ keys_buf[1] },
};
// master
{
json::stack::object object{keys[0]};
user_keys.cross_master(object);
}
// self
{
json::stack::object object{keys[1]};
user_keys.cross_self(object);
}
json::iov event, content;
const json::iov::push push[]
{
{ event, { "type", "m.signing_key_update" } },
{ event, { "sender", user_id } },
{ content, { "master_key", keys[0].completed() } },
{ content, { "self_signing_key", keys[1].completed() } },
{ content, { "user_id", user_id } },
};
// For diagnostic purposes; usually not defined.
const json::iov::push push_room_id
{
event, m::valid(m::id::ROOM, room_id),
{
"room_id", [&room_id]
{
return room_id;
}
}
};
m::vm::copts opts;
opts.edu = true;
opts.prop_mask.reset();
opts.prop_mask.set("origin");
opts.notify_clients = false;
m::vm::eval
{
event, content, opts
};
}
catch(const ctx::interrupted &)
{
throw;
}
catch(const std::exception &e)
{
log::error
{
m::log, "Sending m.signing_key_update for %s :%s",
string_view{user_keys.user_room.user.user_id},
e.what(),
};
}
void
ircd::m::user::keys::update(const m::signing_key_update &sku)
const
{
const m::user::id &user_id
{
json::get<"user_id"_>(sku)
};
const m::user::room room
{
user_id
};
const json::object &msk
{
json::get<"master_key"_>(sku)
};
const auto cross_master_id
{
json::get<"master_key"_>(sku)?
m::send(room, user_id, "ircd.cross_signing.master", "", msk):
m::event::id::buf{}
};
const json::object &ssk
{
json::get<"self_signing_key"_>(sku)
};
const auto cross_self_id
{
ssk?
m::send(room, user_id, "ircd.cross_signing.self", "", ssk):
m::event::id::buf{}
};
const json::object &usk
{
json::get<"user_signing_key"_>(sku)
};
const auto cross_user_id
{
usk && my(user_id)?
m::send(room, user_id, "ircd.cross_signing.user", "", usk):
m::event::id::buf{}
};
}
bool
ircd::m::user::keys::claim(json::stack::object &object,
const string_view &device_id,
const string_view &algorithm)
const
{
const fmt::bsprintf<m::event::TYPE_MAX_SIZE> type
{
"ircd.device.one_time_key|%s",
algorithm
};
const m::room::type events
{
user_room, type, { -1UL, -1L }, true
};
return !events.for_each([this, &object, &device_id]
(const string_view &type, const auto &, const m::event::idx &event_idx)
{
if(m::redacted(event_idx))
return true;
const bool match
{
m::query(std::nothrow, event_idx, "state_key", [&device_id]
(const string_view &state_key) noexcept
{
return state_key == device_id;
})
};
if(!match)
return true;
const auto algorithm
{
split(type, '|').second
};
const bool fetched
{
m::get(std::nothrow, event_idx, "content", [&object, &algorithm]
(const json::object &content)
{
json::stack::member
{
object, algorithm, json::object
{
content[""] // ircd.device.* quirk
}
};
})
};
if(!fetched)
return true;
const auto event_id
{
m::event_id(event_idx)
};
const auto redact_id
{
m::redact(user_room, user_room.user, event_id, "claimed")
};
return false;
});
}
void
ircd::m::user::keys::device(json::stack::object &out,
const string_view &device_id)
const
{
const m::user::devices devices
{
user_room.user
};
devices.get(std::nothrow, device_id, "keys", [this, &out, &device_id]
(const auto &, const json::object &device_keys)
{
const auto &user_id
{
user_room.user.user_id
};
for(const auto &member : device_keys)
if(member.first != "signatures")
json::stack::member
{
out, member
};
json::stack::object sigs
{
out, "signatures"
};
json::stack::object user_sigs
{
sigs, user_id
};
attach_sigs(user_sigs, device_keys, user_id);
const m::room::state state
{
user_room
};
state.for_each("ircd.keys.signatures", [this, &user_sigs, &user_id, &device_id]
(const string_view &, const string_view &state_key, const auto &event_idx)
{
const auto &[target, source]
{
unmake_sigs_state_key(state_key)
};
if(target && target != device_id)
return true;
attach_sigs(user_sigs, event_idx, user_id);
return true;
});
});
}
bool
ircd::m::user::keys::append_keys(json::stack::object &out,
const event::idx &event_idx,
const user::id &user_id)
const
{
return m::get(std::nothrow, event_idx, "content", [this, &out, &user_id]
(const json::object &device_keys)
{
append_keys(out, device_keys, user_id);
});
}
void
ircd::m::user::keys::append_keys(json::stack::object &out,
const json::object &device_keys,
const user::id &user_id)
const
{
for(const auto &member : device_keys)
if(member.first != "signatures")
json::stack::member
{
out, member
};
json::stack::object sigs
{
out, "signatures"
};
// signatures of the key's owner
assert(user_room.user.user_id);
append_sigs(sigs, device_keys, user_room.user.user_id);
// signatures of a cross-signer
assert(user_id);
if(user_id != user_room.user.user_id)
append_sigs(sigs, device_keys, user_id);
}
void
ircd::m::user::keys::append_sigs(json::stack::object &out,
const json::object &device_keys,
const user::id &user_id)
const
{
json::stack::object user_sigs
{
out, user_id
};
attach_sigs(user_sigs, device_keys, user_id);
const json::object device_keys_keys
{
device_keys["keys"]
};
const m::room::state state
{
user_room
};
state.for_each("ircd.keys.signatures", [this, &user_sigs, &user_id, &device_keys_keys]
(const string_view &, const string_view &state_key, const auto &event_idx)
{
const auto &[target, source]
{
unmake_sigs_state_key(state_key)
};
for(const auto &[key_id_, key] : device_keys_keys)
{
const auto &key_id
{
split(key_id_, ':').second
};
if(target != key_id)
continue;
attach_sigs(user_sigs, event_idx, user_id);
}
return true;
});
}
bool
ircd::m::user::keys::attach_sigs(json::stack::object &user_sigs,
const event::idx &event_idx,
const user::id &user_id)
const
{
return m::get(std::nothrow, event_idx, "content", [this, &user_sigs, &user_id]
(const json::object &device_sigs)
{
attach_sigs(user_sigs, device_sigs, user_id);
});
}
void
ircd::m::user::keys::attach_sigs(json::stack::object &user_sigs,
const json::object &device_sigs,
const user::id &user_id)
const
{
const json::object device_sigs_sigs
{
device_sigs["signatures"]
};
const json::object device_sigs_user_sigs
{
device_sigs_sigs[user_id]
};
for(const auto &member : device_sigs_user_sigs)
json::stack::member
{
user_sigs, member
};
}
//
// user::keys::sigs
//
std::tuple<ircd::string_view, ircd::string_view>
ircd::m::user::keys::unmake_sigs_state_key(const string_view &state_key)
noexcept
{
const auto &[tgt, src]
{
rsplit(state_key, '%')
};
return std::tuple
{
tgt, src
};
}
ircd::string_view
ircd::m::user::keys::make_sigs_state_key(const mutable_buffer &buf,
const string_view &tgt,
const string_view &src)
{
return fmt::sprintf
{
buf, "%s%s",
string_view{tgt},
tgt != src && src?
string_view{src}:
string_view{},
};
}

View file

@ -443,6 +443,7 @@ client_client_sync_groups_la_SOURCES = client/sync/groups.cc
client_client_sync_to_device_la_SOURCES = client/sync/to_device.cc
client_client_sync_device_lists_la_SOURCES = client/sync/device_lists.cc
client_client_sync_device_one_time_keys_count_la_SOURCES = client/sync/device_one_time_keys_count.cc
client_client_sync_device_unused_fallback_key_types_la_SOURCES = client/sync/device_unused_fallback_key_types.cc
client_module_LTLIBRARIES += \
client/client_sync_account_data.la \
@ -452,6 +453,7 @@ client_module_LTLIBRARIES += \
client/client_sync_to_device.la \
client/client_sync_device_lists.la \
client/client_sync_device_one_time_keys_count.la \
client/client_sync_device_unused_fallback_key_types.la \
###
# client/sync/rooms/
@ -506,12 +508,14 @@ client_module_LTLIBRARIES += \
# client/room_keys/
#
client_client_room_keys_version_la_SOURCES = client/room_keys/version.cc
client_client_room_keys_keys_la_SOURCES = client/room_keys/keys.cc
client_client_room_keys_la_SOURCES = \
client/room_keys/keys.cc \
client/room_keys/version.cc \
client/room_keys/room_keys.cc \
###
client_module_LTLIBRARIES += \
client/client_room_keys_version.la \
client/client_room_keys_keys.la \
client/client_room_keys.la \
###
#

View file

@ -51,6 +51,16 @@ ircd::m::client_capabilities::get(client &client,
mods::loaded("client_account")
};
const bool m_set_displayname__enabled
{
mods::loaded("client_profile")
};
const bool m_set_avatar_url__enabled
{
mods::loaded("client_profile")
};
const json::value default_room_version
{
string_view{m::createroom::version_default}, json::STRING
@ -66,6 +76,14 @@ ircd::m::client_capabilities::get(client &client,
{
{ "enabled", m_change_password__enabled },
}},
{ "m.set_displayname", json::members
{
{ "enabled", m_set_displayname__enabled },
}},
{ "m.set_avatar_url", json::members
{
{ "enabled", m_set_avatar_url__enabled },
}},
{ "m.room_versions", json::members
{
{ "default", default_room_version },

View file

@ -60,7 +60,7 @@ ircd::m::groups::handle_post(client &client,
return resource::response
{
client, http::CREATED, json::members
client, http::OK, json::members
{
{ "group_id", group_id }
},

View file

@ -103,7 +103,7 @@ post__createroom(client &client,
top.~object();
return m::resource::response
{
client, http::CREATED, json::object
client, http::OK, json::object
{
out.completed()
}

View file

@ -274,6 +274,11 @@ _get_device(json::stack::object &obj,
obj, "device_id", device_id
};
json::stack::member
{
obj, "user_id", devices.user.user_id
};
devices.get(std::nothrow, device_id, "display_name", [&obj]
(const auto &, const string_view &value)
{

View file

@ -376,10 +376,10 @@ append_event(json::stack::array &out,
{
out, event,
{
.event_idx = &event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
},
};
}

View file

@ -42,10 +42,11 @@ recv_response(const string_view &,
const system_point &);
static void
recv_responses(query_map &,
recv_responses(const host_users_map &,
query_map &,
failure_map &,
json::stack::object &,
const milliseconds &);
const system_point &);
static void
handle_failures(const failure_map &,
@ -137,6 +138,11 @@ post__keys_claim(client &client,
send_requests(map, buffers, failures)
};
const system_point timedout
{
ircd::now<system_point>() + timeout
};
m::resource::response::chunked response
{
client, http::OK
@ -152,9 +158,9 @@ post__keys_claim(client &client,
out
};
recv_responses(queries, failures, top, timeout);
recv_responses(map, queries, failures, top, timedout);
handle_failures(failures, top);
return {};
return response;
}
void
@ -174,14 +180,22 @@ handle_failures(const failure_map &failures,
}
void
recv_responses(query_map &queries,
recv_responses(const host_users_map &map,
query_map &queries,
failure_map &failures,
json::stack::object &out,
const milliseconds &timeout)
const system_point &timeout)
{
const system_point timedout
static const user_devices_map empty;
const auto it
{
ircd::now<system_point>() + timeout
map.find(origin(m::my()))
};
const user_devices_map &self
{
it != end(map)? it->second: empty
};
json::stack::object one_time_keys
@ -189,13 +203,55 @@ recv_responses(query_map &queries,
out, "one_time_keys"
};
for(auto &[remote, request] : queries)
// local handle
for(const auto &[user_id, reqs] : self)
{
assert(!failures.count(remote));
if(failures.count(remote))
continue;
const m::user::keys keys
{
user_id
};
recv_response(remote, request, failures, one_time_keys, timedout);
json::stack::object user_object
{
one_time_keys, user_id
};
for(const auto &[device_id, algorithm] : json::object(reqs))
{
json::stack::object device_object
{
user_object, device_id
};
keys.claim(device_object, device_id, json::string(algorithm));
}
}
// remote handle
while(!queries.empty())
{
auto next
{
ctx::when_any(begin(queries), end(queries), []
(auto &it) -> m::fed::user::keys::claim &
{
return it->second;
})
};
const bool ok
{
next.wait_until(timeout, std::nothrow)
};
const auto it(next.get());
const unwind remove{[&queries, &it]
{
queries.erase(it);
}};
auto &[remote, request] {*it};
recv_response(remote, request, failures, one_time_keys, timeout);
}
}
@ -223,14 +279,22 @@ try
};
for(const auto &[user_id, keys] : one_time_keys)
{
if(m::user::id(user_id).host() != remote)
continue;
json::stack::member
{
object, user_id, json::object{keys}
object, user_id, json::object
{
keys
}
};
}
}
catch(const std::exception &e)
{
log::error
log::derror
{
m::log, "user keys claim from %s :%s",
remote,
@ -247,7 +311,8 @@ send_requests(const host_users_map &hosts,
{
query_map ret;
for(const auto &[remote, user_devices] : hosts)
send_request(remote, user_devices, failures, buffers, ret);
if(likely(!my_host(remote)))
send_request(remote, user_devices, failures, buffers, ret);
return ret;
}
@ -292,7 +357,7 @@ try
}
catch(const std::exception &e)
{
log::error
log::derror
{
m::log, "user keys claim to %s for %zu users :%s",
remote,

View file

@ -66,52 +66,26 @@ ircd::m::post_keys_device_signing_upload(client &client,
auth["password"]
};
const m::user::room room
const m::user user
{
request.user_id
};
if(!room.user.is_password(password))
if(!user.is_password(password))
throw m::ACCESS_DENIED
{
"Incorrect password."
};
const json::object &msk
const m::user::keys keys
{
request["master_key"]
user
};
const auto master_id
{
msk?
send(room, request.user_id, "ircd.device.signing.master", "", msk):
event::id::buf{}
};
m::signing_key_update sku{request};
json::get<"user_id"_>(sku) = request.user_id;
const json::object &ssk
{
request["self_signing_key"]
};
const auto self_signing_id
{
ssk?
send(room, request.user_id, "ircd.device.signing.self", "", ssk):
event::id::buf{}
};
const json::object &usk
{
request["user_signing_key"]
};
const auto user_signing_id
{
usk?
send(room, request.user_id, "ircd.device.signing.user", "", usk):
event::id::buf{}
};
keys.update(sku);
return resource::response
{

View file

@ -19,8 +19,32 @@ namespace
using buffer_list = std::vector<unique_buffer<mutable_buffer>>;
}
static host_users_map
parse_user_request(const json::object &device_keys);
static void
handle_cross_keys(const m::resource::request &,
const user_devices_map &,
query_map &,
failure_map &,
json::stack::object &,
const string_view &);
static void
handle_device_keys(const m::resource::request &,
const user_devices_map &,
query_map &,
failure_map &,
json::stack::object &);
static void
handle_responses(const m::resource::request &,
const host_users_map &,
query_map &,
failure_map &,
json::stack::object &);
static void
handle_errors(const m::resource::request &,
query_map &,
failure_map &);
static bool
send_request(const string_view &,
@ -34,19 +58,8 @@ send_requests(const host_users_map &,
buffer_list &,
failure_map &);
static void
recv_response(const m::resource::request &,
const string_view &,
m::fed::user::keys::query &,
failure_map &,
json::stack::object &);
static void
recv_responses(const m::resource::request &,
query_map &,
failure_map &,
json::stack::object &,
const milliseconds &);
static host_users_map
parse_user_request(const json::object &device_keys);
static void
handle_failures(const failure_map &,
@ -148,6 +161,20 @@ post__keys_query(client &client,
send_requests(map, buffers, failures)
};
auto responses
{
ctx::when_all(begin(queries), end(queries), []
(auto &it) -> m::fed::user::keys::query &
{
return it->second;
})
};
const bool all_good
{
responses.wait_until(now<system_point>() + timeout, std::nothrow)
};
m::resource::response::chunked response
{
client, http::OK
@ -163,9 +190,9 @@ post__keys_query(client &client,
out
};
recv_responses(request, queries, failures, top, timeout);
handle_responses(request, map, queries, failures, top);
handle_failures(failures, top);
return {};
return response;
}
void
@ -177,216 +204,39 @@ handle_failures(const failure_map &failures,
out, "failures"
};
for(const auto &p : failures)
{
const string_view &hostname(p.first);
const std::exception_ptr &eptr(p.second);
for(const auto &[remote, eptr] : failures)
json::stack::member
{
response_failures, hostname, what(eptr)
response_failures, remote, what(eptr)
};
}
}
void
recv_responses(const m::resource::request &client_request,
query_map &queries,
failure_map &failures,
json::stack::object &out,
const milliseconds &timeout)
try
host_users_map
parse_user_request(const json::object &device_keys)
{
const system_point timedout
host_users_map ret;
for(const auto &member : device_keys)
{
ircd::now<system_point>() + timeout
};
const m::user::id &user_id(member.first);
const json::array &device_ids(member.second);
const string_view &host(user_id.host());
while(!queries.empty())
{
static const auto dereferencer{[]
(auto &it) -> m::fed::user::keys::query &
auto it(ret.lower_bound(host));
if(it == end(ret) || it->first != host)
it = ret.emplace_hint(it, host, user_devices_map{});
user_devices_map &users(it->second);
{
return it->second;
}};
auto it(users.lower_bound(user_id));
if(it == end(users) || it->first != user_id)
it = users.emplace_hint(it, user_id, json::array{});
auto next
{
ctx::when_any(begin(queries), end(queries), dereferencer)
};
next.wait_until(timedout); // throws on timeout
const auto it{next.get()};
const unwind remove{[&queries, &it]
{
queries.erase(it);
}};
const auto &remote(it->first);
auto &request(it->second);
assert(!failures.count(remote));
if(failures.count(remote))
continue;
recv_response(client_request, remote, request, failures, out);
}
}
catch(const std::exception &)
{
for(const auto &[remote, request] : queries)
failures.emplace(remote, std::current_exception());
}
void
recv_response(const m::resource::request &client_request,
const string_view &remote,
m::fed::user::keys::query &request,
failure_map &failures,
json::stack::object &out)
try
{
const auto code
{
request.get()
};
const json::object response
{
request
};
// device_keys
{
json::stack::object object
{
out, "device_keys"
};
const json::object &device_keys
{
response["device_keys"]
};
for(const auto &[_user_id, device_keys] : device_keys)
{
const m::user::id &user_id
{
_user_id
};
json::stack::object user_object
{
object, user_id
};
for(const auto &[device_id, keys] : json::object(device_keys))
json::stack::member
{
user_object, device_id, keys
};
if(!empty(device_ids))
it->second = device_ids;
}
}
// master_keys
{
json::stack::object object
{
out, "master_keys"
};
const json::object &master_keys
{
response["master_keys"]
};
for(const auto &[_user_id, master_key] : master_keys)
{
const m::user::id &user_id
{
_user_id
};
json::stack::member
{
object, user_id, json::object
{
master_key
}
};
}
}
// self_signing_keys
{
json::stack::object object
{
out, "self_signing_keys"
};
const json::object &self_signing_keys
{
response["self_signing_keys"]
};
for(const auto &[_user_id, self_signing_key] : self_signing_keys)
{
const m::user::id &user_id
{
_user_id
};
json::stack::member
{
object, user_id, json::object
{
self_signing_key
}
};
}
}
// user_signing_keys
{
json::stack::object object
{
out, "user_signing_keys"
};
const json::object &user_signing_keys
{
response["user_signing_keys"]
};
for(const auto &[_user_id, user_signing_key] : user_signing_keys)
{
const m::user::id &user_id
{
_user_id
};
if(client_request.user_id != _user_id)
continue;
json::stack::member
{
object, user_id, json::object
{
user_signing_key
}
};
}
}
}
catch(const std::exception &e)
{
log::error
{
m::log, "user keys query from %s :%s",
remote,
e.what()
};
failures.emplace(remote, std::current_exception());
return ret;
}
query_map
@ -395,12 +245,9 @@ send_requests(const host_users_map &hosts,
failure_map &failures)
{
query_map ret;
for(const auto &pair : hosts)
{
const string_view &remote(pair.first);
const user_devices_map &user_devices(pair.second);
send_request(remote, user_devices, failures, buffers, ret);
}
for(const auto &[remote, user_devices] : hosts)
if(likely(!my_host(remote)))
send_request(remote, user_devices, failures, buffers, ret);
return ret;
}
@ -441,43 +288,281 @@ try
return true;
}
catch(const ctx::interrupted &e)
{
throw;
}
catch(const std::exception &e)
{
log::error
failures.emplace(remote, std::current_exception());
log::derror
{
m::log, "user keys query to %s :%s",
remote,
e.what()
};
failures.emplace(remote, std::current_exception());
return false;
}
host_users_map
parse_user_request(const json::object &device_keys)
void
handle_responses(const m::resource::request &request,
const host_users_map &map,
query_map &queries,
failure_map &failures,
json::stack::object &out)
{
host_users_map ret;
for(const auto &member : device_keys)
static const user_devices_map empty;
const auto it
{
const m::user::id &user_id(member.first);
const json::array &device_ids(member.second);
const string_view &host(user_id.host());
map.find(origin(m::my()))
};
auto it(ret.lower_bound(host));
if(it == end(ret) || it->first != host)
it = ret.emplace_hint(it, host, user_devices_map{});
const user_devices_map &self
{
it != end(map)? it->second: empty
};
user_devices_map &users(it->second);
handle_errors(request, queries, failures);
handle_device_keys(request, self, queries, failures, out);
handle_cross_keys(request, self, queries, failures, out, "master_keys");
handle_cross_keys(request, self, queries, failures, out, "self_signing_keys");
handle_cross_keys(request, self, queries, failures, out, "user_signing_keys");
}
void
handle_errors(const m::resource::request &request,
query_map &queries,
failure_map &failures)
{
auto it(begin(queries));
while(it != end(queries))
{
const auto &[remote, query] {*it};
if(query.eptr())
{
auto it(users.lower_bound(user_id));
if(it == end(users) || it->first != user_id)
it = users.emplace_hint(it, user_id, json::array{});
failures.emplace(remote, query.eptr());
it = queries.erase(it);
}
else ++it;
}
}
if(!empty(device_ids))
it->second = device_ids;
void
handle_device_keys(const m::resource::request &request,
const user_devices_map &self,
query_map &queries,
failure_map &failures,
json::stack::object &out)
{
json::stack::object object
{
out, "device_keys"
};
// local handle
for(const auto &[user_id, device_ids] : self)
{
const m::user::keys keys
{
user_id
};
json::stack::object user_object
{
object, user_id
};
if(empty(json::array(device_ids)))
{
const m::user::devices devices
{
user_id
};
devices.for_each([&user_object, &keys]
(const auto &, const string_view &device_id)
{
json::stack::object device_object
{
user_object, device_id
};
keys.device(device_object, device_id);
});
}
else for(const json::string device_id : json::array(device_ids))
{
json::stack::object device_object
{
user_object, device_id
};
keys.device(device_object, device_id);
}
}
return ret;
// remote handle
for(const auto &[remote, query] : queries) try
{
const json::object response
{
query.in.content
};
const json::object &device_keys
{
response["device_keys"]
};
for(const auto &[user_id, device_keys] : device_keys)
{
if(m::user::id(user_id).host() != remote)
continue;
json::stack::object user_object
{
object, user_id
};
for(const auto &[device_id, keys] : json::object(device_keys))
json::stack::member
{
user_object, device_id, keys
};
}
}
catch(const ctx::interrupted &)
{
throw;
}
catch(const std::exception &e)
{
failures.emplace(remote, std::current_exception());
log::derror
{
m::log, "Processing device_keys response from '%s' :%s",
remote,
e.what(),
};
}
}
static std::tuple<string_view, bool>
translate_cross_type(const string_view &name)
{
bool match_user;
string_view cross_type;
switch(match_user = false; hash(name))
{
case "master_keys"_:
cross_type = "ircd.cross_signing.master";
break;
case "self_signing_keys"_:
cross_type = "ircd.cross_signing.self";
break;
case "user_signing_keys"_:
cross_type = "ircd.cross_signing.user";
match_user = true;
break;
};
assert(cross_type);
return
{
cross_type, match_user
};
}
void
handle_cross_keys(const m::resource::request &request,
const user_devices_map &self,
query_map &queries,
failure_map &failures,
json::stack::object &out_,
const string_view &name)
{
const auto &[cross_type, match_user]
{
translate_cross_type(name)
};
json::stack::object out
{
out_, name
};
// local handle
for(const auto &[user_id, device_ids] : self)
{
if(match_user && request.user_id != user_id)
continue;
const m::user::keys keys
{
user_id
};
if(!keys.has_cross(cross_type))
continue;
json::stack::object user_object
{
out, user_id
};
keys.cross(user_object, cross_type);
}
// remote handle
for(auto &[remote, query] : queries) try
{
if(match_user && request.user_id.host() != remote)
continue;
const json::object response
{
query.in.content
};
const json::object &object
{
response[name]
};
for(const auto &[user_id, keys] : object)
{
if(m::user::id(user_id).host() != remote)
continue;
if(match_user && request.user_id != user_id)
continue;
json::stack::member
{
out, user_id, json::object
{
keys
}
};
}
}
catch(const ctx::interrupted &)
{
throw;
}
catch(const std::exception &e)
{
failures.emplace(remote, std::current_exception());
log::derror
{
m::log, "Processing %s response from '%s' :%s",
name,
remote,
e.what(),
};
}
}

View file

@ -43,71 +43,37 @@ ircd::m::resource::response
ircd::m::post_keys_signatures_upload(client &client,
const resource::request &request)
{
const m::device::id::buf device_id
const auto src_dev
{
m::user::tokens::device(request.access_token)
user::tokens::device(std::nothrow, request.access_token)
};
const m::user::room user_room
for(const auto &[user_id_, device_keys_] : request)
{
request.user_id
};
for(const auto &[_user_id, devices_keys_] : request)
{
if(!valid(m::id::USER, _user_id))
continue;
if(_user_id != request.user_id)
throw m::ACCESS_DENIED
{
"Uploading for user %s by %s not allowed or supported",
_user_id,
string_view{request.user_id},
};
const json::object device_keys
{
device_keys_
};
const m::user::id user_id
{
_user_id
user_id_
};
const m::user::devices devices
const user::room user_room
{
user_id
};
const json::object &devices_keys
for(const auto &[tgt_id, keys] : device_keys)
{
devices_keys_
};
for(const auto &[_device_id, device_keys_] : devices_keys)
{
const m::device_keys device_keys
char state_key_buf[512];
const string_view state_key
{
device_keys_
user::keys::make_sigs_state_key(state_key_buf, tgt_id, src_dev)
};
if(json::get<"device_id"_>(device_keys) != _device_id)
throw m::BAD_REQUEST
{
"device_id '%s' does not match object property name '%s'",
json::get<"device_id"_>(device_keys),
_device_id,
};
if((false) && _device_id != device_id) // is this the "cross-sign?" gotta find out!
throw m::ACCESS_DENIED
{
"device_id '%s' does not match your current device_id '%s'",
_device_id,
string_view{device_id},
};
const bool set
{
devices.set(_device_id, "signatures", device_keys_)
};
send(user_room, request.user_id, "ircd.keys.signatures", state_key, keys);
}
}

View file

@ -121,12 +121,6 @@ post__login_password(client &client,
m::id::device::buf{m::id::generate, my_host()}
};
if(!my(device_id))
throw m::UNSUPPORTED
{
"Device ID's with foreign hostparts are not supported."
};
char access_token_buf[32];
const string_view access_token
{

View file

@ -206,7 +206,7 @@ ircd::m::get_notifications(client &client,
{
event_object, event,
{
.event_idx = &event_idx,
.event_idx = event_idx,
.keys = &notification_event_keys,
//.query_txnid = false,
//.query_prev_state = false,

View file

@ -134,6 +134,7 @@ get__publicrooms(client &client,
opts.lower_bound = true;
opts.room_id = since;
opts.request_user_id = request.user_id;
opts.room_type = json::string{filter["room_type"]};
if(m::valid(m::id::USER, search_term))
opts.user_id = search_term;

View file

@ -148,7 +148,7 @@ try
// Send response to user
return m::resource::response
{
client, http::CREATED, response
client, http::OK, response
};
}
catch(const m::INVALID_MXID &e)
@ -191,7 +191,7 @@ post__register_guest(client &client,
return m::resource::response
{
client, http::CREATED,
client, http::OK,
{
{ "user_id", user_id },
{ "home_server", my_host() },
@ -243,7 +243,7 @@ try
// Send response to user
return m::resource::response
{
client, http::CREATED, response
client, http::OK, response
};
}
catch(const m::INVALID_MXID &e)

View file

@ -8,10 +8,10 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "room_keys.h"
namespace ircd::m
{
static string_view make_state_key(const mutable_buffer &, const string_view &, const string_view &, const event::idx &);
static resource::response _get_room_keys_keys(client &, const resource::request &, const room::state &, const event::idx &, const string_view &, const string_view &);
static void _get_room_keys_keys(client &, const resource::request &, const room::state &, const event::idx &, const string_view &, json::stack::object &);
static resource::response get_room_keys_keys(client &, const resource::request &);
@ -21,18 +21,14 @@ namespace ircd::m
static resource::response put_room_keys_keys(client &, const resource::request &);
extern resource::method room_keys_keys_put;
static event::id::buf delete_room_keys_key(client &, const resource::request &, const room &, const event::idx &);
static event::id::buf delete_room_keys_key(client &, const resource::request &, const room &, const room::id &, const string_view &, const event::idx &);
static resource::response delete_room_keys_keys(client &, const resource::request &);
extern resource::method room_keys_keys_delete;
extern resource room_keys_keys;
}
ircd::mapi::header
IRCD_MODULE
{
"Client (undocumented) :e2e Room Keys Keys"
};
decltype(ircd::m::room_keys_keys)
ircd::m::room_keys_keys
{
@ -79,7 +75,7 @@ ircd::m::delete_room_keys_keys(client &client,
const event::idx version
{
request.query.at<event::idx>("version")
request.query.get<event::idx>("version", 0)
};
const m::user::room user_room
@ -92,15 +88,104 @@ ircd::m::delete_room_keys_keys(client &client,
user_room
};
if(!room_id && !session_id)
{
state.for_each("ircd.room_keys.key", [&client, &request, &user_room, &version]
(const string_view &, const string_view &state_key, const event::idx &event_idx)
{
const auto &[room_id, session_id, _version]
{
unmake_state_key(state_key)
};
if(version && _version != lex_cast(version))
return true;
delete_room_keys_key(client, request, user_room, event_idx);
return true;
});
}
else if(!session_id)
{
state.for_each("ircd.room_keys.key", [&client, &request, &user_room, &version, &room_id]
(const string_view &, const string_view &state_key, const event::idx &event_idx)
{
const auto &[_room_id, session_id, _version]
{
unmake_state_key(state_key)
};
if(version && _version != lex_cast(version))
return true;
if(_room_id != room_id)
return true;
delete_room_keys_key(client, request, user_room, event_idx);
return true;
});
}
else delete_room_keys_key(client, request, user_room, room_id, session_id, version);
const auto &[count, etag]
{
count_etag(state, version)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
return resource::response
{
client, json::members
{
{ "count", count },
{ "etag", _etag },
}
};
}
ircd::m::event::id::buf
ircd::m::delete_room_keys_key(client &client,
const resource::request &request,
const room &user_room,
const room::id &room_id,
const string_view &session_id,
const event::idx &version)
{
char state_key_buf[event::STATE_KEY_MAX_SIZE];
const string_view state_key
{
make_state_key(state_key_buf, room_id, session_id, version)
};
const room::state state
{
user_room
};
const auto event_idx
{
state.get(std::nothrow, "ircd.room_keys.key", state_key)
};
if(!event_idx)
return {};
return delete_room_keys_key(client, request, user_room, event_idx);
}
ircd::m::event::id::buf
ircd::m::delete_room_keys_key(client &client,
const resource::request &request,
const room &user_room,
const event::idx &event_idx)
{
const auto event_id
{
m::event_id(state.get("ircd.room_keys.key", state_key))
m::event_id(event_idx)
};
const auto redact_id
@ -108,10 +193,7 @@ ircd::m::delete_room_keys_keys(client &client,
m::redact(user_room, request.user_id, event_id, "deleted by client")
};
return resource::response
{
client, http::OK
};
return redact_id;
}
//
@ -160,6 +242,16 @@ ircd::m::put_room_keys_keys(client &client,
request.query.at<event::idx>("version")
};
const m::user::room user_room
{
request.user_id
};
const m::room::state state
{
user_room
};
if(!room_id && !session_id)
{
const json::object &rooms
@ -167,9 +259,16 @@ ircd::m::put_room_keys_keys(client &client,
request["rooms"]
};
for(const auto &[room_id, sessions] : rooms)
for(const auto &[session_id, session] : json::object(sessions))
for(const auto &[room_id, room_data] : rooms)
{
const json::object sessions
{
json::object(room_data)["sessions"]
};
for(const auto &[session_id, session] : sessions)
put_room_keys_keys_key(client, request, room_id, session_id, version, session);
}
}
else if(!session_id)
{
@ -183,9 +282,23 @@ ircd::m::put_room_keys_keys(client &client,
}
else put_room_keys_keys_key(client, request, room_id, session_id, version, request);
const auto &[count, etag]
{
count_etag(state, version)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
return resource::response
{
client, http::OK
client, json::members
{
{ "count", count },
{ "etag", _etag },
}
};
}
@ -320,12 +433,12 @@ ircd::m::get_room_keys_keys(client &client,
state.for_each("ircd.room_keys.key", [&client, &request, &state, &version, &rooms, &last_room]
(const string_view &, const string_view &state_key, const event::idx &)
{
const auto &room_id
const auto &[room_id, _session_id, _version]
{
token(state_key, ":::", 0)
unmake_state_key(state_key)
};
if(!m::valid(id::ROOM, room_id))
if(_version != lex_cast(version))
return true;
if(room_id == last_room)
@ -359,29 +472,28 @@ ircd::m::_get_room_keys_keys(client &client,
state.for_each("ircd.room_keys.key", [&room_id, &version, &sessions]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
string_view part[3]; const auto parts
const auto &[_room_id, _session_id, _version]
{
tokens(state_key, ":::", part)
unmake_state_key(state_key)
};
const auto &_room_id{part[0]};
const auto &_session_id{part[1]};
const auto &_version{part[2]};
if(!m::valid(id::ROOM, _room_id))
return true;
if(_room_id != room_id)
return true;
if(_version != lex_cast<event::idx>(version))
if(_version != lex_cast(version))
return true;
m::get(std::nothrow, event_idx, "content", [&sessions, &_session_id]
const string_view &session_id
{
_session_id
};
m::get(std::nothrow, event_idx, "content", [&sessions, &session_id]
(const json::object &session)
{
json::stack::member
{
sessions, _session_id, session
sessions, session_id, session
};
});
@ -419,18 +531,3 @@ ircd::m::_get_room_keys_keys(client &client,
return {}; // responded from closure or thrown
}
ircd::string_view
ircd::m::make_state_key(const mutable_buffer &buf,
const string_view &room_id,
const string_view &session_id,
const event::idx &version)
{
return fmt::sprintf
{
buf, "%s:::%s:::%u",
room_id,
session_id,
version,
};
}

View file

@ -0,0 +1,94 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "room_keys.h"
ircd::mapi::header
IRCD_MODULE
{
"Client :e2e Room Keys"
};
std::tuple<int64_t, int64_t>
ircd::m::count_etag(const room::state &state,
const event::idx &version)
{
char version_buf[64];
const auto version_str
{
lex_cast(version, version_buf)
};
uint64_t count(0), etag(0);
state.for_each("ircd.room_keys.key", [&]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
const auto &[room_id, session_id, _version_str]
{
unmake_state_key(state_key)
};
if(_version_str != version_str)
return true;
etag += event_idx;
count += 1;
return true;
});
return
{
int64_t(count),
int64_t(etag),
};
}
std::tuple<ircd::string_view, ircd::string_view, ircd::string_view>
ircd::m::unmake_state_key(const string_view &state_key)
{
assert(state_key);
string_view part[3];
const auto parts
{
tokens(state_key, ":::", part)
};
assert(parts == 3);
if(unlikely(!m::valid(id::ROOM, part[0])))
part[0] = {};
if(unlikely(!lex_castable<ulong>(part[2])))
part[2] = {};
return std::make_tuple
(
part[0], part[1], part[2]
);
}
ircd::string_view
ircd::m::make_state_key(const mutable_buffer &buf,
const string_view &room_id,
const string_view &session_id,
const event::idx &version)
{
assert(room_id);
assert(m::valid(id::ROOM, room_id));
assert(session_id);
assert(session_id != "sessions");
assert(version != 0);
return fmt::sprintf
{
buf, "%s:::%s:::%u",
room_id,
session_id,
version,
};
}

View file

@ -0,0 +1,18 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#pragma GCC visibility push(hidden)
namespace ircd::m
{
string_view make_state_key(const mutable_buffer &, const string_view &, const string_view &, const event::idx &);
std::tuple<string_view, string_view, string_view> unmake_state_key(const string_view &);
std::tuple<int64_t, int64_t> count_etag(const room::state &, const event::idx &version);
}
#pragma GCC visibility pop

View file

@ -8,6 +8,8 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "room_keys.h"
namespace ircd::m
{
static resource::response get_room_keys_version(client &, const resource::request &);
@ -25,12 +27,6 @@ namespace ircd::m
extern resource room_keys_version;
}
ircd::mapi::header
IRCD_MODULE
{
"Client (undocumented) :e2e Room Keys Version"
};
decltype(ircd::m::room_keys_version)
ircd::m::room_keys_version
{
@ -209,11 +205,6 @@ ircd::m::put_room_keys_version(client &client,
event_idx,
};
const auto event_id
{
m::event_id(event_idx)
};
const json::string &algorithm
{
request["algorithm"]
@ -224,13 +215,36 @@ ircd::m::put_room_keys_version(client &client,
request["auth_data"]
};
//
// TODO: XXX
//
const auto event_id
{
m::event_id(event_idx)
};
const json::member relates[]
{
{ "event_id", event_id },
{ "rel_type", "m.replace" },
};
const json::strung content
{
json::insert(request, json::members
{
{ "m.relates_to", relates }
})
};
const auto update_id
{
m::send(user_room, request.user_id, "ircd.room_keys.version", json::object
{
content
})
};
return resource::response
{
client, http::NOT_IMPLEMENTED
client, http::OK
};
}
@ -301,9 +315,36 @@ ircd::m::get_room_keys_version(client &client,
"No version found.",
};
m::get(event_idx, "content", [&client, &event_idx]
const m::replaced latest_idx
{
event_idx, m::replaced::latest
};
const event::idx version_idx
{
latest_idx?
event::idx{latest_idx}:
event_idx
};
const m::room::state state
{
user_room
};
m::get(version_idx, "content", [&client, &event_idx, &state]
(const json::object &content)
{
const auto &[count, etag]
{
count_etag(state, event_idx)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
const json::value version
{
lex_cast(event_idx), json::STRING
@ -313,9 +354,11 @@ ircd::m::get_room_keys_version(client &client,
{
client, json::members
{
{ "version", version },
{ "algorithm", content["algorithm"] },
{ "auth_data", content["auth_data"] },
{ "count", count },
{ "etag", _etag },
{ "version", version },
}
};
});

View file

@ -132,10 +132,10 @@ get__context(client &client,
{
_event, event,
{
.event_idx = &event.event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event.event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
}
};
}
@ -179,10 +179,10 @@ get__context(client &client,
{
array, event,
{
.event_idx = &event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
.query_txnid = true,
}
};
@ -233,10 +233,10 @@ get__context(client &client,
{
array, event,
{
.event_idx = &event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
.query_txnid = true,
}
};
@ -301,10 +301,10 @@ get__context(client &client,
{
array, event,
{
.event_idx = &event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
.query_txnid = false,
}
};

View file

@ -46,8 +46,32 @@ get__event(client &client,
event_id, fopts
};
const unique_mutable_buffer buf
{
m::event::MAX_SIZE
};
json::stack out{buf};
{
json::stack::object top{out};
m::event::append
{
top, event,
{
.event_idx = event.event_idx,
.user_id = request.user_id,
.query_prev_state = false,
.query_redacted = false,
.query_visible = false,
}
};
};
return m::resource::response
{
client, event.source
client, json::object
{
out.completed()
}
};
}

View file

@ -202,10 +202,10 @@ get__initialsync_local(client &client,
{
state, state_event,
{
.event_idx = &event_idx,
.user_id = &user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
.query_txnid = false,
}
};
@ -255,14 +255,17 @@ get__initialsync_local(client &client,
if(!visible(event, user.user_id))
continue;
m::event::append(chunk, event,
m::event::append
{
.event_idx = &event_idx,
.user_id = &user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.query_txnid = true,
});
chunk, event,
{
.event_idx = event_idx,
.user_id = user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
.query_txnid = true,
}
};
}
}

View file

@ -165,8 +165,8 @@ get__members(client &client,
{
chunk, event,
{
.event_idx = &event_idx,
.user_id = &request.user_id,
.event_idx = event_idx,
.user_id = request.user_id,
.query_txnid = false,
.query_prev_state = false,
.query_redacted = false,

View file

@ -152,10 +152,10 @@ get__messages(client &client,
{
chunk, event,
{
.event_idx = &event_idx,
.user_id = &user_room.user.user_id,
.user_room = &user_room,
.room_depth = &room_depth,
.event_idx = event_idx,
.user_id = user_room.user.user_id,
.user_room_id = user_room.room_id,
.room_depth = room_depth,
}
}
};

View file

@ -60,9 +60,11 @@ get__relations(client &client,
"relation rel_type path parameter required"
};
const string_view &rel_type
const string_view rel_type
{
url::decode(rel_type_buf, request.parv[3])
request.parv.size() > 3?
url::decode(rel_type_buf, request.parv[3]):
string_view{}
};
// Get the alleged type path parameter.
@ -75,9 +77,11 @@ get__relations(client &client,
"relation ?type? path parameter required"
};
const string_view &type
const string_view type
{
url::decode(type_buf, request.parv[4])
request.parv.size() > 4?
url::decode(type_buf, request.parv[4]):
string_view{}
};
const auto event_idx
@ -170,8 +174,8 @@ relations_chunk_append(client &client,
{
chunk, event,
{
.event_idx = &event_idx,
.user_id = &request.user_id,
.event_idx = event_idx,
.user_id = request.user_id,
.query_txnid = false,
}
};

View file

@ -206,8 +206,8 @@ append_event(const m::resource::request &request,
{
array, event,
{
.event_idx = &event_idx,
.user_id = &request.user_id,
.event_idx = event_idx,
.user_id = request.user_id,
.query_txnid = false,
.query_prev_state = false,
.query_redacted = false,

View file

@ -550,8 +550,8 @@ try
{
result_event, event,
{
.event_idx = &result.event_idx,
.user_id = &query.user_id,
.event_idx = result.event_idx,
.user_id = query.user_id,
.event_filter = &event_filter,
.query_prev_state = false,
.query_visible = true,
@ -599,8 +599,8 @@ try
{
events_before, event,
{
.event_idx = &event_idx,
.user_id = &query.user_id,
.event_idx = event_idx,
.user_id = query.user_id,
.event_filter = &event_filter,
.query_prev_state = false,
.query_visible = true,
@ -628,8 +628,8 @@ try
{
events_after, event,
{
.event_idx = &event_idx,
.user_id = &query.user_id,
.event_idx = event_idx,
.user_id = query.user_id,
.event_filter = &event_filter,
.query_prev_state = false,
.query_visible = true,

View file

@ -38,7 +38,21 @@ ircd::m::sync::device_lists_linear(data &data)
assert(data.event);
const m::event &event{*data.event};
if(!startswith(json::get<"type"_>(event), "ircd.device"))
const bool including
{
false
|| startswith(json::get<"type"_>(event), "ircd.device")
|| startswith(json::get<"type"_>(event), "ircd.keys.signatures")
};
const bool excluding
{
false
|| startswith(json::get<"type"_>(event), "ircd.device.one_time_key")
};
if(!including || excluding)
return false;
const m::user sender
@ -56,7 +70,9 @@ ircd::m::sync::device_lists_linear(data &data)
const bool changed
{
mitsein.has(data.user, "join")
false
|| sender == data.user.user_id
|| mitsein.has(data.user, "join")
};
const bool left
@ -67,6 +83,11 @@ ircd::m::sync::device_lists_linear(data &data)
if(!changed && !left)
return false;
json::stack::object device_lists
{
*data.out, "device_lists"
};
json::stack::array array
{
*data.out, left? "left": "changed"

View file

@ -0,0 +1,64 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
namespace ircd::m::sync
{
static bool device_unused_fallback_key_types_polylog(data &);
static bool device_unused_fallback_key_types_linear(data &);
extern item device_unused_fallback_key_types;
}
ircd::mapi::header
IRCD_MODULE
{
"Client Sync :Device Unused Fallback Key Types"
};
decltype(ircd::m::sync::device_unused_fallback_key_types)
ircd::m::sync::device_unused_fallback_key_types
{
"device_unused_fallback_key_types",
device_unused_fallback_key_types_polylog,
device_unused_fallback_key_types_linear
};
bool
ircd::m::sync::device_unused_fallback_key_types_linear(data &data)
{
if(!data.device_id)
return false;
if(!data.event || !data.event->event_id)
return false;
if(!startswith(json::get<"type"_>(*data.event), "ircd.device"))
return false;
if(json::get<"room_id"_>(*data.event) != data.user_room)
return false;
json::stack::array array
{
*data.out, "device_unused_fallback_key_types"
};
array.append("signed_curve25519");
return true;
}
bool
ircd::m::sync::device_unused_fallback_key_types_polylog(data &data)
{
if(!data.device_id)
return false;
return false;
}

View file

@ -221,10 +221,10 @@ ircd::m::sync::room_state_linear_events(data &data)
{
array, event,
{
.event_idx = &event_idx,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = event_idx,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
.query_txnid = false,
.query_prev_state = true,
}
@ -265,10 +265,10 @@ ircd::m::sync::room_state_linear_events(data &data)
{
array, *data.event,
{
.event_idx = &data.event_idx,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = data.event_idx,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
.query_txnid = false,
.query_prev_state = true,
}
@ -377,10 +377,10 @@ ircd::m::sync::room_state_polylog_events(data &data)
{
array, event,
{
.event_idx = &event_idx,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = event_idx,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
.query_txnid = false,
.query_prev_state = false,
}
@ -517,10 +517,10 @@ ircd::m::sync::room_state_phased_events(data &data)
{
array, event,
{
.event_idx = &event_idx,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = event_idx,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
.query_txnid = false,
.query_prev_state = true,
}
@ -664,10 +664,10 @@ ircd::m::sync::room_state_phased_member_events(data &data,
{
array, event,
{
.event_idx = &sender_idx,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = sender_idx,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
.query_txnid = false,
.query_prev_state = false,
}

View file

@ -212,11 +212,11 @@ ircd::m::sync::room_timeline_linear(data &data)
{
array, *data.event,
{
.event_idx = &data.event_idx,
.client_txnid = &data.client_txnid,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = data.event_idx,
.client_txnid = data.client_txnid,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
}
};
}
@ -289,11 +289,11 @@ ircd::m::sync::_room_timeline_linear_command(data &data)
{
array, *data.event,
{
.event_idx = &data.event_idx,
.client_txnid = &data.client_txnid,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = data.event_idx,
.client_txnid = data.client_txnid,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
}
};
}
@ -400,11 +400,11 @@ ircd::m::sync::_room_timeline_polylog_events(data &data,
{
array, event,
{
.event_idx = &event_idx,
.client_txnid = &data.client_txnid,
.user_id = &data.user.user_id,
.user_room = &data.user_room,
.room_depth = &data.room_depth,
.event_idx = event_idx,
.client_txnid = data.client_txnid,
.user_id = data.user.user_id,
.user_room_id = data.user_room.room_id,
.room_depth = data.room_depth,
}
};
}

View file

@ -122,7 +122,7 @@ post__filter(client &client,
return m::resource::response
{
client, http::CREATED,
client, http::OK,
{
{ "filter_id", filter_id }
}

View file

@ -43,17 +43,17 @@ get_user(client &client,
url::decode(user_id, request.parv[0])
};
if(request.user_id != user_id)
throw m::UNSUPPORTED
{
"Getting user data as someone else is not yet supported"
};
const string_view &cmd
{
request.parv[1]
};
if(request.user_id != user_id && !request.bridge_id)
throw m::UNSUPPORTED
{
"Getting user data as someone else is only for bridges."
};
if(cmd == "filter")
return get__filter(client, request, user_id);
@ -93,10 +93,10 @@ post_user(client &client,
url::decode(user_id, request.parv[0])
};
if(request.user_id != user_id)
if(request.user_id != user_id && !request.bridge_id)
throw m::UNSUPPORTED
{
"Posting user data as someone else is not yet supported"
"Posting user data as someone else is only for bridges."
};
const string_view &cmd
@ -140,10 +140,10 @@ put_user(client &client,
url::decode(user_id, request.parv[0])
};
if(request.user_id != user_id)
if(request.user_id != user_id && !request.bridge_id)
throw m::UNSUPPORTED
{
"Putting user data as someone else is not yet supported"
"Putting user data as someone else is only for bridges."
};
if(request.parv.size() < 2)
@ -193,10 +193,10 @@ delete_user(client &client,
url::decode(user_id, request.parv[0])
};
if(request.user_id != user_id)
if(request.user_id != user_id && !request.bridge_id)
throw m::UNSUPPORTED
{
"Deleting user data as someone else is not yet supported"
"Deleting user data as someone else is only for bridges."
};
if(request.parv.size() < 2)

View file

@ -85,6 +85,7 @@ ircd::m::client_versions::versions_default
" v1.3"
" v1.4"
" v1.5"
" v1.6"
};
/// Note this conf item doesn't persist to and from the database, which means
@ -240,4 +241,13 @@ ircd::m::client_versions::append_unstable_features(client &client,
bool(e2ee_forced_trusted_private)
}
};
// Supports filtering of /publicRooms by room type as per MSC3827
json::stack::member
{
out, "org.matrix.msc3827.stable", json::value
{
true
}
};
}

View file

@ -11455,31 +11455,47 @@ console_cmd__room__count(opt &out, const string_view &line)
bool
console_cmd__room__events(opt &out, const string_view &line)
{
const params param{line, " ",
const params param_any{line, " ",
{
"room_id", "depth|-limit", "order", "limit"
}};
const params param_type{line, " ",
{
"room_id", "type", "depth|-limit", "order", "limit"
}};
const bool use_type
{
param_type["type"] && !lex_castable<int64_t>(param_type["type"])
};
// decide which argument overload to use
const params &param
{
use_type? param_type : param_any
};
const auto &room_id
{
m::room_id(param.at(0))
m::room_id(param.at("room_id"))
};
const int64_t depth
{
param.at<int64_t>(1, std::numeric_limits<int64_t>::max())
param.at<int64_t>("depth|-limit", std::numeric_limits<int64_t>::max())
};
const char order
{
param.at(2, "b"_sv).at(0)
param.at("order", "b"_sv).at(0)
};
ssize_t limit
{
depth < 0?
std::abs(depth):
param.at(3, ssize_t(32))
param.at("depth|-limit", ssize_t(32))
};
const m::room room
@ -11493,15 +11509,21 @@ console_cmd__room__events(opt &out, const string_view &line)
};
m::event::fetch event;
for(; it && limit > 0; order == 'b'? --it : ++it, --limit)
for(; it && limit > 0; order == 'b'? --it : ++it)
{
if(!seek(std::nothrow, event, it.event_idx()))
continue;
if(use_type)
if(!globular_imatch(param["type"])(json::get<"type"_>(event)))
continue;
out
<< std::left << std::setw(10) << it.event_idx() << " "
<< pretty_oneline(event)
<< std::endl;
--limit;
}
return true;
@ -13312,9 +13334,9 @@ console_cmd__user__read(opt &out, const string_view &line)
"user_id", "room_id", "limit"
}};
const m::user::id user_id
const auto user_id
{
param.at("user_id")
param.at("user_id", "*"_sv)
};
const auto room_id
@ -13324,6 +13346,11 @@ console_cmd__user__read(opt &out, const string_view &line)
m::room::id::buf{}
};
const bool all_users
{
param["user_id"] == "*"
};
const bool all_rooms
{
param["room_id"] == "*"
@ -13340,14 +13367,9 @@ console_cmd__user__read(opt &out, const string_view &line)
param["room_id"] == "***"
};
size_t limit
ssize_t limit
{
param.at("limit", 32UL)
};
const m::user::room user_room
{
user_id
param.at("limit", 32L)
};
const m::event::closure each_event{[&out]
@ -13411,19 +13433,35 @@ console_cmd__user__read(opt &out, const string_view &line)
out << std::endl;
}};
if(all_rooms)
if(all_rooms && !all_users)
{
const m::user::room user_room
{
user_id
};
const m::room::state state
{
user_room
};
state.for_each("ircd.read", each_event);
state.for_each("ircd.read", m::event::closure_bool{[&each_event, &limit]
(const m::event &event)
{
each_event(event);
return --limit > 0;
}});
return true;
}
if(eye_track)
if(eye_track && !all_users)
{
const m::user::room user_room
{
user_id
};
const m::room::type type
{
user_room, "ircd.read"
@ -13440,14 +13478,19 @@ console_cmd__user__read(opt &out, const string_view &line)
if(likely(event.valid))
each_event(event);
return --limit;
return --limit > 0;
});
return true;
}
if(fully_read)
if(fully_read && !all_users)
{
const m::user::room user_room
{
user_id
};
const m::room::type type
{
user_room, "ircd.account_data!", { -1UL, -1UL }, true
@ -13468,29 +13511,55 @@ console_cmd__user__read(opt &out, const string_view &line)
return true;
each_event(event);
return --limit;
return --limit > 0;
});
return true;
}
const m::room::state::space space
if(!all_users)
{
user_room
};
space.for_each("ircd.read", room_id, [&each_event, &limit]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx) -> bool
{
const m::event::fetch event
const m::user::room user_room
{
std::nothrow, event_idx
user_id
};
if(likely(event.valid))
each_event(event);
const m::room::state::space space
{
user_room
};
return --limit;
space.for_each("ircd.read", room_id, [&each_event, &limit]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx) -> bool
{
const m::event::fetch event
{
std::nothrow, event_idx
};
if(likely(event.valid))
each_event(event);
return --limit > 0;
});
}
const m::events::range range
{
-1UL, 0UL
};
m::events::for_each(range, [&each_event, &limit]
(const m::event::idx &seq, const m::event &event)
{
if(json::get<"type"_>(event) != "ircd.read")
return true;
if(!my(event))
return true;
each_event(event);
return --limit > 0;
});
return true;
@ -13894,7 +13963,7 @@ console_cmd__user__tokens(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "clear"
"user_id"
}};
const m::user user
@ -13902,27 +13971,11 @@ console_cmd__user__tokens(opt &out, const string_view &line)
param.at("user_id")
};
const bool clear
{
param["clear"] == "clear"
};
const m::user::tokens tokens
{
user
};
if(clear)
{
const size_t count
{
tokens.del("Invalidated by administrator console.")
};
out << "Invalidated " << count << std::endl;
return true;
}
tokens.for_each([&out]
(const m::event::idx &event_idx, const string_view &token)
{
@ -13963,6 +14016,33 @@ console_cmd__user__tokens(opt &out, const string_view &line)
return true;
}
bool
console_cmd__user__tokens__clear(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id"
}};
const m::user user
{
param.at("user_id")
};
const m::user::tokens tokens
{
user
};
const size_t count
{
tokens.del("Invalidated by administrator console.")
};
out << "Invalidated " << count << std::endl;
return true;
}
bool
console_cmd__user__profile(opt &out, const string_view &line)
{
@ -14324,11 +14404,11 @@ console_cmd__user__devices(opt &out, const string_view &line)
}
bool
console_cmd__user__devices__update(opt &out, const string_view &line)
console_cmd__user__devices__delete(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "device_id", "deleted"
"user_id", "device_id"
}};
const m::user::id &user_id
@ -14338,12 +14418,7 @@ console_cmd__user__devices__update(opt &out, const string_view &line)
const string_view &device_id
{
param.at("device_id")
};
const bool deleted
{
param["deleted"] == "deleted"
param.at("device_id", string_view{})
};
const m::user::devices devices
@ -14351,20 +14426,84 @@ console_cmd__user__devices__update(opt &out, const string_view &line)
user_id
};
json::iov content;
const json::iov::push push[]
if(device_id)
{
{ content, { "user_id", user_id } },
{ content, { "device_id", device_id } },
{ content, { "deleted", deleted } },
devices.del(device_id);
out << device_id << std::endl;
return true;
}
devices.for_each([&out, &devices]
(const auto &event_idx, const string_view &device_id)
{
devices.del(device_id);
out << device_id << std::endl;
return true;
});
return true;
}
bool
console_cmd__user__devices__update(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "device_id", "room_id"
}};
const m::user::id &user_id
{
param.at("user_id")
};
const bool broadcasted
const string_view &device_id
{
m::user::devices::send(content)
param.at("device_id", "*"_sv)
};
out << "done" << std::endl;
const m::room::id::buf room_id
{
m::valid(m::id::ROOM, param["room_id"])?
m::room_id(param["room_id"]):
m::room::id::buf{}
};
const m::user::devices devices
{
user_id
};
const auto update{[&out, &devices, &user_id, &room_id]
(const auto &device_id)
{
m::user::devices::send
{
devices, device_id, room_id
};
out
<< "broadcast: "
<< device_id
<< std::endl;
}};
const bool found
{
!devices.for_each([&update, &device_id]
(const auto &, const string_view &_device_id)
{
if(device_id != "*" && _device_id != device_id)
return true;
update(_device_id);
return _device_id != device_id; // false to break
})
};
if(device_id != "*" && !found)
update(device_id);
return true;
}
@ -14376,6 +14515,43 @@ console_id__device(opt &out,
return true;
}
bool
console_cmd__user__keys__update(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "room_id"
}};
const m::user::id &user_id
{
param.at("user_id")
};
const m::room::id::buf room_id
{
m::valid(m::id::ROOM, param["room_id"])?
m::room_id(param["room_id"]):
m::room::id::buf{}
};
const m::user::keys keys
{
user_id
};
m::user::keys::send
{
keys, room_id
};
out
<< "broadcast: "
<< user_id
<< std::endl;
return true;
}
bool
console_cmd__user__ignores(opt &out, const string_view &line)
{
@ -15058,7 +15234,7 @@ console_cmd__feds__head(opt &out, const string_view &line)
if(prev_event.valid)
out << pretty_oneline(prev_event);
else
out << string_view{prev_event_id};
out << result.request->room_id << ' ' << prev_event_id;
out << std::endl;
}
@ -17084,7 +17260,7 @@ console_cmd__fed__user__devices(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "remote"
"user_id", "remote", "op"
}};
const m::user::id &user_id
@ -17097,6 +17273,11 @@ console_cmd__fed__user__devices(opt &out, const string_view &line)
param.at("remote", user_id.host())
};
const bool raw
{
has(param["op"], "raw")
};
m::fed::user::devices::opts opts;
opts.remote = remote;
@ -17121,6 +17302,14 @@ console_cmd__fed__user__devices(opt &out, const string_view &line)
request
};
if(raw)
{
out
<< string_view{response}
<< std::endl;
return true;
}
const string_view stream_id
{
unquote(response["stream_id"])
@ -17151,16 +17340,23 @@ console_cmd__fed__user__keys__query(opt &out, const string_view &line)
param.at("user_id")
};
const string_view &device_id
{
param.at("device_id", string_view{})
};
const string_view remote
{
param.at("remote", user_id.host())
};
const bool raw
{
param["device_id"] == "raw"
};
const string_view device_id
{
!raw?
param.at("device_id", string_view{}):
string_view{}
};
m::fed::user::opts opts;
opts.remote = remote;
@ -17185,6 +17381,14 @@ console_cmd__fed__user__keys__query(opt &out, const string_view &line)
request
};
if(raw)
{
out
<< string_view{response}
<< std::endl;
return true;
}
const json::object &device_keys
{
response["device_keys"]
@ -18148,7 +18352,7 @@ console_cmd__redact__last(opt &out, const string_view &line)
<< redact_id
<< " redacted "
<< event_id
;
<< std::endl;
return --count > 0;
@ -18157,6 +18361,93 @@ console_cmd__redact__last(opt &out, const string_view &line)
return true;
}
bool
console_cmd__redact__state(opt &out, const string_view &line)
{
const params param{line, " ",
{
"room_id", "redactor", "type", "state_key"
}};
const m::room::id::buf room_id
{
m::room_id(param.at("room_id"))
};
const m::user::id redactor
{
param.at("redactor")
};
const auto type
{
param["type"]
};
const auto state_key
{
param["state_key"]
};
const m::room room
{
room_id
};
if(state_key)
{
const auto event_idx
{
room.get(type, state_key)
};
const auto event_id
{
m::event_id(std::nothrow, event_idx)
};
const auto redact_id
{
m::redact(room, redactor, event_id, "console")
};
out
<< redact_id
<< " redacted "
<< event_id
<< std::endl;
return true;
}
const m::room::state state
{
room
};
state.for_each(type, [&out, &room, &redactor]
(const auto &, const auto &state_key, const m::event::idx &event_idx)
{
const auto event_id
{
m::event_id(std::nothrow, event_idx)
};
const auto redact_id
{
m::redact(room, redactor, event_id, "console")
};
out
<< redact_id
<< " redacted "
<< event_id
<< std::endl;
return true;
});
return true;
}
//
// well-known
//
@ -18240,6 +18531,35 @@ console_cmd__well_known__matrix__server(opt &out, const string_view &line)
return true;
}
bool
console_cmd__well_known__matrix__client(opt &out, const string_view &line)
{
const params param{line, " ",
{
"remote"
}};
const fmt::bsprintf<512> url
{
"https://%s/.well-known/matrix/client",
param.at("remote"),
};
char buf[1024];
const json::object response
{
rest::get
{
buf, string_view{url}
}
};
out
<< string_view{response}
<< std::endl;
return true;
}
//
// bridge
//

View file

@ -114,6 +114,7 @@ handle_get(client &client,
opts.room_id = since;
opts.search_term = search_term;
opts.request_node_id = request.node_id;
opts.room_type = json::string(filter["room_type"]);
size_t count{0};
m::room::id::buf prev_batch_buf;

View file

@ -64,6 +64,11 @@ get__user_devices(client &client,
user_id
};
const m::user::keys user_keys
{
user_id
};
m::resource::response::chunked::json response
{
client, http::OK
@ -82,40 +87,42 @@ get__user_devices(client &client,
}
};
const auto master_event_idx
if(user_keys.has_cross_master())
{
user_room.get(std::nothrow, "ircd.device.signing.master", "")
};
m::get(std::nothrow, master_event_idx, "content", [&response]
(const json::object &content)
{
json::stack::member
json::stack::object object
{
response, "master_key", content
response, "master_key"
};
});
const auto self_event_idx
{
user_room.get(std::nothrow, "ircd.device.signing.self", "")
};
user_keys.cross_master(object);
}
m::get(std::nothrow, self_event_idx, "content", [&response]
(const json::object &content)
if(user_keys.has_cross_self())
{
json::stack::member
json::stack::object object
{
response, "self_signing_keys", content
response, "self_signing_key"
};
});
user_keys.cross_self(object);
}
if(my_host(request.node_id) && user_keys.has_cross_user())
{
json::stack::object object
{
response, "user_signing_key"
};
user_keys.cross_user(object);
}
json::stack::array devices
{
response, "devices"
};
user_devices.for_each([&user_devices, &devices]
user_devices.for_each([&user_devices, &devices, &user_keys]
(const auto &, const string_view &device_id)
{
json::stack::object device
@ -128,6 +135,16 @@ get__user_devices(client &client,
device, "device_id", device_id
};
if(user_keys.has_device(device_id))
{
json::stack::object keys
{
device, "keys"
};
user_keys.device(keys, device_id);
}
// The property name difference here is on purpose, probably one of
// those so-called spec "thinkos"
user_devices.get(std::nothrow, device_id, "display_name", [&device]
@ -139,15 +156,6 @@ get__user_devices(client &client,
};
});
user_devices.get(std::nothrow, device_id, "keys", [&device]
(const auto &, const json::object &value)
{
json::stack::member
{
device, "keys", value
};
});
return true;
});

View file

@ -47,29 +47,27 @@ post__user_keys_claim(client &client,
request["one_time_keys"]
};
m::resource::response::chunked response
m::resource::response::chunked::json response
{
client, http::OK
};
json::stack out
{
response.buf, response.flusher()
};
json::stack::object top
{
out
};
json::stack::object response_keys
{
top, "one_time_keys"
response, "one_time_keys"
};
for(const auto &[user_id, devices] : one_time_keys)
for(const auto &[user_id_, devices] : one_time_keys)
{
const m::user::room user_room
const m::user::id user_id
{
user_id_
};
if(!m::exists(user_id))
continue;
const m::user::keys keys
{
user_id
};
@ -79,26 +77,11 @@ post__user_keys_claim(client &client,
response_keys, user_id
};
for(const auto &[device_id_, algorithm_] : json::object(devices))
for(const auto &[device_id, algorithm_] : json::object(devices))
{
const json::string &algorithm{algorithm_};
const json::string &device_id{device_id_};
const auto match{[&device_id]
(const string_view &state_key) noexcept
const json::string algorithm
{
return state_key == device_id;
}};
char buf[m::event::TYPE_MAX_SIZE];
const string_view type{fmt::sprintf
{
buf, "ircd.device.one_time_key|%s",
algorithm
}};
const m::room::type events
{
user_room, type, { -1UL, -1L }, true
algorithm_
};
json::stack::object response_device
@ -106,33 +89,9 @@ post__user_keys_claim(client &client,
response_user, device_id
};
events.for_each([&response_device, &match]
(const string_view &type, const auto &, const m::event::idx &event_idx)
{
if(!m::query(std::nothrow, event_idx, "state_key", match))
return true;
m::get(std::nothrow, event_idx, "content", [&response_device, type]
(const json::object &content)
{
const auto algorithm
{
split(type, '|').second
};
json::stack::member
{
response_device, algorithm, json::object
{
content[""] // device quirk
}
};
});
return false;
});
keys.claim(response_device, device_id, algorithm);
}
}
return {};
return response;
}

View file

@ -32,6 +32,11 @@ _query_user_device(client &,
const string_view &device_id,
json::stack::object &out);
static void
_query_user_keys(client &,
const m::resource::request &,
json::stack &);
static void
_query_self_keys(client &,
const m::resource::request &,
@ -72,6 +77,9 @@ post__user_keys_query(client &client,
_query_device_keys(client, request, response);
_query_master_keys(client, request, response);
_query_self_keys(client, request, response);
if(my_host(request.node_id))
_query_user_keys(client, request, response);
return response;
}
@ -147,24 +155,20 @@ _query_master_keys(client &client,
user_id_
};
const m::user::room room
const m::user::keys keys
{
user_id
};
const auto event_idx
if(!keys.has_cross_master())
continue;
json::stack::object object
{
room.get(std::nothrow, "ircd.device.signing.master", "")
response_keys, user_id
};
m::get(std::nothrow, event_idx, "content", [&response_keys, &user_id]
(const json::object &content)
{
json::stack::member
{
response_keys, user_id, content
};
});
keys.cross_master(object);
}
}
@ -190,24 +194,59 @@ _query_self_keys(client &client,
user_id_
};
const m::user::room room
const m::user::keys keys
{
user_id
};
const auto event_idx
if(!keys.has_cross_self())
continue;
json::stack::object object
{
room.get(std::nothrow, "ircd.device.signing.self", "")
response_keys, user_id
};
m::get(std::nothrow, event_idx, "content", [&response_keys, &user_id]
(const json::object &content)
keys.cross_self(object);
}
}
void
_query_user_keys(client &client,
const m::resource::request &request,
json::stack &out)
{
const json::object request_keys
{
request.at("device_keys")
};
json::stack::object response_keys
{
out, "user_signing_keys"
};
for(const auto &[user_id_, device_ids_] : request_keys)
{
const m::user::id &user_id
{
json::stack::member
{
response_keys, user_id, content
};
});
user_id_
};
const m::user::keys keys
{
user_id
};
if(!keys.has_cross_user())
continue;
json::stack::object object
{
response_keys, user_id
};
keys.cross_user(object);
}
}
@ -218,7 +257,12 @@ _query_user_device(client &client,
const string_view &device_id,
json::stack::object &out)
{
if(!devices.has(device_id, "keys"))
const m::user::keys keys
{
devices.user
};
if(!keys.has_device(device_id))
return;
json::stack::object object
@ -226,15 +270,7 @@ _query_user_device(client &client,
out, device_id
};
devices.get(std::nothrow, device_id, "keys", [&device_id, &object]
(const auto &event_idx, const json::object &device_keys)
{
for(const auto &member : device_keys)
json::stack::member
{
object, member.first, member.second
};
});
keys.device(object, device_id);
devices.get(std::nothrow, device_id, "display_name", [&device_id, &object]
(const auto &event_idx, const string_view &display_name)

View file

@ -677,14 +677,15 @@ ircd::m::bridge::append(const config &config,
const event::idx &event_idx,
const event &event)
{
event::append::opts opts;
opts.event_idx = &event_idx;
opts.query_txnid = false;
opts.query_prev_state = true;
opts.query_redacted = false;
event::append
{
events, event, opts
events, event, event::append::opts
{
.event_idx = event_idx,
.query_txnid = false,
.query_prev_state = true,
.query_redacted = false,
},
};
log::debug

View file

@ -40,7 +40,21 @@ struct command_result
string_view msgtype {"m.notice"};
};
conf::item<bool>
static conf::item<size_t>
watch_limit
{
{ "name", "ircd.m.command.watch.limit" },
{ "default", 256 },
};
static conf::item<bool>
watch_opers
{
{ "name", "ircd.m.command.watch.opers" },
{ "default", true },
};
static conf::item<bool>
command_typing
{
{ "name", "ircd.m.command.typing" },
@ -55,6 +69,30 @@ execute_command(const mutable_buffer &buf,
const m::event::id &reply_to,
const bool public_response);
static void
watch_command(const m::user::id &user_id,
const m::room::id &room_id,
const m::event::id &reply_id,
const m::event::id &response_id,
const m::room::id &response_room,
const m::user::id &response_sender,
const string_view &response_type,
const string_view &cmd,
const bool &public_response,
const milliseconds &watch_delay);
static const json::value
undef_val
{
string_view{nullptr}, json::STRING
};
static const json::value
html_format
{
"org.matrix.custom.html", json::STRING
};
void
handle_command(const m::event &event,
m::vm::eval &eval)
@ -105,6 +143,18 @@ try
if(!is_command)
return;
const json::string reply_to
{
content["reply_id"]
};
const auto reply_id
{
reply_to?
m::event::id::buf{reply_to}:
m::event::id::buf{}
};
// View of the command string without prefix
string_view input
{
@ -120,29 +170,43 @@ try
startswith(input, '!')
};
if(public_response)
input = lstrip(input, '!');
const bool command_watch
{
startswith(input, "watch ")
&& (!watch_opers || is_oper(user))
};
milliseconds watch_delay {0ms};
if(command_watch)
{
const auto delay
{
lex_cast<float>(token(input, ' ', 1))
};
watch_delay = milliseconds
{
long(delay * 1000.0)
};
input = tokens_after(input, ' ', 1);
}
const string_view cmd
{
lstrip(input, '!')
};
const json::string reply_to
{
content["reply_id"]
};
const auto reply_id
{
reply_to?
m::event::id{reply_to}:
m::event::id{}
input
};
log::debug
{
m::log, "Server command from %s in %s public:%b replying:%s :%s",
m::log, "Server command from %s in %s public:%b watch:%ld replying:%s :%s",
string_view{room_id},
string_view{user.user_id},
public_response,
watch_delay.count(),
string_view{reply_id}?: "false"_sv,
cmd
};
@ -175,7 +239,7 @@ try
public_response? "m.room.message" : "ircd.cmd.result"
};
const auto &response_event_id
const auto &command_event_id
{
public_response?
string_view{event_id}:
@ -192,16 +256,6 @@ try
alt?: "no alt text"_sv, json::STRING
};
static const json::value undef_val
{
string_view{nullptr}, json::STRING
};
static const json::value html_format
{
"org.matrix.custom.html", json::STRING
};
char relates_buf[576], reply_buf[288];
json::object in_reply_to, relates_to {json::empty_object};
if(event_id)
@ -219,16 +273,52 @@ try
});
}
m::send(response_room, response_sender, response_type,
const auto response_id
{
{ "msgtype", msgtype?: "m.notice" },
{ "format", html? html_format: undef_val },
{ "body", content_body },
{ "formatted_body", html? html_val: undef_val },
{ "room_id", room_id },
{ "input", input },
{ "m.relates_to", relates_to },
});
m::send(response_room, response_sender, response_type,
{
{ "msgtype", msgtype?: "m.notice" },
{ "format", html? html_format: undef_val },
{ "body", content_body },
{ "formatted_body", html? html_val: undef_val },
{ "room_id", room_id },
{ "input", cmd },
{ "m.relates_to", relates_to },
})
};
if(command_watch)
ctx::context
{
"watch", ctx::context::POST | ctx::context::DETACH,
[
user_id(m::user::id::buf(user.user_id)),
room_id(m::room::id::buf(room_id)),
reply_id(m::event::id::buf(reply_id)),
response_id(m::event::id::buf(response_id)),
response_room(m::room::id::buf(response_room)),
response_sender(m::user::id::buf(response_sender)),
response_type(std::string(response_type)),
cmd(std::string(cmd)),
public_response,
watch_delay
]
{
watch_command
(
user_id,
room_id,
reply_id,
response_id,
response_room,
response_sender,
response_type,
cmd,
public_response,
watch_delay
);
}
};
}
catch(const std::exception &e)
{
@ -258,6 +348,90 @@ catch(const std::exception &e)
};
}
void
watch_command(const m::user::id &user_id,
const m::room::id &room_id,
const m::event::id &reply_id,
const m::event::id &response_id,
const m::room::id &response_room,
const m::user::id &response_sender,
const string_view &response_type,
const string_view &cmd,
const bool &public_response,
const milliseconds &watch_delay)
{
const auto annotation_id
{
public_response?
m::annotate(response_room, response_sender, response_id, "▶️"_sv):
m::event::id::buf{}
};
const unwind deannotate{[&]
{
if(annotation_id && !m::redacted(annotation_id))
m::redact(response_room, response_sender, annotation_id, "cleared");
}};
const unique_buffer<mutable_buffer> buf
{
56_KiB
};
for(size_t i(0); i < size_t(watch_limit); ++i)
{
sleep(watch_delay);
if(m::redacted(response_id))
break;
if(annotation_id && m::redacted(annotation_id))
break;
const auto &[html, alt, msgtype]
{
execute_command(buf, user_id, room_id, cmd, reply_id, public_response)
};
if(!html && !alt)
break;
const json::value html_val
{
html, json::STRING
};
const json::value content_body
{
alt?: "no alt text"_sv, json::STRING
};
m::send(response_room, response_sender, response_type,
{
{ "body", content_body },
{ "format", html? html_format: undef_val },
{ "formatted_body", html? html_val: undef_val },
{ "input", cmd },
{ "msgtype", msgtype?: "m.notice" },
{ "room_id", room_id },
{ "m.new_content", json::members
{
{ "body", content_body },
{ "format", html? html_format: undef_val },
{ "formatted_body", html? html_val: undef_val },
{ "input", cmd },
{ "msgtype", msgtype?: "m.notice" },
{ "room_id", room_id },
}},
{ "m.relates_to", json::members
{
{ "event_id", response_id },
{ "rel_type", "m.replace" },
}}
});
}
}
static command_result
command__summarize(const mutable_buffer &buf,
const m::user &user,

View file

@ -56,53 +56,29 @@ try
if(user_id.host() != at<"origin"_>(event))
return;
const json::object &msk
{
json::get<"master_key"_>(update)
};
const m::user::room room
{
user_id
};
if(!exists(room))
if(!exists(user_id))
{
log::derror
{
m::log, "Refusing signing key update for unknown %s",
json::get<"user_id"_>(update),
string_view{user_id},
};
return;
}
const auto master_id
const m::user::keys keys
{
msk?
send(room, user_id, "ircd.device.signing.master", "", msk):
m::event::id::buf{}
user_id
};
const json::object &ssk
{
json::get<"self_signing_key"_>(update)
};
const auto self_id
{
ssk?
send(room, user_id, "ircd.device.signing.self", "", ssk):
m::event::id::buf{}
};
keys.update(update);
log::info
{
m::log, "Signing key update from :%s by %s master:%s self:%s",
m::log, "Signing key update from '%s' for %s",
json::get<"origin"_>(event),
json::get<"user_id"_>(update),
string_view{master_id},
string_view{self_id},
};
}
catch(const ctx::interrupted &e)
@ -113,7 +89,7 @@ catch(const std::exception &e)
{
log::derror
{
m::log, "m.signing_key_update from %s :%s",
m::log, "m.signing_key_update from '%s' :%s",
json::get<"origin"_>(event),
e.what(),
};

View file

@ -96,7 +96,7 @@ post__upload(client &client,
return m::resource::response
{
client, http::CREATED, json::members
client, http::OK, json::members
{
{ "content_uri", content_uri }
}

View file

@ -89,7 +89,7 @@ ircd::net::dns::cache::put(const hostport &hp,
{
opts.qtype == 33?
make_SRV_key(state_key_buf, hp, opts):
host(hp)
tolower(state_key_buf, host(hp))
};
return put(type, state_key, code, msg);
@ -117,7 +117,7 @@ ircd::net::dns::cache::put(const hostport &hp,
{
opts.qtype == 33?
make_SRV_key(state_key_buf, hp, opts):
host(hp)
tolower(state_key_buf, host(hp))
};
return put(type, state_key, rrs);
@ -369,7 +369,7 @@ ircd::net::dns::cache::get(const hostport &hp,
{
opts.qtype == 33?
make_SRV_key(state_key_buf, hp, opts):
host(hp)
tolower(state_key_buf, host(hp))
};
const m::room::state state
@ -431,7 +431,7 @@ ircd::net::dns::cache::for_each(const hostport &hp,
{
opts.qtype == 33?
make_SRV_key(state_key_buf, hp, opts):
host(hp)
tolower(state_key_buf, host(hp))
};
const m::room::state state

View file

@ -174,6 +174,10 @@ static bool
github_handle__push(std::ostream &,
const json::object &content);
static bool
github_post_handle__push(const m::event::id &,
const json::object &content);
static bool
github_handle__pull_request(std::ostream &,
const json::object &content);
@ -355,6 +359,9 @@ github_handle(client &client,
m::msghtml(room_id, user_id, view(out, buf[0]), view(alt, buf[1]), "m.notice")
};
if(type == "push")
github_post_handle__push(evid, request.content);
log::info
{
"Webhook [%s] '%s' delivered to %s %s",
@ -620,7 +627,7 @@ find_reaction_id(const m::room &room,
}
static ircd::m::event::id::buf
find_reaction_id_endswith(const m::room &room,
find_reaction_id_contains(const m::room &room,
const m::user::id &user_id,
const m::event::id &event_id,
const string_view &label)
@ -633,7 +640,7 @@ find_reaction_id_endswith(const m::room &room,
relates["key"]
};
return endswith(key, label);
return has(key, label);
});
}
@ -656,14 +663,14 @@ clear_reaction(const m::room &room,
}
static bool
clear_reaction_endswith(const m::room &room,
clear_reaction_contains(const m::room &room,
const m::user::id &user_id,
const m::event::id &event_id,
const string_view &label)
{
const auto reaction_id
{
find_reaction_id_endswith(room, user_id, event_id, label)
find_reaction_id_contains(room, user_id, event_id, label)
};
if(!reaction_id)
@ -848,6 +855,118 @@ github_markdown(unique_const_buffer &buf,
);
}
static bool
github_hook_for_each(const string_view &repo,
const function_bool<json::object> &closure)
{
unique_const_buffer buf;
const json::array response
{
github_request
(
buf, "GET", repo, "hooks"
)
};
for(const json::object hook : response)
if(!closure(hook))
return false;
return true;
}
static void
github_hook_ping(const string_view &repo,
const string_view &hook)
{
unique_const_buffer buf;
github_request
(
buf, "POST", repo, "hooks/%s/pings",
hook
);
}
static void
github_hook_ping(const string_view &repo)
{
github_hook_for_each(repo, [&repo]
(const json::object &hook)
{
const json::string id
{
hook["id"]
};
github_hook_ping(repo, id);
});
}
static bool
github_hook_shot_for_each(const string_view &repo,
const string_view &hook,
const bool &redelivery,
const function_bool<json::object> &closure)
{
unique_const_buffer buf;
const json::array response
{
github_request
(
//TODO: pagination token
buf, "GET", repo, "hooks/%s/deliveries?per_page=100",
hook
)
};
for(const json::object shot : response)
if(!closure(shot))
return false;
return true;
}
static void
github_hook_shot_retry(const string_view &repo,
const string_view &hook,
const string_view &id)
{
unique_const_buffer buf;
github_request
(
buf, "POST", repo, "hooks/%s/deliveries/%s/attempts",
hook,
id
);
}
static bool
github_hook_shot_for_each_fail(const string_view &repo,
const string_view &hook,
const function_bool<json::object> &closure)
{
bool ret {true};
github_hook_shot_for_each(repo, hook, true, [&ret, &closure]
(const json::object &object)
{
if(object.at<bool>("redelivery"))
return true;
const json::string status
{
object["status"]
};
if(status == "OK")
return false;
ret = closure(object);
return ret;
});
return ret;
}
static bool
github_run_for_each_jobs(const string_view &repo,
const string_view &run_id,
@ -907,6 +1026,59 @@ github_run_rerun_failed(const string_view &repo,
github_request(buf, "POST", repo, "actions/runs/%s/rerun-failed-jobs", run_id);
}
static void
github_run_dispatch(const string_view &repo,
const string_view &name,
const string_view &ref = "master",
const json::members inputs = {})
{
const json::strung content{json::members
{
{ "ref", ref },
{ "inputs", inputs },
}};
unique_const_buffer buf;
github_request(content, buf, "POST", repo, "actions/workflows/%s/dispatches", name);
}
static bool
github_flow_for_each(const string_view &repo,
const function_bool<json::object> &closure,
const bool active_only = true)
{
unique_const_buffer buf;
const json::object response
{
github_request
(
//TODO: pagination token
buf, "GET", repo, "actions/workflows?per_page=100&page=1"
)
};
const json::array workflows
{
response["workflows"]
};
for(const json::object flow : workflows)
{
const json::string state
{
flow["state"]
};
if(active_only && state != "active")
continue;
if(!closure(flow))
return false;
}
return true;
}
bool
github_handle__workflow_run(std::ostream &out,
std::ostream &alt,
@ -973,15 +1145,8 @@ github_handle__workflow_run(std::ostream &out,
annote = ircd::strlcat(buf, " "_sv);
annote = ircd::strlcat(buf, name);
const auto reaction_id
{
push_event_id && action != "requested"? // skip search on first action
find_reaction_id_endswith(_webhook_room, _webhook_user, push_event_id, name):
m::event::id::buf{}
};
if(reaction_id)
m::redact(_webhook_room, _webhook_user, reaction_id, "status change");
if(push_event_id && action != "requested") // skip search on first action
while(clear_reaction_contains(_webhook_room, _webhook_user, push_event_id, name));
m::annotate(_webhook_room, _webhook_user, push_event_id, annote);
@ -1433,40 +1598,77 @@ try
json::object{relates_content}.get("body")
};
if(!startswith(relates_body, "job status table "))
return;
const auto suffix
if(startswith(relates_body, "job status table "))
{
lstrip(relates_body, "job status table ")
};
const auto suffix
{
lstrip(relates_body, "job status table ")
};
string_view token[3];
ircd::tokens(suffix, ' ', token);
const auto &[repopath, run_id, attempt] {token};
assert(repopath);
assert(run_id);
if(!repopath || !run_id)
return;
string_view token[3];
ircd::tokens(suffix, ' ', token);
const auto &[repopath, run_id, attempt] {token};
assert(repopath);
assert(run_id);
if(!repopath || !run_id)
return;
switch(hash(key))
switch(hash(key))
{
case hash("🚮"_sv):
github_run_delete(repopath, run_id);
m::redact(room, user_id, relates_event_id, "deleted");
break;
case hash(""_sv):
github_run_cancel(repopath, run_id);
break;
case hash("🔄"_sv):
github_run_rerun_failed(repopath, run_id);
break;
case hash("↪️"_sv):
github_run_rerun(repopath, run_id);
break;
}
}
else if(startswith(relates_body, "push by "))
{
case hash("🚮"_sv):
github_run_delete(repopath, run_id);
m::redact(room, user_id, relates_event_id, "deleted");
break;
const auto suffix
{
lstrip(relates_body, "push by ")
};
case hash(""_sv):
github_run_cancel(repopath, run_id);
break;
string_view token[5];
ircd::tokens(suffix, ' ', token);
const auto &[pusher, _by_, repo, _at_, commit] {token};
assert(pusher);
assert(repo);
assert(commit);
if(!repo)
return;
case hash("🔄"_sv):
github_run_rerun_failed(repopath, run_id);
break;
if(startswith(key, "▶️"))
{
const auto id
{
between(key, '(', ')')
};
case hash("↪️"_sv):
github_run_rerun(repopath, run_id);
break;
const auto ref
{
// commit // hash not supported by github
"master"
};
github_run_dispatch(repo, id, ref, json::members
{
});
return;
}
}
}
catch(const ctx::interrupted &)
@ -1786,6 +1988,54 @@ github_handle__push(std::ostream &out,
return true;
}
static bool
github_post_handle__push(const m::event::id &push_event_id,
const json::object &content)
{
const m::user::id::buf _webhook_user
{
string_view{webhook_user}, my_host()
};
const auto _webhook_room
{
m::room_id(string_view(webhook_room))
};
const auto repo
{
github_repopath(content)
};
github_flow_for_each(repo, [&]
(const json::object &flow)
{
const json::string name
{
flow["name"]
};
const json::string id
{
flow["id"]
};
const fmt::bsprintf<128> key
{
"▶️ %s (%s)", name, id
};
const auto annote_id
{
m::annotate(_webhook_room, _webhook_user, push_event_id, string_view(key))
};
return true;
});
return true;
}
bool
github_handle__pull_request(std::ostream &out,
const json::object &content)