add file message
This commit is contained in:
parent
f429feaaa8
commit
05d55139f5
@ -186,6 +186,44 @@ bool NGCEXTEventProvider::parse_ft1_data_ack(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NGCEXTEventProvider::parse_ft1_message(
|
||||||
|
uint32_t group_number, uint32_t peer_number,
|
||||||
|
const uint8_t* data, size_t data_size,
|
||||||
|
bool _private
|
||||||
|
) {
|
||||||
|
if (_private) {
|
||||||
|
std::cerr << "NGCEXT: ft1_message cant be private (yet)\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Events::NGCEXT_ft1_message e;
|
||||||
|
e.group_number = group_number;
|
||||||
|
e.peer_number = peer_number;
|
||||||
|
size_t curser = 0;
|
||||||
|
|
||||||
|
// - 4 byte (message_id)
|
||||||
|
e.message_id = 0u;
|
||||||
|
_DATA_HAVE(sizeof(e.message_id), std::cerr << "NGCEXT: packet too small, missing message_id\n"; return false)
|
||||||
|
for (size_t i = 0; i < sizeof(e.message_id); i++, curser++) {
|
||||||
|
e.message_id |= uint32_t(data[curser]) << (i*8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - 4 byte (file_kind)
|
||||||
|
e.file_kind = 0u;
|
||||||
|
_DATA_HAVE(sizeof(e.file_kind), std::cerr << "NGCEXT: packet too small, missing file_kind\n"; return false)
|
||||||
|
for (size_t i = 0; i < sizeof(e.file_kind); i++, curser++) {
|
||||||
|
e.file_kind |= uint32_t(data[curser]) << (i*8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - X bytes (file_kind dependent id, differnt sizes)
|
||||||
|
e.file_id = {data+curser, data+curser+(data_size-curser)};
|
||||||
|
|
||||||
|
return dispatch(
|
||||||
|
NGCEXT_Event::FT1_MESSAGE,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool NGCEXTEventProvider::handlePacket(
|
bool NGCEXTEventProvider::handlePacket(
|
||||||
const uint32_t group_number,
|
const uint32_t group_number,
|
||||||
const uint32_t peer_number,
|
const uint32_t peer_number,
|
||||||
@ -214,6 +252,8 @@ bool NGCEXTEventProvider::handlePacket(
|
|||||||
return parse_ft1_data(group_number, peer_number, data+1, data_size-1, _private);
|
return parse_ft1_data(group_number, peer_number, data+1, data_size-1, _private);
|
||||||
case NGCEXT_Event::FT1_DATA_ACK:
|
case NGCEXT_Event::FT1_DATA_ACK:
|
||||||
return parse_ft1_data_ack(group_number, peer_number, data+1, data_size-1, _private);
|
return parse_ft1_data_ack(group_number, peer_number, data+1, data_size-1, _private);
|
||||||
|
case NGCEXT_Event::FT1_MESSAGE:
|
||||||
|
return parse_ft1_message(group_number, peer_number, data+1, data_size-1, _private);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,21 @@ namespace Events {
|
|||||||
std::vector<uint16_t> sequence_ids;
|
std::vector<uint16_t> sequence_ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NGCEXT_ft1_message {
|
||||||
|
uint32_t group_number;
|
||||||
|
uint32_t peer_number;
|
||||||
|
|
||||||
|
// - 4 byte (message_id)
|
||||||
|
uint32_t message_id;
|
||||||
|
|
||||||
|
// request the other side to initiate a FT
|
||||||
|
// - 4 byte (file_kind)
|
||||||
|
uint32_t file_kind;
|
||||||
|
|
||||||
|
// - X bytes (file_kind dependent id, differnt sizes)
|
||||||
|
std::vector<uint8_t> file_id;
|
||||||
|
};
|
||||||
|
|
||||||
} // Events
|
} // Events
|
||||||
|
|
||||||
enum class NGCEXT_Event : uint8_t {
|
enum class NGCEXT_Event : uint8_t {
|
||||||
@ -131,7 +146,7 @@ enum class NGCEXT_Event : uint8_t {
|
|||||||
HS1_RESPONSE_LAST_IDS,
|
HS1_RESPONSE_LAST_IDS,
|
||||||
|
|
||||||
// request the other side to initiate a FT
|
// request the other side to initiate a FT
|
||||||
// - 1 byte (file_kind)
|
// - 4 byte (file_kind)
|
||||||
// - X bytes (file_kind dependent id, differnt sizes)
|
// - X bytes (file_kind dependent id, differnt sizes)
|
||||||
FT1_REQUEST = 0x80 | 8u,
|
FT1_REQUEST = 0x80 | 8u,
|
||||||
|
|
||||||
@ -139,7 +154,7 @@ enum class NGCEXT_Event : uint8_t {
|
|||||||
|
|
||||||
// tell the other side you want to start a FT
|
// tell the other side you want to start a FT
|
||||||
// TODO: might use id layer instead. with it, it would look similar to friends_ft
|
// TODO: might use id layer instead. with it, it would look similar to friends_ft
|
||||||
// - 1 byte (file_kind)
|
// - 4 byte (file_kind)
|
||||||
// - 8 bytes (data size, can be 0 if unknown, BUT files have to be atleast 1 byte)
|
// - 8 bytes (data size, can be 0 if unknown, BUT files have to be atleast 1 byte)
|
||||||
// - 1 byte (temporary_file_tf_id, for this peer only, technically just a prefix to distinguish between simultainious fts)
|
// - 1 byte (temporary_file_tf_id, for this peer only, technically just a prefix to distinguish between simultainious fts)
|
||||||
// - X bytes (file_kind dependent id, differnt sizes)
|
// - X bytes (file_kind dependent id, differnt sizes)
|
||||||
@ -168,6 +183,14 @@ enum class NGCEXT_Event : uint8_t {
|
|||||||
// - ]
|
// - ]
|
||||||
FT1_DATA_ACK,
|
FT1_DATA_ACK,
|
||||||
|
|
||||||
|
// send file as message
|
||||||
|
// basically the opposite of request
|
||||||
|
// contains file_kind and file_id (and timestamp?)
|
||||||
|
// - 4 byte (message_id)
|
||||||
|
// - 4 byte (file_kind)
|
||||||
|
// - X bytes (file_kind dependent id, differnt sizes)
|
||||||
|
FT1_MESSAGE,
|
||||||
|
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,6 +203,7 @@ struct NGCEXTEventI {
|
|||||||
virtual bool onEvent(const Events::NGCEXT_ft1_init_ack&) { return false; }
|
virtual bool onEvent(const Events::NGCEXT_ft1_init_ack&) { return false; }
|
||||||
virtual bool onEvent(const Events::NGCEXT_ft1_data&) { return false; }
|
virtual bool onEvent(const Events::NGCEXT_ft1_data&) { return false; }
|
||||||
virtual bool onEvent(const Events::NGCEXT_ft1_data_ack&) { return false; }
|
virtual bool onEvent(const Events::NGCEXT_ft1_data_ack&) { return false; }
|
||||||
|
virtual bool onEvent(const Events::NGCEXT_ft1_message&) { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
using NGCEXTEventProviderI = EventProviderI<NGCEXTEventI>;
|
using NGCEXTEventProviderI = EventProviderI<NGCEXTEventI>;
|
||||||
@ -233,6 +257,12 @@ class NGCEXTEventProvider : public ToxEventI, public NGCEXTEventProviderI {
|
|||||||
bool _private
|
bool _private
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool parse_ft1_message(
|
||||||
|
uint32_t group_number, uint32_t peer_number,
|
||||||
|
const uint8_t* data, size_t data_size,
|
||||||
|
bool _private
|
||||||
|
);
|
||||||
|
|
||||||
bool handlePacket(
|
bool handlePacket(
|
||||||
const uint32_t group_number,
|
const uint32_t group_number,
|
||||||
const uint32_t peer_number,
|
const uint32_t peer_number,
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <solanaceae/toxcore/utils.hpp>
|
#include <solanaceae/toxcore/utils.hpp>
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -118,6 +120,29 @@ bool NGCFT1::sendPKG_FT1_DATA_ACK(
|
|||||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NGCFT1::sendPKG_FT1_MESSAGE(
|
||||||
|
uint32_t group_number,
|
||||||
|
uint32_t message_id,
|
||||||
|
uint32_t file_kind,
|
||||||
|
const uint8_t* file_id, size_t file_id_size
|
||||||
|
) {
|
||||||
|
std::vector<uint8_t> pkg;
|
||||||
|
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_MESSAGE));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(message_id); i++) {
|
||||||
|
pkg.push_back((message_id>>(i*8)) & 0xff);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||||
|
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < file_id_size; i++) {
|
||||||
|
pkg.push_back(file_id[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lossless
|
||||||
|
return _t.toxGroupSendCustomPacket(group_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<LEDBAT::SeqIDType>& timeouts_set) {
|
void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<LEDBAT::SeqIDType>& timeouts_set) {
|
||||||
auto& tf_opt = peer.send_transfers.at(idx);
|
auto& tf_opt = peer.send_transfers.at(idx);
|
||||||
assert(tf_opt.has_value());
|
assert(tf_opt.has_value());
|
||||||
@ -306,6 +331,7 @@ NGCFT1::NGCFT1(
|
|||||||
_neep.subscribe(this, NGCEXT_Event::FT1_INIT_ACK);
|
_neep.subscribe(this, NGCEXT_Event::FT1_INIT_ACK);
|
||||||
_neep.subscribe(this, NGCEXT_Event::FT1_DATA);
|
_neep.subscribe(this, NGCEXT_Event::FT1_DATA);
|
||||||
_neep.subscribe(this, NGCEXT_Event::FT1_DATA_ACK);
|
_neep.subscribe(this, NGCEXT_Event::FT1_DATA_ACK);
|
||||||
|
_neep.subscribe(this, NGCEXT_Event::FT1_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NGCFT1::iterate(float time_delta) {
|
void NGCFT1::iterate(float time_delta) {
|
||||||
@ -383,6 +409,19 @@ bool NGCFT1::NGC_FT1_send_init_private(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NGCFT1::NGC_FT1_send_message_public(
|
||||||
|
uint32_t group_number,
|
||||||
|
uint32_t& message_id,
|
||||||
|
uint32_t file_kind,
|
||||||
|
const uint8_t* file_id, size_t file_id_size
|
||||||
|
) {
|
||||||
|
// create msg_id
|
||||||
|
message_id = randombytes_random();
|
||||||
|
|
||||||
|
// TODO: check return value
|
||||||
|
return sendPKG_FT1_MESSAGE(group_number, message_id, file_kind, file_id, file_id_size);
|
||||||
|
}
|
||||||
|
|
||||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_request& e) {
|
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_request& e) {
|
||||||
std::cout << "NGCFT1: FT1_REQUEST fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
std::cout << "NGCFT1: FT1_REQUEST fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||||
|
|
||||||
@ -577,3 +616,19 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_message& e) {
|
||||||
|
std::cout << "NGCFT1: FT1_MESSAGE mid:" << e.message_id << " fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||||
|
|
||||||
|
// .... just rethrow??
|
||||||
|
// TODO: dont
|
||||||
|
return dispatch(
|
||||||
|
NGCFT1_Event::recv_message,
|
||||||
|
Events::NGCFT1_recv_message{
|
||||||
|
e.group_number, e.peer_number,
|
||||||
|
e.message_id,
|
||||||
|
static_cast<NGCFT1_file_kind>(e.file_kind),
|
||||||
|
e.file_id.data(), e.file_id.size()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,18 @@ namespace Events {
|
|||||||
// TODO: reason
|
// TODO: reason
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NGCFT1_recv_message {
|
||||||
|
uint32_t group_number;
|
||||||
|
uint32_t peer_number;
|
||||||
|
|
||||||
|
uint32_t message_id;
|
||||||
|
|
||||||
|
NGCFT1_file_kind file_kind;
|
||||||
|
|
||||||
|
const uint8_t* file_id;
|
||||||
|
size_t file_id_size;
|
||||||
|
};
|
||||||
|
|
||||||
} // Events
|
} // Events
|
||||||
|
|
||||||
enum class NGCFT1_Event : uint8_t {
|
enum class NGCFT1_Event : uint8_t {
|
||||||
@ -95,6 +107,8 @@ enum class NGCFT1_Event : uint8_t {
|
|||||||
recv_done,
|
recv_done,
|
||||||
send_done,
|
send_done,
|
||||||
|
|
||||||
|
recv_message,
|
||||||
|
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,6 +120,7 @@ struct NGCFT1EventI {
|
|||||||
virtual bool onEvent(const Events::NGCFT1_send_data&) { return false; } // const?
|
virtual bool onEvent(const Events::NGCFT1_send_data&) { return false; } // const?
|
||||||
virtual bool onEvent(const Events::NGCFT1_recv_done&) { return false; }
|
virtual bool onEvent(const Events::NGCFT1_recv_done&) { return false; }
|
||||||
virtual bool onEvent(const Events::NGCFT1_send_done&) { return false; }
|
virtual bool onEvent(const Events::NGCFT1_send_done&) { return false; }
|
||||||
|
virtual bool onEvent(const Events::NGCFT1_recv_message&) { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
using NGCFT1EventProviderI = EventProviderI<NGCFT1EventI>;
|
using NGCFT1EventProviderI = EventProviderI<NGCFT1EventI>;
|
||||||
@ -181,6 +196,7 @@ class NGCFT1 : public ToxEventI, public NGCEXTEventI, public NGCFT1EventProvider
|
|||||||
bool sendPKG_FT1_INIT_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id);
|
bool sendPKG_FT1_INIT_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id);
|
||||||
bool sendPKG_FT1_DATA(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, uint16_t sequence_id, const uint8_t* data, size_t data_size);
|
bool sendPKG_FT1_DATA(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, uint16_t sequence_id, const uint8_t* data, size_t data_size);
|
||||||
bool sendPKG_FT1_DATA_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const uint16_t* seq_ids, size_t seq_ids_size);
|
bool sendPKG_FT1_DATA_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const uint16_t* seq_ids, size_t seq_ids_size);
|
||||||
|
bool sendPKG_FT1_MESSAGE(uint32_t group_number, uint32_t message_id, uint32_t file_kind, const uint8_t* file_id, size_t file_id_size);
|
||||||
|
|
||||||
void updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<LEDBAT::SeqIDType>& timeouts_set);
|
void updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<LEDBAT::SeqIDType>& timeouts_set);
|
||||||
void iteratePeer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer);
|
void iteratePeer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer);
|
||||||
@ -211,12 +227,21 @@ class NGCFT1 : public ToxEventI, public NGCEXTEventI, public NGCFT1EventProvider
|
|||||||
uint8_t* transfer_id
|
uint8_t* transfer_id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// sends the message and fills in message_id
|
||||||
|
bool NGC_FT1_send_message_public(
|
||||||
|
uint32_t group_number,
|
||||||
|
uint32_t& message_id,
|
||||||
|
uint32_t file_kind,
|
||||||
|
const uint8_t* file_id, size_t file_id_size
|
||||||
|
);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool onEvent(const Events::NGCEXT_ft1_request&) override;
|
bool onEvent(const Events::NGCEXT_ft1_request&) override;
|
||||||
bool onEvent(const Events::NGCEXT_ft1_init&) override;
|
bool onEvent(const Events::NGCEXT_ft1_init&) override;
|
||||||
bool onEvent(const Events::NGCEXT_ft1_init_ack&) override;
|
bool onEvent(const Events::NGCEXT_ft1_init_ack&) override;
|
||||||
bool onEvent(const Events::NGCEXT_ft1_data&) override;
|
bool onEvent(const Events::NGCEXT_ft1_data&) override;
|
||||||
bool onEvent(const Events::NGCEXT_ft1_data_ack&) override;
|
bool onEvent(const Events::NGCEXT_ft1_data_ack&) override;
|
||||||
|
bool onEvent(const Events::NGCEXT_ft1_message&) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//bool onToxEvent(const Tox_Event_Group_Custom_Packet* e) override;
|
//bool onToxEvent(const Tox_Event_Group_Custom_Packet* e) override;
|
||||||
|
@ -5,12 +5,15 @@
|
|||||||
#include <solanaceae/contact/components.hpp>
|
#include <solanaceae/contact/components.hpp>
|
||||||
#include <solanaceae/tox_contacts/components.hpp>
|
#include <solanaceae/tox_contacts/components.hpp>
|
||||||
#include <solanaceae/message3/components.hpp>
|
#include <solanaceae/message3/components.hpp>
|
||||||
|
#include <solanaceae/tox_messages/components.hpp>
|
||||||
|
|
||||||
#include <solanaceae/message3/file_r_file.hpp>
|
#include <solanaceae/message3/file_r_file.hpp>
|
||||||
|
|
||||||
#include "./ft1_sha1_info.hpp"
|
#include "./ft1_sha1_info.hpp"
|
||||||
#include "./hash_utils.hpp"
|
#include "./hash_utils.hpp"
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ SHA1_NGCFT1::SHA1_NGCFT1(
|
|||||||
|
|
||||||
void SHA1_NGCFT1::iterate(float delta) {
|
void SHA1_NGCFT1::iterate(float delta) {
|
||||||
{ // timers
|
{ // timers
|
||||||
// chunk sending
|
// sending transfers
|
||||||
for (auto peer_it = _sending_transfers.begin(); peer_it != _sending_transfers.end();) {
|
for (auto peer_it = _sending_transfers.begin(); peer_it != _sending_transfers.end();) {
|
||||||
for (auto it = peer_it->second.begin(); it != peer_it->second.end();) {
|
for (auto it = peer_it->second.begin(); it != peer_it->second.end();) {
|
||||||
it->second.time_since_activity += delta;
|
it->second.time_since_activity += delta;
|
||||||
@ -492,6 +495,30 @@ bool SHA1_NGCFT1::sendFilePath(const Contact3 c, std::string_view file_name, std
|
|||||||
} // else queue?
|
} // else queue?
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (_cr.any_of<Contact::Components::ToxGroupEphemeral>(c)) {
|
||||||
|
const uint32_t group_number = _cr.get<Contact::Components::ToxGroupEphemeral>(c).group_number;
|
||||||
|
uint32_t message_id = 0;
|
||||||
|
|
||||||
|
// TODO: check return
|
||||||
|
_nft.NGC_FT1_send_message_public(group_number, message_id, static_cast<uint32_t>(NGCFT1_file_kind::HASH_SHA1_INFO), sha1_info_hash.data(), sha1_info_hash.size());
|
||||||
|
reg_ptr->emplace<Message::Components::ToxGroupMessageID>(e, message_id);
|
||||||
|
|
||||||
|
// TODO: generalize?
|
||||||
|
auto& synced_by = reg_ptr->emplace<Message::Components::SyncedBy>(e).list;
|
||||||
|
synced_by.emplace(c_self);
|
||||||
|
} else if (
|
||||||
|
// non online group
|
||||||
|
_cr.any_of<Contact::Components::ToxGroupPersistent>(c)
|
||||||
|
) {
|
||||||
|
// create msg_id
|
||||||
|
const uint32_t message_id = randombytes_random();
|
||||||
|
reg_ptr->emplace<Message::Components::ToxGroupMessageID>(e, message_id);
|
||||||
|
|
||||||
|
// TODO: generalize?
|
||||||
|
auto& synced_by = reg_ptr->emplace<Message::Components::SyncedBy>(e).list;
|
||||||
|
synced_by.emplace(c_self);
|
||||||
|
}
|
||||||
|
|
||||||
_rmm.throwEventConstruct(*reg_ptr, e);
|
_rmm.throwEventConstruct(*reg_ptr, e);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
Loading…
Reference in New Issue
Block a user