0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-25 08:12:37 +01:00

Checkpoint matrix with preliminary federation client and keyserver related.

This commit is contained in:
Jason Volk 2017-10-03 04:12:54 -07:00
parent 3a9696fb6c
commit 20869309a2
16 changed files with 1160 additions and 252 deletions

View file

@ -67,8 +67,9 @@ struct _name_ \
namespace ircd::m
{
IRCD_M_EXCEPTION(error, UNKNOWN, http::INTERNAL_SERVER_ERROR);
IRCD_M_EXCEPTION(error, NOT_FOUND, http::NOT_FOUND);
IRCD_M_EXCEPTION(error, BAD_REQUEST, http::BAD_REQUEST);
IRCD_M_EXCEPTION(error, BAD_JSON, http::BAD_REQUEST);
IRCD_M_EXCEPTION(error, NOT_FOUND, http::NOT_FOUND);
}
inline

View file

@ -102,6 +102,7 @@ struct ircd::m::event
// Queue of contexts waiting to see the next inserted event
static ctx::view<const event> inserted;
static id::buf head;
static const_iterator find(const id &);
static void insert(json::iov &);

View file

@ -113,4 +113,8 @@ struct ircd::m::filter
using super_type::tuple;
using super_type::operator=;
static size_t size(const string_view &filter_id);
filter(const string_view &filter_id, const mutable_buffer &);
};

87
include/ircd/m/keys.h Normal file
View file

