Start proto::parser, remove msgpack::reader
- proto::parser will likely contain helper functions for parsing messages from a buffer, for now it will explicitly define message parsers for each available message. It also leverages the new unpacker API, which has type safety in mind. These messages should not be unstructured, so it doesn't make sense to use the token API.
This commit is contained in:
parent
c0945246de
commit
b2a6f25306
@ -59,8 +59,9 @@ constexpr decltype(auto) unpack_integral(Itr& inp) noexcept {
|
||||
|
||||
// Defines a range of bytes for binary formats.
|
||||
template <typename T>
|
||||
concept byte_range = std::ranges::contiguous_range<T>
|
||||
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
|
||||
concept unpackable_byte_range =
|
||||
std::ranges::contiguous_range<T>
|
||||
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
|
||||
|
||||
template <format_type F, typename Iter>
|
||||
constexpr inline bool accept_format(Iter& itr) noexcept {
|
||||
@ -168,6 +169,24 @@ struct builtin_unpacker<bool> {
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_unpacker<map_desc> {
|
||||
static constexpr auto unpack(auto& unpacker) noexcept
|
||||
-> tl::expected<map_desc, msgpack::error> {
|
||||
auto marker = *(unpacker.in);
|
||||
if ((marker & ~format::fixmap::mask) == format::fixmap::marker) {
|
||||
return detail::unpack_format<format::fixmap>(unpacker);
|
||||
}
|
||||
switch (marker) {
|
||||
case format::map16::marker:
|
||||
return detail::unpack_format<format::map16>(unpacker);
|
||||
case format::map32::marker:
|
||||
return detail::unpack_format<format::map32>(unpacker);
|
||||
}
|
||||
return tl::make_unexpected(msgpack::error::wrong_type);
|
||||
}
|
||||
};
|
||||
|
||||
// This unpacker will accept any uintX, positive_fixint types.
|
||||
struct unsigned_int_unpacker {
|
||||
static constexpr auto unpack(auto& unpacker) noexcept
|
||||
|
||||
35
include/parselink/proto/error.h
Normal file
35
include/parselink/proto/error.h
Normal file
@ -0,0 +1,35 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Proto
|
||||
//
|
||||
// Error definitions for protocol-related functionality
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef parselink_proto_error_adef9d32bb51411a
|
||||
#define parselink_proto_error_adef9d32bb51411a
|
||||
|
||||
namespace parselink {
|
||||
namespace proto {
|
||||
|
||||
enum class error {
|
||||
system_error,
|
||||
incomplete,
|
||||
unsupported,
|
||||
bad_data,
|
||||
too_large,
|
||||
};
|
||||
|
||||
} // namespace proto
|
||||
} // namespace parselink
|
||||
|
||||
#endif // parselink_proto_error_adef9d32bb51411a
|
||||
@ -18,12 +18,9 @@
|
||||
#ifndef message_0c61530748b9f966
|
||||
#define message_0c61530748b9f966
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace parselink {
|
||||
namespace proto {
|
||||
@ -37,35 +34,40 @@ namespace proto {
|
||||
// This may be revised in the future. The header could remain as msgpack, or
|
||||
// switch to something hand-crafted for saving bits.
|
||||
|
||||
struct error_message {
|
||||
struct base_message {};
|
||||
|
||||
template <typename T>
|
||||
concept message_type = std::is_base_of_v<base_message, T>;
|
||||
|
||||
struct error_message : base_message {
|
||||
std::uint32_t code; // An error code
|
||||
std::string_view what; // A string
|
||||
};
|
||||
|
||||
// C->S: Request to (re)connect.
|
||||
struct connect_message {
|
||||
struct connect_message : base_message {
|
||||
std::uint32_t version; // The version of the client.
|
||||
std::uint32_t user_id; // The user id.
|
||||
std::string_view user_id; // The user id.
|
||||
std::span<std::byte> session_token; // An optional existing session token.
|
||||
};
|
||||
|
||||
// S->C: Challenge to authenticate client as user_id
|
||||
struct challenge_message {
|
||||
struct challenge_message : base_message {
|
||||
std::uint32_t version;
|
||||
std::span<std::byte> challenge;
|
||||
};
|
||||
|
||||
// C->S: Calculated response to a challenge.
|
||||
struct response_message {
|
||||
struct response_message : base_message {
|
||||
std::span<std::byte> response;
|
||||
};
|
||||
|
||||
// S->C: Session token.
|
||||
struct session_established_message {
|
||||
struct session_established_message : base_message {
|
||||
std::span<std::byte> session_token;
|
||||
};
|
||||
|
||||
struct parser_data_message {
|
||||
struct parser_data_message : base_message {
|
||||
std::string_view opts;
|
||||
std::span<std::byte> payload;
|
||||
};
|
||||
|
||||
60
include/parselink/proto/parser.h
Normal file
60
include/parselink/proto/parser.h
Normal file
@ -0,0 +1,60 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Proto
|
||||
//
|
||||
// Parser for extracting messages from msgpack.
|
||||
//
|
||||
// Note: Eventually, it would be nice to automatically generate the parser for
|
||||
// message structures. Definitions below are templated to allow for this.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef parselink_proto_parser_ad351d41fe4c72dd
|
||||
#define parselink_proto_parser_ad351d41fe4c72dd
|
||||
|
||||
#include "parselink/proto/error.h"
|
||||
#include "parselink/proto/message.h"
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
namespace parselink {
|
||||
namespace proto {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Default parse template instantiation -- unsupported message.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
template <message_type M>
|
||||
tl::expected<M, error> parse(std::span<std::byte const> data) noexcept {
|
||||
return tl::make_unexpected(error::unsupported);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Currently-implemented message types.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define PARSELINK_PROTO_SUPPORTED_MESSAGE(msgtype) \
|
||||
tl::expected<msgtype, error> parse_##msgtype( \
|
||||
std::span<std::byte const> data) noexcept; \
|
||||
template <> \
|
||||
constexpr tl::expected<msgtype, error> parse( \
|
||||
std::span<std::byte const> data) noexcept { \
|
||||
return parse_##msgtype(data); \
|
||||
}
|
||||
|
||||
PARSELINK_PROTO_SUPPORTED_MESSAGE(connect_message);
|
||||
|
||||
#undef PARSELINK_PROTO_SUPPORTED_MESSAGE
|
||||
|
||||
} // namespace proto
|
||||
} // namespace parselink
|
||||
|
||||
#endif // parselink_proto_parser_ad351d41fe4c72dd
|
||||
@ -19,6 +19,7 @@
|
||||
#define session_07eae057feface79
|
||||
|
||||
#include "parselink/msgpack/token.h"
|
||||
#include "parselink/proto/error.h"
|
||||
#include "parselink/proto/session_id.h"
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
@ -46,14 +47,6 @@ struct std::hash<std::span<std::byte const>> {
|
||||
namespace parselink {
|
||||
namespace proto {
|
||||
|
||||
enum class error {
|
||||
system_error,
|
||||
incomplete,
|
||||
unsupported,
|
||||
bad_data,
|
||||
too_large,
|
||||
};
|
||||
|
||||
// Structure containing header information parsed from a buffer.
|
||||
struct header_info {
|
||||
std::uint32_t message_size; // Size of the message, minus the header.
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <span>
|
||||
#include <system_error>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
@ -69,24 +70,22 @@ struct [[nodiscard]] handle {
|
||||
int fd_{-1};
|
||||
};
|
||||
|
||||
inline tl::expected<handle, std::error_code> open(
|
||||
std::string_view path, int flags) noexcept {
|
||||
handle file(::open(std::string{path}.c_str(), flags));
|
||||
inline tl::expected<handle, std::errc> open(
|
||||
std::string_view path, int flags, int mode = {}) noexcept {
|
||||
handle file(::open(std::string{path}.c_str(), flags, mode));
|
||||
if (file) {
|
||||
return file;
|
||||
} else {
|
||||
return tl::make_unexpected(
|
||||
std::make_error_code(static_cast<std::errc>(-*file)));
|
||||
return tl::make_unexpected(static_cast<std::errc>(-*file));
|
||||
}
|
||||
}
|
||||
|
||||
inline tl::expected<std::vector<std::byte>, std::error_code> read_some(
|
||||
inline tl::expected<std::vector<std::byte>, std::errc> read_some(
|
||||
handle file, std::size_t amount) noexcept {
|
||||
std::vector<std::byte> data;
|
||||
|
||||
if (!file) {
|
||||
return tl::make_unexpected(
|
||||
std::make_error_code(static_cast<std::errc>(-*file)));
|
||||
return tl::make_unexpected(static_cast<std::errc>(-*file));
|
||||
}
|
||||
|
||||
data.resize(amount);
|
||||
@ -102,13 +101,12 @@ inline tl::expected<std::vector<std::byte>, std::error_code> read_some(
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
|
||||
inline tl::expected<std::vector<T>, std::error_code> read(
|
||||
handle file) noexcept {
|
||||
inline tl::expected<std::vector<T>, std::errc> read(
|
||||
handle const& file) noexcept {
|
||||
std::vector<T> data;
|
||||
|
||||
if (!file) {
|
||||
return tl::make_unexpected(
|
||||
std::make_error_code(static_cast<std::errc>(-*file)));
|
||||
return tl::make_unexpected(static_cast<std::errc>(-*file));
|
||||
}
|
||||
|
||||
auto cur = lseek(*file, 0, SEEK_CUR);
|
||||
@ -126,6 +124,31 @@ inline tl::expected<std::vector<T>, std::error_code> read(
|
||||
return data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
|
||||
inline tl::expected<std::size_t, std::errc> write(
|
||||
handle const& file, std::span<T> data) noexcept {
|
||||
if (!file) {
|
||||
return tl::make_unexpected(static_cast<std::errc>(-*file));
|
||||
}
|
||||
|
||||
lseek(*file, 0, SEEK_SET);
|
||||
|
||||
auto result = ftruncate(*file, 0);
|
||||
|
||||
if (result < 0) {
|
||||
return tl::make_unexpected(static_cast<std::errc>(errno));
|
||||
}
|
||||
|
||||
auto amt_written = ::write(*file, data.data(), data.size());
|
||||
|
||||
if (result < 0) {
|
||||
return tl::make_unexpected(static_cast<std::errc>(errno));
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(amt_written);
|
||||
}
|
||||
|
||||
} // namespace file
|
||||
} // namespace utility
|
||||
} // namespace parselink
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
cc_library(
|
||||
name = "proto",
|
||||
srcs = [
|
||||
"parser.cpp",
|
||||
"session.cpp",
|
||||
"session_id.cpp",
|
||||
],
|
||||
|
||||
70
source/proto/parser.cpp
Normal file
70
source/proto/parser.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Proto
|
||||
//
|
||||
// Parser implementations for various msgpack messages.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "parselink/proto/parser.h"
|
||||
#include "parselink/logging.h"
|
||||
|
||||
#include "parselink/msgpack/core/unpacker.h"
|
||||
|
||||
namespace parselink {
|
||||
namespace proto {
|
||||
|
||||
namespace {
|
||||
|
||||
logging::logger logger{"parser"};
|
||||
|
||||
constexpr auto assign(auto& param, msgpack::unpacker& unpacker) noexcept
|
||||
-> tl::expected<void, error> {
|
||||
auto do_assign = [¶m](auto&& v) { param = v; };
|
||||
return unpacker.unpack<std::decay_t<decltype(param)>>()
|
||||
.map(do_assign)
|
||||
.map_error([](auto) { return error::bad_data; });
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
tl::expected<connect_message, error> parse_connect_message(
|
||||
std::span<std::byte const> data) noexcept {
|
||||
msgpack::unpacker unpacker(data);
|
||||
connect_message msg;
|
||||
|
||||
constexpr static tl::unexpected<error> bad_data(error::bad_data);
|
||||
|
||||
auto type = unpacker.unpack<std::string_view>();
|
||||
if (!type || type != "connect") return bad_data;
|
||||
|
||||
auto entries = unpacker.unpack<msgpack::map_desc>();
|
||||
if (!entries) return bad_data;
|
||||
|
||||
for (auto i = entries->count; i != 0; --i) {
|
||||
auto key = unpacker.unpack<std::string_view>();
|
||||
if (!key) return bad_data;
|
||||
if (key == "version") {
|
||||
if (auto val = assign(msg.version, unpacker)) continue;
|
||||
} else if (key == "user_id") {
|
||||
if (auto val = assign(msg.user_id, unpacker)) continue;
|
||||
}
|
||||
logger.debug("Unknown key: {}", *key);
|
||||
return bad_data;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
} // namespace proto
|
||||
} // namespace parselink
|
||||
@ -21,9 +21,9 @@
|
||||
#include "parselink/server.h"
|
||||
|
||||
#include "parselink/logging.h"
|
||||
#include "parselink/msgpack/core/writer.h"
|
||||
#include "parselink/msgpack/token/reader.h"
|
||||
#include "parselink/msgpack/token/views.h"
|
||||
#include "parselink/msgpack/core/packer.h"
|
||||
#include "parselink/msgpack/core/unpacker.h"
|
||||
#include "parselink/proto/parser.h"
|
||||
#include "parselink/proto/session.h"
|
||||
#include "parselink/utility/file.h"
|
||||
|
||||
@ -75,6 +75,13 @@ struct fmt::formatter<boost::system::error_code>
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<tl::monostate> : fmt::formatter<std::string_view> {
|
||||
constexpr auto format(auto const&, auto& ctx) const {
|
||||
return fmt::formatter<std::string_view>::format(".", ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<msgpack::token> {
|
||||
template <typename ParseContext>
|
||||
@ -170,12 +177,12 @@ public:
|
||||
|
||||
net::awaitable<tl::expected<proto::session*, proto::error>> create_session(
|
||||
std::shared_ptr<user_connection> const& conn,
|
||||
proto::connect_info const& info);
|
||||
proto::connect_message const& info);
|
||||
|
||||
private:
|
||||
awaitable<void> user_listen();
|
||||
|
||||
tl::expected<tl::monostate, std::error_code> load_keys() noexcept;
|
||||
tl::expected<tl::monostate, std::errc> load_keys() noexcept;
|
||||
|
||||
hydro_kx_keypair kp_;
|
||||
net::io_context io_context_;
|
||||
@ -268,12 +275,7 @@ public:
|
||||
co_return;
|
||||
}
|
||||
|
||||
auto reader = msgpack::token_reader(msgbuf);
|
||||
std::array<msgpack::token, 32> tokens;
|
||||
auto connect_info =
|
||||
reader.read_some(tokens)
|
||||
.map_error([](auto) { return proto::error::bad_data; })
|
||||
.and_then(proto::parse_connect);
|
||||
auto connect_info = proto::parse<proto::connect_message>(msgbuf);
|
||||
|
||||
if (!connect_info) {
|
||||
logger.error("Session failed: {}", connect_info.error());
|
||||
@ -303,7 +305,7 @@ monolithic_server::monolithic_server(std::string_view address,
|
||||
, addr_(net::ip::address::from_string(std::string{address}))
|
||||
, user_port_{user_port}
|
||||
, websocket_port_{websocket_port} {
|
||||
load_keys();
|
||||
logger.debug("Loaded keys: {}", load_keys());
|
||||
|
||||
logger.debug("Creating monolithic_server(address = {}, user_port = {}, "
|
||||
"websocket_port = {})",
|
||||
@ -336,43 +338,73 @@ std::error_code monolithic_server::run() noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
tl::expected<tl::monostate, std::error_code>
|
||||
monolithic_server::load_keys() noexcept {
|
||||
std::string_view filename = "server_kp.keys";
|
||||
tl::expected<tl::monostate, std::errc> monolithic_server::load_keys() noexcept {
|
||||
std::string_view filename = "/home/rihya/server_kp.keys";
|
||||
|
||||
auto load_key =
|
||||
[this](auto raw) -> tl::expected<tl::monostate, std::error_code> {
|
||||
if (sizeof(kp_) != raw.size()) {
|
||||
return tl::make_unexpected(
|
||||
std::make_error_code(std::errc::bad_message));
|
||||
}
|
||||
std::ranges::transform(std::begin(raw),
|
||||
std::next(std::begin(raw), sizeof(kp_.pk)), std::begin(kp_.pk),
|
||||
enum class result { loaded, generated };
|
||||
|
||||
auto load_key = [this](auto const& raw) -> tl::expected<result, std::errc> {
|
||||
msgpack::unpacker unpacker(raw);
|
||||
|
||||
auto pk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.pk)>>();
|
||||
if (!pk) return tl::make_unexpected(std::errc::bad_message);
|
||||
auto sk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.sk)>>();
|
||||
if (!sk) return tl::make_unexpected(std::errc::bad_message);
|
||||
|
||||
std::ranges::transform(pk->begin(), pk->end(), std::begin(kp_.pk),
|
||||
[](auto c) { return std::bit_cast<unsigned char>(c); });
|
||||
return tl::monostate{};
|
||||
|
||||
std::ranges::transform(sk->begin(), sk->end(), std::begin(kp_.sk),
|
||||
[](auto c) { return std::bit_cast<unsigned char>(c); });
|
||||
|
||||
return result::loaded;
|
||||
};
|
||||
|
||||
auto generate_keys = [this](auto const& err)
|
||||
-> tl::expected<tl::monostate, std::error_code> {
|
||||
auto generate_keys =
|
||||
[this](auto const& err) -> tl::expected<result, std::errc> {
|
||||
logger.warning("Could not load server keys, generating a keypair");
|
||||
hydro_kx_keygen(&kp_);
|
||||
std::span<std::byte, sizeof(kp_.pk)> pk(
|
||||
reinterpret_cast<std::byte*>(kp_.pk), sizeof(kp_.pk));
|
||||
std::span<std::byte, sizeof(kp_.sk)> sk(
|
||||
reinterpret_cast<std::byte*>(kp_.sk), sizeof(kp_.pk));
|
||||
logger.debug("\n\tPK: {}\n\tSK: {}", pk, sk);
|
||||
return tl::monostate{};
|
||||
return result::generated;
|
||||
};
|
||||
|
||||
return utility::file::open(filename, O_RDWR | O_CREAT)
|
||||
.and_then(utility::file::read<std::byte>)
|
||||
.and_then(load_key)
|
||||
.or_else(generate_keys);
|
||||
auto commit = [this](auto const& handle)
|
||||
-> tl::expected<tl::monostate, std::errc> {
|
||||
std::vector<std::byte> buff(4 + sizeof(kp_.pk) + sizeof(kp_.sk));
|
||||
std::span<std::byte> pk(
|
||||
reinterpret_cast<std::byte*>(kp_.pk), sizeof(kp_.pk));
|
||||
std::span<std::byte> sk(
|
||||
reinterpret_cast<std::byte*>(kp_.sk), sizeof(kp_.pk));
|
||||
msgpack::packer packer(buff);
|
||||
packer.pack(pk);
|
||||
packer.pack(sk);
|
||||
return utility::file::write(handle, packer.subspan()).map([](auto) {
|
||||
logger.info("Wrote new keys to disk");
|
||||
return tl::monostate{};
|
||||
});
|
||||
};
|
||||
|
||||
auto load_or_generate_keys = [&load_key, &generate_keys, &commit](
|
||||
auto const& handle)
|
||||
-> tl::expected<tl::monostate, std::errc> {
|
||||
return utility::file::read<std::byte>(handle)
|
||||
.and_then(load_key)
|
||||
.or_else(generate_keys)
|
||||
.and_then([&handle, &commit](auto r)
|
||||
-> tl::expected<tl::monostate, std::errc> {
|
||||
if (r == result::generated) {
|
||||
return commit(handle);
|
||||
}
|
||||
return tl::monostate{};
|
||||
});
|
||||
};
|
||||
|
||||
return utility::file::open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)
|
||||
.and_then(load_or_generate_keys);
|
||||
}
|
||||
|
||||
net::awaitable<tl::expected<proto::session*, proto::error>>
|
||||
monolithic_server::create_session(std::shared_ptr<user_connection> const& conn,
|
||||
proto::connect_info const& info) {
|
||||
proto::connect_message const& info) {
|
||||
// Move to session strand.
|
||||
co_await net::post(session_strand_,
|
||||
net::bind_executor(session_strand_, use_awaitable));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user