eliminate using namespace
This commit is contained in:
+69
-77
@@ -8,10 +8,8 @@
|
||||
#include "Loggers.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string encode_chat_message(Version version, const string& message) {
|
||||
string encoded_message;
|
||||
static std::string encode_chat_message(Version version, const std::string& message) {
|
||||
std::string encoded_message;
|
||||
encoded_message.resize(8, 0);
|
||||
encoded_message += uses_utf16(version) ? tt_utf8_to_utf16("\tE" + message) : tt_utf8_to_ascii("\tE" + message);
|
||||
encoded_message.resize((encoded_message.size() + 3) & (~3));
|
||||
@@ -19,14 +17,10 @@ static string encode_chat_message(Version version, const string& message) {
|
||||
}
|
||||
|
||||
ReplaySession::Event::Event(Type type, uint64_t client_id, size_t line_num)
|
||||
: type(type),
|
||||
client_id(client_id),
|
||||
allow_size_disparity(false),
|
||||
complete(false),
|
||||
line_num(line_num) {}
|
||||
: type(type), client_id(client_id), allow_size_disparity(false), complete(false), line_num(line_num) {}
|
||||
|
||||
string ReplaySession::Event::str() const {
|
||||
string ret;
|
||||
std::string ReplaySession::Event::str() const {
|
||||
std::string ret;
|
||||
if (this->type == Type::CONNECT) {
|
||||
ret = std::format("Event[{}, CONNECT", this->client_id);
|
||||
} else if (this->type == Type::DISCONNECT) {
|
||||
@@ -46,11 +40,11 @@ string ReplaySession::Event::str() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
||||
ReplaySession::Client::Client(std::shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
||||
: id(id),
|
||||
port(port),
|
||||
version(version),
|
||||
channel(make_shared<PeerChannel>(
|
||||
channel(std::make_shared<PeerChannel>(
|
||||
io_context,
|
||||
this->version,
|
||||
Language::ENGLISH,
|
||||
@@ -60,12 +54,12 @@ ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t
|
||||
false,
|
||||
false)) {}
|
||||
|
||||
string ReplaySession::Client::str() const {
|
||||
std::string ReplaySession::Client::str() const {
|
||||
return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version));
|
||||
}
|
||||
|
||||
shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, shared_ptr<Client> c, size_t line_num) {
|
||||
auto event = make_shared<Event>(type, c->id, line_num);
|
||||
std::shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num) {
|
||||
auto event = std::make_shared<Event>(type, c->id, line_num);
|
||||
if (!this->last_event.get()) {
|
||||
this->first_event = event;
|
||||
} else {
|
||||
@@ -78,7 +72,7 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, s
|
||||
return event;
|
||||
}
|
||||
|
||||
void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
|
||||
auto version = this->clients.at(ev->client_id)->version;
|
||||
|
||||
void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
|
||||
@@ -325,11 +319,11 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
throw std::logic_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
|
||||
: state(state),
|
||||
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
||||
commands_sent(0),
|
||||
@@ -338,13 +332,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
bytes_received(0),
|
||||
idle_timeout_timer(*this->state->io_context),
|
||||
run_failed(false) {
|
||||
shared_ptr<Event> parsing_command = nullptr;
|
||||
std::shared_ptr<Event> parsing_command = nullptr;
|
||||
|
||||
size_t line_num = 0;
|
||||
size_t num_events = 0;
|
||||
while (!feof(input_log)) {
|
||||
line_num++;
|
||||
string line = phosg::fgets(input_log);
|
||||
std::string line = phosg::fgets(input_log);
|
||||
if (line.ends_with("\n")) {
|
||||
line.resize(line.size() - 1);
|
||||
}
|
||||
@@ -353,11 +347,11 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
}
|
||||
|
||||
if (parsing_command.get()) {
|
||||
string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
||||
std::string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
||||
if (line.starts_with(expected_start)) {
|
||||
// Parse out the hex part of the hex/ASCII dump
|
||||
string mask_bytes;
|
||||
string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
||||
std::string mask_bytes;
|
||||
std::string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
||||
parsing_command->data += data_bytes;
|
||||
parsing_command->mask += mask_bytes;
|
||||
continue;
|
||||
@@ -378,35 +372,35 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
if (line.starts_with("### cc ")) {
|
||||
// ### cc $<chat command>
|
||||
if (this->clients.size() != 1) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
|
||||
line_num));
|
||||
}
|
||||
shared_ptr<Event> event;
|
||||
std::shared_ptr<Event> event;
|
||||
try {
|
||||
auto c = this->clients.begin()->second;
|
||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||
event->data = encode_chat_message(c->version, line.substr(7));
|
||||
num_events++;
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (line.starts_with("### on C-")) {
|
||||
// ### on C-{} cc <chat command>
|
||||
shared_ptr<Event> event;
|
||||
std::shared_ptr<Event> event;
|
||||
try {
|
||||
size_t end_offset;
|
||||
auto c = this->clients.at(stoull(line.substr(9), &end_offset, 16));
|
||||
if (line.compare(end_offset + 9, 4, " cc ") != 0) {
|
||||
throw runtime_error("malformed `on C-X cc $...` shortcut command");
|
||||
throw std::runtime_error("malformed `on C-X cc $...` shortcut command");
|
||||
}
|
||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
|
||||
num_events++;
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -414,25 +408,25 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
// I <pid/ts> - [GameServer] Client connected: C-1 via TG-9000-GC_V3-gc-jp10-game_server
|
||||
// I <pid/ts> - [GameServer] Client connected: C-3 via TSI-9000-GC_V3-game_server
|
||||
size_t offset = line.find(" - [GameServer] Client connected: C-");
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
|
||||
if (!tokens[8].starts_with("C-")) {
|
||||
throw runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
|
||||
auto listen_tokens = phosg::split(tokens[10], '-');
|
||||
if (listen_tokens.size() < 4) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
|
||||
}
|
||||
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
|
||||
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
|
||||
|
||||
auto c = make_shared<Client>(state->io_context, client_id, port, version);
|
||||
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
|
||||
if (!this->clients.emplace(c->id, c).second) {
|
||||
throw runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||
}
|
||||
this->create_event(Event::Type::CONNECT, c, line_num);
|
||||
num_events++;
|
||||
@@ -441,24 +435,24 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
|
||||
// I <pid/ts> - [GameServer] Running cleanup tasks for C-{}
|
||||
offset = line.find(" - [GameServer] Running cleanup tasks for C-");
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 11) {
|
||||
throw runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||
}
|
||||
if (!tokens[10].starts_with("C-")) {
|
||||
throw runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
|
||||
try {
|
||||
auto& c = this->clients.at(client_id);
|
||||
if (c->disconnect_event.get()) {
|
||||
throw runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||
}
|
||||
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
|
||||
num_events++;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -466,13 +460,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
// I <pid/ts> - [Commands] Sending to C-{:X} (...)
|
||||
// I <pid/ts> - [Commands] Received from C-{:X} (...)
|
||||
offset = line.find(" - [Commands] Sending to C-");
|
||||
if (offset == string::npos) {
|
||||
if (offset == std::string::npos) {
|
||||
offset = line.find(" - [Commands] Received from C-");
|
||||
}
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 10) {
|
||||
throw runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||
}
|
||||
bool from_client = (tokens[6] == "Received");
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
@@ -480,8 +474,8 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
parsing_command = this->create_event(
|
||||
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
|
||||
num_events++;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -490,14 +484,12 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
|
||||
replay_log.debug_f("{} clients in log", this->clients.size());
|
||||
for (const auto& it : this->clients) {
|
||||
string client_str = it.second->str();
|
||||
replay_log.debug_f(" {} => {}", it.first, client_str);
|
||||
replay_log.debug_f(" {} => {}", it.first, it.second->str());
|
||||
}
|
||||
|
||||
replay_log.debug_f("{} events in replay log", num_events);
|
||||
for (auto ev = this->first_event; ev != nullptr; ev = ev->next_event) {
|
||||
string ev_str = ev->str();
|
||||
replay_log.debug_f(" {}", ev_str);
|
||||
replay_log.debug_f(" {}", ev->str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,25 +504,25 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
switch (this->first_event->type) {
|
||||
case Event::Type::CONNECT: {
|
||||
if (c->channel->connected()) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
|
||||
}
|
||||
|
||||
shared_ptr<const PortConfiguration> port_config;
|
||||
std::shared_ptr<const PortConfiguration> port_config;
|
||||
try {
|
||||
port_config = this->state->number_to_port_config.at(c->port);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format(
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
|
||||
}
|
||||
|
||||
auto server_channel = make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||
PeerChannel::link_peers(c->channel, server_channel);
|
||||
|
||||
if (this->state->game_server.get()) {
|
||||
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
|
||||
} else {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) no server available for connection", this->first_event->line_num));
|
||||
}
|
||||
break;
|
||||
@@ -542,7 +534,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
|
||||
case Event::Type::SEND:
|
||||
if (!c->channel->connected()) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
|
||||
}
|
||||
c->channel->send(this->first_event->data);
|
||||
@@ -552,25 +544,25 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
|
||||
case Event::Type::RECEIVE: {
|
||||
if (!c->channel->connected()) {
|
||||
throw runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||
this->first_event->line_num));
|
||||
}
|
||||
if (c->receive_events.front() != this->first_event) {
|
||||
throw logic_error("Client receive events are out of order");
|
||||
throw std::logic_error("Client receive events are out of order");
|
||||
}
|
||||
|
||||
this->reschedule_idle_timeout();
|
||||
auto msg = co_await c->channel->recv();
|
||||
|
||||
// TODO: Use the iovec form of phosg::print_data here instead of prepend_command_header (which copies data)
|
||||
string full_command = prepend_command_header(
|
||||
std::string full_command = prepend_command_header(
|
||||
c->version, (c->channel->crypt_in.get() != nullptr), msg.command, msg.flag, msg.data);
|
||||
this->commands_received++;
|
||||
this->bytes_received += full_command.size();
|
||||
|
||||
if (c->receive_events.empty()) {
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error("received unexpected command for client");
|
||||
throw std::runtime_error("received unexpected command for client");
|
||||
}
|
||||
|
||||
auto& ev = c->receive_events.front();
|
||||
@@ -579,15 +571,15 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||
}
|
||||
for (size_t x = 0; x < min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
|
||||
replay_log.error_f("Expected command:");
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,8 +592,8 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
case Version::BB_PATCH:
|
||||
if (msg.command == 0x02) {
|
||||
auto& cmd = msg.check_size_t<S_ServerInit_Patch_02>();
|
||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
}
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
@@ -618,11 +610,11 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
if (msg.command == 0x02 || msg.command == 0x17 || msg.command == 0x91 || msg.command == 0x9B) {
|
||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
||||
if (is_v1_or_v2(c->version)) {
|
||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
} else { // V3
|
||||
c->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -631,19 +623,19 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
|
||||
// first one here.
|
||||
c->channel->crypt_in = make_shared<PSOBBEncryption>(
|
||||
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
||||
c->channel->crypt_out = make_shared<PSOBBEncryption>(
|
||||
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unsupported encryption version");
|
||||
throw std::logic_error("unsupported encryption version");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("unhandled event type");
|
||||
throw std::logic_error("unhandled event type");
|
||||
}
|
||||
this->first_event->complete = true;
|
||||
}
|
||||
@@ -653,7 +645,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
this->last_event = nullptr;
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
replay_log.error_f("Replay failed: {}", e.what());
|
||||
if (this->first_event) {
|
||||
replay_log.error_f("Next pending event: {}", this->first_event->str());
|
||||
|
||||
Reference in New Issue
Block a user