@ -0,0 +1,87 @@
/*
* charybdis: 21st Century IRC++d
*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#pragma once
#define HAVE_IRCD_M_KEYS_H
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsubobject-linkage"
namespace ircd::m
{
struct key;
}
namespace ircd::my
{
extern ed25519::pk public_key;
extern ed25519::sk secret_key;
extern std::string public_key_b64;
}
namespace ircd::m::name
{
constexpr const char *const server_name {"server_name"};
constexpr const char *const verify_keys {"verify_keys"};
constexpr const char *const old_verify_keys {"old_verify_keys"};
// constexpr const char *const signatures {"signatures"};
constexpr const char *const tls_fingerprints {"tls_fingerprints"};
constexpr const char *const valid_until_ts {"valid_until_ts"};
}
/// 2.2.1.1 Publishing Keys
///
/// Key Type, Description
/// server_name String, DNS name of the homeserver.
/// verify_keys Object, Public keys of the homeserver for verifying digital signatures.
/// old_verify_keys Object, The public keys that the server used to use and when it stopped using them.
/// signatures Object, Digital signatures for this object signed using the verify_keys.
/// tls_fingerprints Array of Objects, Hashes of X.509 TLS certificates used by this this server encoded as Unpadded Base64.
/// valid_until_ts Integer, POSIX timestamp when the list of valid keys should be refreshed.
///
struct ircd::m::key
:json::tuple
<
json::property<name::old_verify_keys, json::object>,
json::property<name::server_name, string_view>,
json::property<name::signatures, json::object>,
json::property<name::tls_fingerprints, json::array>,
json::property<name::valid_until_ts, time_t>,
json::property<name::verify_keys, json::object>
>
{
static room keys;
using super_type::tuple;
using super_type::operator=;
};
namespace ircd::m::keys
{
using closure = std::function<void (const key &)>;
bool get(const string_view &server_name, const closure &);
void set(const key &);
}
#pragma GCC diagnostic pop

View file

@ -43,13 +43,15 @@ struct ircd::m::room
id room_id;
bool membership(const m::id::user &, const string_view &membership = "join") const;
event::id head(event::id::buf &) const;
event::id::buf send(json::iov &event);
event::id::buf send(const json::members &event);
bool is_member(const m::id::user &, const string_view &membership = "join");
void membership(const m::id::user &, json::iov &content);
void leave(const m::id::user &, json::iov &content);
void join(const m::id::user &, json::iov &content);
void create(const m::id::user &sender, const m::id::user &creator, json::iov &content);
room(const id &room_id)
@ -60,19 +62,3 @@ struct ircd::m::room
:room_id{}
{}
};
struct ircd::m::room::members
{
id room_id;
bool is_member(const m::id::user &) const;
bool membership(const m::id::user &, const string_view & = "join") const;
members(const id &room_id)
:room_id{room_id}
{}
members(const room &room)
:room_id{room.room_id}
{}
};

View file

@ -29,15 +29,15 @@ namespace ircd {
namespace m {
struct session
:client
{
std::shared_ptr<ircd::client> client;
std::string access_token;
std::deque<std::string> tape;
std::multimap<string_view, string_view> resource;
json::object operator()(parse::buffer &pb, request &);
session(const host_port &);
session(const hostport &);
};
} // namespace m

View file

@ -28,9 +28,6 @@
namespace ircd::m
{
struct user;
extern user me;
extern room my_room;
}
struct ircd::m::user

View file

@ -24,8 +24,8 @@
// m/session.h
//
ircd::m::session::session(const host_port &host_port)
:client{host_port}
ircd::m::session::session(const hostport &host_port)
:client{std::make_shared<ircd::client>(host_port)}
{
}
@ -33,29 +33,111 @@ ircd::json::object
ircd::m::session::operator()(parse::buffer &pb,
request &r)
{
parse::capstan pc
const json::member origin
{
pb, read_closure(*this)
"origin", my_host()
};
const http::line::header headers[]
const json::member destination
{
{ "Content-Type"s, "application/json"s }
//host(remote_hostport(*client->sock))
"destination", "zemos.net"
};
const json::member method
{
"method", r.method
};
const std::string uri
{
std::string {"/"} + std::string{r.path} +
(r.query? (std::string{"?"} + std::string{r.query}) : std::string{})
};
json::iov iov;
const json::iov::push pushed[]
{
{ iov, json::member { "uri", uri } },
{ iov, origin },
{ iov, method },
{ iov, destination },
};
const json::iov::add_if content
{
iov, r.content.size() > 2, json::member
{
"content", r.content
}
};
size_t headers{2};
http::line::header header[3]
{
{ "Content-Type", "application/json" },
{ "User-Agent", "IRCd" }
};
char x_matrix_buf[2048];
if(startswith(r.path, "_matrix/federation"))
{
// These buffers can be comfortably large if they're not on a stack and
// nothing in this procedure has a yield; the assertion is tripped if so
static char request_object_buffer[4096];
static char signature_buffer[128];
ctx::critical_assertion ca;
const auto request_object
{
json::stringify(request_object_buffer, iov)
};
std::cout << request_object << std::endl;
const ed25519::sig sig
{
my::secret_key.sign(request_object)
};
const auto signature
{
b64encode_unpadded(signature_buffer, sig)
};
const auto x_matrix_len
{
fmt::sprintf(x_matrix_buf, "X-Matrix origin=%s,key=\"ed25519:pk2\",sig=\"%s\"",
string_view{origin.second},
signature)
};
const string_view x_matrix
{
x_matrix_buf, size_t(x_matrix_len)
};
header[headers++] = { "Authorization", x_matrix };
}
http::request
{
host(remote_addr(*this)),
string_view{destination.second}, //host(remote(*client)),
r.method,
r.path,
r.query,
r.content,
write_closure(*this),
{ headers, headers + (sizeof(headers) / sizeof(http::line::header)) }
write_closure(*client),
{ header, headers }
};
http::code status;
json::object object;
parse::capstan pc
{
pb, read_closure(*client)
};
http::response
{
pc,
@ -81,36 +163,56 @@ ircd::m::session::operator()(parse::buffer &pb,
namespace ircd::m
{
std::map<std::string, ircd::module> modules;
ircd::listener *listener;
ircd::net::listener *listener;
static void leave_ircd_room();
static void join_ircd_room();
void bootstrap();
static void init_keys(const std::string &secret_key_file);
static void bootstrap();
}
const ircd::m::user::id::buf
ircd_user_id
{
"ircd", "cdc.z" //TODO: hostname
};
ircd::m::user
ircd::m::me
{
"@ircd:cdc.z"
ircd_user_id
};
const ircd::m::room::id::buf
ircd_room_id
{
"ircd", ircd::my_host()
};
ircd::m::room
ircd::m::my_room
{
ircd::m::room::id{"!ircd:cdc.z"}
ircd_room_id
};
ircd::m::room
ircd::m::filter::filters
ircd::string_view
ircd::m::my_host()
{
ircd::m::room::id{"!filters:cdc.z"}
};
return "cdc.z:8447"; //me.user_id.host();
}
ircd::m::init::init()
try
{
init_keys("charybdis.sk");
const string_view prefixes[]
{
"client_", "key_",
};
for(const auto &name : mods::available())
if(startswith(name, "client_"))
if(startswith_any(name, std::begin(prefixes), std::end(prefixes)))
modules.emplace(name, name);
if(db::sequence(*event::events) == 0)
@ -123,14 +225,14 @@ try
{ "name", "Chat Matrix" },
{ "host", "0.0.0.0" },
{ "port", 8447 },
{ "ssl_certificate_file", "/home/jason/zemos.net.tls.crt" },
{ "ssl_certificate_chain_file", "/home/jason/zemos.net.tls.crt" },
{ "ssl_certificate_file", "/home/jason/zemos.net.tls2.crt" },
{ "ssl_certificate_chain_file", "/home/jason/zemos.net.tls2.crt" },
{ "ssl_tmp_dh_file", "/home/jason/zemos.net.tls.dh" },
{ "ssl_private_key_file_pem", "/home/jason/zemos.net.tls.key" },
{ "ssl_private_key_file_pem", "/home/jason/zemos.net.tls2.key" },
})};
//TODO: conf obviously
listener = new ircd::listener{options};
listener = new ircd::net::listener{options};
join_ircd_room();
}
@ -174,6 +276,11 @@ ircd::m::leave_ircd_room()
my_room.leave(me.user_id, content);
}
namespace ircd::m
{
static void bootstrap_keys();
}
void
ircd::m::bootstrap()
{
@ -192,6 +299,174 @@ ircd::m::bootstrap()
user::accounts.join(me.user_id, content);
user::sessions.create(me.user_id, me.user_id, content);
filter::filters.create(me.user_id, me.user_id, content);
bootstrap_keys();
}
///////////////////////////////////////////////////////////////////////////////
//
// m/keys.h
//
const ircd::m::room::id::buf
keys_room_id
{
"keys", ircd::my_host()
};
ircd::m::room
ircd::m::key::keys
{
keys_room_id
};
ircd::ed25519::sk
ircd::my::secret_key
{};
ircd::ed25519::pk
ircd::my::public_key
{};
std::string
ircd::my::public_key_b64
{};
static void
ircd::m::init_keys(const std::string &sk_file)
{
my::secret_key = ed25519::sk
{
sk_file, &my::public_key
};
my::public_key_b64 = b64encode_unpadded(my::public_key);
log::info("My ed25519 public key is: %s",
my::public_key_b64);
}
namespace ircd
{
size_t certbytes(const mutable_raw_buffer &buf, const std::string &certfile);
}
static void
ircd::m::bootstrap_keys()
{
json::iov content;
key::keys.create(me.user_id, me.user_id, content);
key my_key;
json::val<name::server_name>(my_key) = my_host();
json::val<name::old_verify_keys>(my_key) = "{}";
const auto valid_until
{
ircd::time<milliseconds>() + duration_cast<milliseconds>(hours(2160)).count()
};
json::val<name::valid_until_ts>(my_key) = valid_until;
static char verify_keys_buf[256];
json::val<name::verify_keys>(my_key) = json::stringify(verify_keys_buf, json::members
{
{ "ed25519:pk2", json::members
{
{ "key", my::public_key_b64 }
}}
});
//static unsigned char pembuf[4096];
//const auto cbsz{ircd::certbytes(pembuf, "/home/jason/zemos.net.tls.crt")};
static std::array<uint8_t, 32> tls_hash;
a2u(tls_hash, "C259B83ABED34D81B31F773737574FBD966CE33BDED708BF502CA1D4CEC3D318");
static char tls_b64_buf[256];
const json::members tlsfps
{
{ "sha256", b64encode_unpadded(tls_b64_buf, tls_hash) }
};
const json::value tlsfp[1]
{
{ tlsfps }
};
static char tls_fingerprints_buf[256];
json::val<name::tls_fingerprints>(my_key) = json::stringify(tls_fingerprints_buf, json::value
{
tlsfp, 1
});
const std::string presig
{
json::string(my_key)
};
const ed25519::sig sig
{
my::secret_key.sign(const_raw_buffer{presig})
};
static char signature[128], signatures[256];
json::val<name::signatures>(my_key) = json::stringify(signatures, json::members
{
{ my_host(), json::members
{
{ "ed25519:pk2", b64encode_unpadded(signature, sig) }
}}
});
keys::set(my_key);
}
void
ircd::m::keys::set(const key &key)
{
const auto &state_key
{
at<name::server_name>(key)
};
const m::user::id::buf sender
{
"ircd", at<name::server_name>(key)
};
const auto content
{
json::string(key)
};
json::iov event;
json::iov::push members[]
{
{ event, json::member { "type", "ircd.key" }},
{ event, json::member { "state_key", state_key }},
{ event, json::member { "sender", sender }},
{ event, json::member { "content", content }}
};
key::keys.send(event);
}
bool
ircd::m::keys::get(const string_view &server_name,
const closure &closure)
{
const m::event::query<m::event::where::equal> query
{
{ "room_id", key::keys.room_id },
{ "type", "ircd.key" },
{ "state_key", server_name },
};
return m::events::test(query, [&closure]
(const auto &event)
{
closure(json::val<name::content>(event));
return true;
});
}
///////////////////////////////////////////////////////////////////////////////
@ -249,6 +524,65 @@ ircd::m::dbs::init_modules()
modules.emplace(name, name);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/filter.h
//
const ircd::m::room::id::buf
filters_room_id
{
"filters", ircd::my_host()
};
ircd::m::room
ircd::m::filter::filters
{
filters_room_id
};
ircd::m::filter::filter(const string_view &filter_id,
const mutable_buffer &buf)
{
const m::event::query<m::event::where::equal> query
{
{ "room_id", filters.room_id },
{ "type", "ircd.filter" },
{ "state_key", filter_id },
};
size_t len{0};
m::events::test(query, [&buf, &len]
(const auto &event)
{
len = copy(buf, json::val<name::content>(event));
return true;
});
new (this) filter{json::object{buf}};
}
size_t
ircd::m::filter::size(const string_view &filter_id)
{
const m::event::query<m::event::where::equal> query
{
{ "room_id", filters.room_id },
{ "type", "ircd.filter" },
{ "state_key", filter_id },
};
size_t ret{0};
m::events::test(query, [&ret]
(const auto &event)
{
ret = json::val<name::content>(event).size();
return true;
});
return ret;
}
///////////////////////////////////////////////////////////////////////////////
//
// m/room.h
@ -310,12 +644,12 @@ void
ircd::m::room::membership(const m::id::user &user_id,
json::iov &content)
{
const string_view membership
const string_view &membership
{
content.at("membership")
};
if(is_member(user_id, membership))
if(this->membership(user_id, membership))
throw m::ALREADY_MEMBER
{
"Member '%s' is already '%s'.", string_view{user_id}, membership
@ -340,15 +674,56 @@ ircd::m::room::membership(const m::id::user &user_id,
}
bool
ircd::m::room::is_member(const m::id::user &user_id,
const string_view &membership)
ircd::m::room::membership(const m::id::user &user_id,
const string_view &membership)
const
{
const members members
const event::query<event::where::equal> member_event
{
room_id
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", user_id },
};
return members.membership(user_id, membership);
if(!membership)
return m::events::test(member_event);
const event::query<event::where::test> membership_test{[&membership]
(const auto &event)
{
const json::object &content
{
json::at<m::name::content>(event)
};
const auto &existing_membership
{
unquote(content.at("membership"))
};
return membership == existing_membership;
}};
return m::events::test(member_event && membership_test);
}
ircd::m::event::id
ircd::m::room::head(event::id::buf &buf)
const
{
const event::query<event::where::equal> query
{
{ "room_id", room_id },
};
events::test(query, [&buf]
(const auto &event)
{
buf = json::val<name::event_id>(event);
return true;
});
return buf;
}
ircd::m::event::id::buf
@ -385,69 +760,33 @@ ircd::m::room::send(json::iov &event)
return generated_event_id;
}
bool
ircd::m::room::members::membership(const m::id::user &user_id,
const string_view &membership)
const
{
if(membership.empty())
return is_member(user_id);
const event::query<event::where::equal> member_event
{
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", user_id },
};
const event::query<event::where::test> membership_test{[&membership]
(const auto &event)
{
const json::object &content
{
json::at<m::name::content>(event)
};
const auto &existing_membership
{
unquote(content.at("membership"))
};
return membership == existing_membership;
}};
return m::events::test(member_event && membership_test);
}
bool
ircd::m::room::members::is_member(const m::id::user &user_id)
const
{
const event::query<event::where::equal> member_event
{
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", user_id },
};
return m::events::test(member_event);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/user.h
//
const ircd::m::room::id::buf
accounts_room_id
{
"accounts", ircd::my_host()
};
ircd::m::room
ircd::m::user::accounts
{
ircd::m::room::id{"!accounts:cdc.z"}
accounts_room_id
};
const ircd::m::room::id::buf
sessions_room_id
{
"sessions", ircd::my_host()
};
ircd::m::room
ircd::m::user::sessions
{
ircd::m::room::id{"!sessions:cdc.z"}
sessions_room_id
};
/// Register the user by joining them to the accounts room.
@ -554,31 +893,7 @@ bool
ircd::m::user::is_active()
const
{
const auto &room_id{accounts.room_id};
const m::event::query<event::where::equal> member_event
{
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", user_id },
};
const m::event::query<event::where::test> is_joined{[]
(const auto &event)
{
const json::object &content
{
json::val<m::name::content>(event)
};
const auto &membership
{
unquote(content["membership"])
};
return membership == "join";
}};
return events::test(member_event && is_joined);
return accounts.membership(user_id);
}
///////////////////////////////////////////////////////////////////////////////
@ -1068,12 +1383,16 @@ ircd::ctx::view<const ircd::m::event>
ircd::m::event::inserted
{};
ircd::m::event::id::buf
ircd::m::event::head
{};
void
ircd::m::event::insert(json::iov &iov)
{
const id::event::buf generated_event_id
{
iov.has("event_id")? id::event::buf{} : id::event::buf{id::generate, "cdc.z"}
iov.has("event_id")? id::event::buf{} : id::event::buf{id::generate, my_host()}
};
const json::iov::add_if event_id
@ -1083,7 +1402,7 @@ ircd::m::event::insert(json::iov &iov)
const json::iov::set origin_server_ts
{
iov, { "origin_server_ts", time<milliseconds>() }
iov, { "origin_server_ts", ircd::time<milliseconds>() }
};
const m::event event
@ -1108,9 +1427,8 @@ ircd::m::event::insert(json::iov &iov)
};
append_indexes(event, txn);
txn(*event::events);
event::head = json::at<name::event_id>(event);
event::inserted.notify(event);
}

View file

@ -120,9 +120,9 @@ try
// Sets up the query to find the access_token in the sessions rooms
const m::event::query<m::event::where::equal> query
{
{ "type", "ircd.access_token" },
{ "state_key", access_token },
{ "room_id", "!sessions:cdc.z" },
{ "type", "ircd.access_token" },
{ "state_key", access_token },
{ "room_id", m::user::sessions.room_id },
};
const bool result

View file

@ -81,6 +81,16 @@ client_module_LTLIBRARIES = \
client/client_voip_turnserver.la \
###
# This puts the source in key/ but the installed
# library is key_X.so in the main modules dir.
key_moduledir = @moduledir@
key_key_server_la_SOURCES = key/server.cc
key_key_query_la_SOURCES = key/query.cc
key_module_LTLIBRARIES = \
key/key_server.la \
key/key_query.la \
###
if JS
server_moduledir = @moduledir@
server_server_console_la_SOURCES = server/console.cc

View file

@ -26,7 +26,7 @@ struct room
{
static constexpr const auto base_url
{
"_matrix/client/r0/rooms/"
"_matrix/client/r0/rooms"
};
using resource::resource;
@ -45,6 +45,102 @@ mapi::header IRCD_MODULE
"registers the resource 'client/rooms'"
};
resource::response
get_messages(client &client,
const resource::request &request,
const string_view &params,
const m::room::id &room_id)
{
const m::event::query<m::event::where::equal> event_in_room
{
{ "room_id", room_id }
};
const m::event::query<m::event::where::test> event_not_state
{
[](const auto &event)
{
return !defined(json::val<m::name::state_key>(event));
}
};
const auto query
{
event_in_room && event_not_state
};
const size_t count
{
std::min(m::events::count(query), 128UL)
};
if(!count)
throw m::NOT_FOUND
{
"No messages."
};
size_t j(0);
json::value ret[count];
m::events::for_each(query, [&count, &j, &ret]
(const auto &event)
{
if(j < count)
ret[j++] = event;
});
return resource::response
{
client, json::members
{
{ "chunk", json::value { ret, j } }
}
};
}
resource::response
get_members(client &client,
const resource::request &request,
const string_view &params,
const m::room::id &room_id)
{
const m::event::query<m::event::where::equal> query
{
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", "" },
};
const auto count
{
m::events::count(query)
};
if(!count)
throw m::NOT_FOUND
{
"No members."
};
size_t j(0);
json::value ret[count];
m::events::for_each(query, [&count, &j, &ret]
(const auto &event)
{
if(j < count)
ret[j++] = event;
});
return resource::response
{
client, json::members
{
{ "chunk", json::value { ret, j } }
}
};
}
resource::response
get_state(client &client,
const resource::request &request,
@ -310,3 +406,46 @@ resource::method method_put
method_put.REQUIRES_AUTH
}
};
resource::response
post_receipt(client &client,
const resource::request &request,
const string_view &params,
const m::room::id &room_id)
{
string_view token[4];
if(tokens(params, '/', token) != 4)
throw m::BAD_REQUEST{"receipt type and event_id required"};
const string_view &receipt_type{token[2]};
const string_view &event_id{token[3]};
std::cout << "type: " << receipt_type << " eid: " << event_id << std::endl;
}
resource::response
post_rooms(client &client, const resource::request &request)
{
const auto params
{
lstrip(request.head.path, room::base_url)
};
string_view token[2];
if(tokens(params, '/', token) != 2)
throw m::BAD_REQUEST{"/rooms command required"};
m::room::id::buf room_id;
urldecode(token[0], room_id);
const string_view &cmd{token[1]};
if(cmd == "receipt")
return post_receipt(client, request, params, room_id);
}
resource::method method_POST
{
rooms_resource, "POST", post_rooms,
{
method_put.REQUIRES_AUTH
}
};

View file

@ -39,18 +39,6 @@ resource sync_resource
sync_description
};
struct syncpoll
{
static std::multimap<std::string, syncpoll> polling;
static std::multimap<steady_point, decltype(polling)::iterator> pollout;
std::weak_ptr<ircd::client> client;
decltype(pollout)::iterator it { std::end(pollout) };
};
decltype(syncpoll::polling) syncpoll::polling {};
decltype(syncpoll::pollout) syncpoll::pollout {};
void longpoll(client &client, const resource::request &request, const steady_point &timeout);
void synchronizer_worker();
@ -88,71 +76,11 @@ mapi::header IRCD_MODULE
};
resource::response
sync_now(client &client,
const resource::request &request,
const string_view &filter_id,
const bool &full_state,
const string_view &set_presence)
{
json::value events[1];
const json::members timeline
{
{ "events", { events, 1 } }
};
const json::members state
{
{ "events", { events, 1 } }
};
const json::members join
{
{ "timeline", timeline },
{ "state", state },
};
const json::object leave{};
const json::object invite{};
const json::members rooms
{
{ "leave", leave },
{ "join", join },
{ "invite", invite },
};
const string_view next_batch{};
const json::object presence{};
const m::event::id head_event_id
{
"$12382382:cdc.z"
};
const json::members content
{
{ "event_id", head_event_id }
};
m::user::sessions.send(
{
{ "type", "ircd.tape.head" },
{ "state_key", request.query.at("access_token") },
{ "sender", request.user_id },
{ "content", content },
});
return resource::response
{
client, json::members
{
{ "next_batch", next_batch },
{ "rooms", rooms },
{ "presence", presence }
}
};
}
initial_sync(client &client,
const resource::request &request,
const string_view &filter_id,
const bool &full_state,
const string_view &set_presence);
resource::response
sync(client &client, const resource::request &request)
@ -193,15 +121,9 @@ sync(client &client, const resource::request &request)
request.query["set_presence"]
};
// Start a new spool for client
if(!since)
return sync_now(client, request, filter_id, full_state, set_presence);
// The !sessions:your.host room is where the ircd.tape.head event holds
// the state we use to calculate the last event the user has seen.
const m::room::state sessions
{
m::user::sessions
};
return initial_sync(client, request, filter_id, full_state, set_presence);
// The ircd.tape.head
const m::event::query<m::event::where::equal> query
@ -254,24 +176,49 @@ resource::method get_sync
/// Input
///
///
struct syncpoll
{
static std::list<syncpoll> polling;
static std::multimap<steady_point, decltype(polling)::iterator> pollout;
std::string user_id;
std::string since;
std::string access_token; // can get rid of this and use some session id
std::weak_ptr<ircd::client> client;
decltype(pollout)::iterator it { std::end(pollout) };
};
decltype(syncpoll::polling) syncpoll::polling {};
decltype(syncpoll::pollout) syncpoll::pollout {};
void
longpoll(client &client,
const resource::request &request,
const steady_point &timeout)
{
static auto &polling{syncpoll::polling};
static auto &pollout{syncpoll::pollout};
const auto it
{
syncpoll::polling.emplace(request.user_id, syncpoll{weak_from(client)})
polling.emplace(polling.end(), syncpoll
{
std::string{request.user_id},
std::string{request.query.at("since")},
std::string{request.query.at("access_token")},
weak_from(client)
})
};
syncpoll &data
{
it->second
*it
};
data.it = syncpoll::pollout.emplace(timeout, it);
data.it = pollout.emplace(timeout, it);
if(syncpoll::pollout.size() == 1)
if(pollout.size() == 1)
notify(synchronizer_timeout_context);
}
@ -279,7 +226,7 @@ longpoll(client &client,
// Timeout worker stack
//
void synchronizer_timeout(const std::string &user_id, const syncpoll &sp);
void synchronizer_timeout(const syncpoll &sp);
/// This function is the base of an ircd::context which yields until a client
/// is due to timeout. This worker reaps timed out clients from the lists.
@ -302,9 +249,8 @@ try
continue;
}
const auto &user_id{iterator->first};
const auto &data{iterator->second};
synchronizer_timeout(user_id, data);
const auto &data{*iterator};
synchronizer_timeout(data);
polling.erase(iterator);
pollout.erase(std::begin(pollout));
}
@ -322,8 +268,7 @@ catch(const ircd::ctx::interrupted &e)
/// TODO: The http error response should not yield this context. If the sendq
/// TODO: is backed up the client should be dc'ed.
void
synchronizer_timeout(const std::string &user_id,
const syncpoll &sp)
synchronizer_timeout(const syncpoll &sp)
try
{
const life_guard<client> client
@ -345,6 +290,7 @@ catch(const std::exception &e)
// Main worker stack
//
bool update_sync(const syncpoll &data, const m::event &event, const m::room &);
void synchronize(const m::event &, const m::room::id &);
void synchronize(const m::event &);
@ -354,16 +300,23 @@ try
{
while(1) try
{
const auto &event
std::unique_lock<decltype(m::event::inserted)> lock
{
m::event::inserted.wait()
m::event::inserted
};
synchronize(event);
// reference to the event on the inserter's stack
const auto &event
{
m::event::inserted.wait(lock)
};
if(!syncpoll::polling.empty())
synchronize(event);
}
catch(const timeout &e)
{
ircd::log::debug("Synchronizer worker timeout");
ircd::log::debug("Synchronizer worker: %s", e.what());
}
}
catch(const ircd::ctx::interrupted &e)
@ -374,9 +327,6 @@ catch(const ircd::ctx::interrupted &e)
void
synchronize(const m::event &event)
{
static auto &polling{syncpoll::polling};
static auto &pollout{syncpoll::pollout};
const auto &room_id
{
json::val<m::name::room_id>(event)
@ -388,33 +338,288 @@ synchronize(const m::event &event)
return;
}
std::cout << event << std::endl;
assert(0);
}
void
synchronize(const m::event &event,
const m::room::id &room_id)
{
std::cout << event << std::endl;
static auto &polling{syncpoll::polling};
static auto &pollout{syncpoll::pollout};
const m::room room
{
room_id
};
for(auto it(std::begin(polling)); it != std::end(polling);)
{
const auto &data{*it};
if(!room.membership(data.user_id))
{
++it;
continue;
}
if(update_sync(data, event, room))
{
pollout.erase(data.it);
polling.erase(it++);
}
else ++it;
}
}
std::string
update_sync_room(client &client,
const m::room &room,
const string_view &since,
const m::event &event)
{
std::vector<std::string> state;
if(defined(json::val<m::name::state_key>(event)))
state.emplace_back(json::string(event));
const auto state_serial
{
json::string(state.data(), state.data() + state.size())
};
std::vector<std::string> timeline;
if(!defined(json::val<m::name::state_key>(event)))
timeline.emplace_back(json::string(event));
const auto timeline_serial
{
json::string(timeline.data(), timeline.data() + timeline.size())
};
const json::members body
{
{ "state", json::member { "events", state_serial } },
{ "timeline", json::member { "events", timeline_serial } }
};
return json::string(body);
}
std::string
update_sync_rooms(client &client,
const m::user::id &user_id,
const m::room &room,
const string_view &since,
const m::event &event)
{
std::vector<std::string> r[3];
std::vector<json::member> m[3];
r[0].emplace_back(update_sync_room(client, room, since, event));
m[0].emplace_back(room.room_id, r[0].back());
const std::string join{json::string(m[0].data(), m[0].data() + m[0].size())};
const std::string leave{json::string(m[1].data(), m[1].data() + m[1].size())};
const std::string invite{json::string(m[2].data(), m[2].data() + m[2].size())};
return json::string(json::members
{
{ "join", join },
{ "leave", leave },
{ "invite", invite },
});
}
bool
handle_event(const m::event &event,
const syncpoll &request)
update_sync(const syncpoll &data,
const m::event &event,
const m::room &room)
try
{
const life_guard<const client> client
const life_guard<client> client
{
request.client
data.client
};
// if(request.timeout < now<steady_point>())
// return false;
const auto rooms
{
update_sync_rooms(*client, data.user_id, room, data.since, event)
};
const auto presence
{
"{}"
};
const string_view next_batch
{
at<m::name::event_id>(event)
};
resource::response
{
*client, json::members
{
{ "next_batch", next_batch },
{ "rooms", rooms },
{ "presence", presence }
}
};
return true;
}
catch(const std::exception &e)
catch(const std::bad_weak_ptr &e)
{
log::error("%s", e.what());
return false;
return true;
}
std::string
initial_sync_room(client &client,
const resource::request &request,
const m::room &room,
const bool &full_state)
{
std::vector<std::string> state;
{
const m::event::query<m::event::where::equal> state_query
{
{ "room_id", room.room_id },
{ "state_key", "" },
};
m::events::for_each(state_query, [&state](const auto &event)
{
state.emplace_back(json::string(event));
});
}
const auto state_serial
{
json::string(state.data(), state.data() + state.size())
};
std::vector<std::string> timeline;
{
const m::event::query<m::event::where::equal> timeline_query
{
{ "room_id", room.room_id },
};
m::events::query(timeline_query, [&timeline](const auto &event)
{
if(timeline.size() > 10)
return true;
if(!defined(json::val<m::name::state_key>(event)))
timeline.emplace_back(json::string(event));
return false;
});
}
const auto timeline_serial
{
json::string(timeline.data(), timeline.data() + timeline.size())
};
const json::members body
{
{ "state", json::member { "events", state_serial } },
{ "timeline", json::member { "events", timeline_serial } }
};
return json::string(body);
}
std::string
initial_sync_rooms(client &client,
const resource::request &request,
const string_view &filter_id,
const bool &full_state)
{
const m::event::query<m::event::where::equal> query
{
{ "type", "m.room.member" },
{ "state_key", request.user_id },
};
std::array<std::vector<std::string>, 3> r;
std::array<std::vector<json::member>, 3> m;
m::events::for_each(query, [&r, &m, &client, &request, &full_state](const auto &event)
{
const auto &content{json::val<m::name::content>(event)};
const auto &membership{unquote(content["membership"])};
const m::room::id &room_id{json::val<m::name::room_id>(event)};
const auto i
{
membership == "join"? 0:
membership == "leave"? 1:
membership == "invite"? 2:
-1
};
r.at(i).emplace_back(initial_sync_room(client, request, room_id, full_state));
m.at(i).emplace_back(room_id, r.at(i).back());
});
const std::string join{json::string(m[0].data(), m[0].data() + m[0].size())};
const std::string leave{json::string(m[1].data(), m[1].data() + m[1].size())};
const std::string invite{json::string(m[2].data(), m[2].data() + m[2].size())};
return json::string(json::members
{
{ "join", join },
{ "leave", leave },
{ "invite", invite },
});
}
resource::response
initial_sync(client &client,
const resource::request &request,
const string_view &filter_id,
const bool &full_state,
const string_view &set_presence)
{
char buf[4096] {0};
json::mutable_object body{buf};
body.insert({"foo","bar"});
body.insert({"baz","bam"});
std::cout << buf << std::endl;
const std::string rooms
{
initial_sync_rooms(client, request, filter_id, full_state)
};
const auto presence
{
"{}"
};
const string_view next_batch
{
m::event::head
};
const json::members content
{
{ "event_id", next_batch }
};
m::user::sessions.send(
{
{ "type", "ircd.tape.head" },
{ "state_key", request.query.at("access_token") },
{ "sender", request.user_id },
{ "content", content },
});
return resource::response
{
client, json::members
{
{ "next_batch", next_batch },
{ "rooms", rooms },
{ "presence", presence }
}
};
}

View file

@ -130,8 +130,6 @@ post_filter(client &client, const resource::request::object<const m::filter> &re
json::val<m::name::state>(room)
};
std::cout << json::get<m::name::limit>(state) << std::endl;
const auto &presence
{
// (5.2) The presence updates to include.

27
modules/key/query.cc Normal file
View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using namespace ircd;
mapi::header IRCD_MODULE
{
"Hosts the key server resource"
};

130
modules/key/server.cc Normal file
View file

@ -0,0 +1,130 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using namespace ircd;
mapi::header IRCD_MODULE
{
"federation 2.2.1.1: Publishing Keys"
};
struct server
:resource
{
static constexpr const auto base_url
{
"_matrix/key/v2/server/"
};
using resource::resource;
}
server_resource
{
server::base_url, resource::opts
{
resource::DIRECTORY,
"federation 2.2.1.1: Publishing Keys"
}
};
resource::response
handle_get(client &client,
const resource::request &request)
{
char key_id_buf[256];
const auto key_id
{
urldecode(split(request.head.path, server_resource.base_url).second, key_id_buf)
};
std::string my_key;
m::keys::get(my_host(), [&my_key](const auto &key)
{
my_key = json::string(key);
});
return resource::response
{
client, json::object{my_key}
};
}
resource::method method_get
{
server_resource, "GET", handle_get
};
// __attribute__((constructor))
static
void foop()
{
using namespace ircd;
uint8_t seed_buf[ed25519::SEED_SZ + 10];
const auto seed
{
b64decode(seed_buf, "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1")
};
ed25519::pk pk;
ed25519::sk sk{&pk, seed};
const auto SERVER_NAME {"domain"};
const auto KEY_ID {"ed25519:1"};
const auto test{[&](const std::string &object)
{
const auto sig
{
sk.sign(const_raw_buffer{object})
};
char sigb64_buf[128];
const auto sigb64
{
b64encode_unpadded(sigb64_buf, sig)
};
std::cout << "sig: " << sigb64 << std::endl;
ed25519::sig unsig; const auto unsigb64
{
b64decode(unsig, sigb64)
};
return pk.verify(const_raw_buffer{object}, unsig);
}};
std::cout <<
test(std::string{json::object
{
"{}"
}})
<< std::endl;
std::cout <<
test(json::string(json::members
{
{ "one", 1 },
{ "two", "Two" }
}))
<< std::endl;
}

View file

@ -48,7 +48,12 @@ init_0()
resource::response
get_root(client &client, const resource::request &request)
{
auto it(files.find(request.head.path));
const auto &path
{
request.head.path?: "index.html"
};
auto it(files.find(path));
if(it == end(files))
throw http::error{http::NOT_FOUND};