mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-10-01 02:35:48 -04:00
Acknoweldge mail reception via presigned receipt
This method does protect recipient metadata but doesn't support multicasting so if a mail has N recipients N copies of the mail need to be sent to the group RsGenExchange made some members protected instead of private so child classes can use them Create Rs{G,N}xsMailPresignedReceipt items to prepare and dispatch receipts Move RsNxsMsg deserialization to RsNxsMsg::deserialize(...) method
This commit is contained in:
parent
9cde0fd996
commit
55ff9067cf
@ -718,18 +718,19 @@ private:
|
|||||||
*/
|
*/
|
||||||
uint8_t createGroup(RsNxsGrp* grp, RsTlvSecurityKeySet& keySet);
|
uint8_t createGroup(RsNxsGrp* grp, RsTlvSecurityKeySet& keySet);
|
||||||
|
|
||||||
|
protected:
|
||||||
/*!
|
/*!
|
||||||
* This completes the creation of an instance on RsNxsMsg
|
* This completes the creation of an instance on RsNxsMsg
|
||||||
* by assigning it a groupId and signature via SHA1 and EVP_sign respectively
|
* by assigning it a groupId and signature via SHA1 and EVP_sign respectively
|
||||||
* What signatures are calculated are based on the authentication policy
|
* What signatures are calculated are based on the authentication policy
|
||||||
* of the service
|
* of the service
|
||||||
* @param msg the Nxs message to create
|
* @param msg the Nxs message to create
|
||||||
* CREATE_FAIL, CREATE_SUCCESS, CREATE_ID_SIGN_NOT_AVAIL
|
|
||||||
* @return CREATE_SUCCESS for success, CREATE_FAIL for fail,
|
* @return CREATE_SUCCESS for success, CREATE_FAIL for fail,
|
||||||
* CREATE_FAIL_TRY_LATER for Id sign key not avail (but requested)
|
* CREATE_FAIL_TRY_LATER for Id sign key not avail (but requested)
|
||||||
*/
|
*/
|
||||||
int createMessage(RsNxsMsg* msg);
|
int createMessage(RsNxsMsg* msg);
|
||||||
|
|
||||||
|
private:
|
||||||
/*!
|
/*!
|
||||||
* convenience function to create sign
|
* convenience function to create sign
|
||||||
* @param signSet signatures are stored here
|
* @param signSet signatures are stored here
|
||||||
@ -876,8 +877,7 @@ private:
|
|||||||
time_t mLastCheck;
|
time_t mLastCheck;
|
||||||
RsGxsIntegrityCheck* mIntegrityCheck;
|
RsGxsIntegrityCheck* mIntegrityCheck;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
|
|
||||||
// TODO: cleanup this should be an enum!
|
// TODO: cleanup this should be an enum!
|
||||||
const uint8_t CREATE_FAIL, CREATE_SUCCESS, CREATE_FAIL_TRY_LATER, SIGN_MAX_WAITING_TIME;
|
const uint8_t CREATE_FAIL, CREATE_SUCCESS, CREATE_FAIL_TRY_LATER, SIGN_MAX_WAITING_TIME;
|
||||||
const uint8_t SIGN_FAIL, SIGN_SUCCESS, SIGN_FAIL_TRY_LATER;
|
const uint8_t SIGN_FAIL, SIGN_SUCCESS, SIGN_FAIL_TRY_LATER;
|
||||||
|
@ -28,6 +28,7 @@ bool RsGxsMailBaseItem::serialize(uint8_t* data, uint32_t size,
|
|||||||
ok = ok && (offset += 8); // Take header in account
|
ok = ok && (offset += 8); // Take header in account
|
||||||
ok = ok && setRawUInt8(data, size, &offset, cryptoType);
|
ok = ok && setRawUInt8(data, size, &offset, cryptoType);
|
||||||
ok = ok && recipientsHint.serialise(data, size, offset);
|
ok = ok && recipientsHint.serialise(data, size, offset);
|
||||||
|
ok = ok && setRawUInt64(data, size, &offset, receiptId);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ bool RsGxsMailBaseItem::deserialize(const uint8_t* data, uint32_t& size,
|
|||||||
ok = ok && getRawUInt8(dataPtr, rssize, &roffset, &crType);
|
ok = ok && getRawUInt8(dataPtr, rssize, &roffset, &crType);
|
||||||
cryptoType = static_cast<EncryptionMode>(crType);
|
cryptoType = static_cast<EncryptionMode>(crType);
|
||||||
ok = ok && recipientsHint.deserialise(dataPtr, rssize, roffset);
|
ok = ok && recipientsHint.deserialise(dataPtr, rssize, roffset);
|
||||||
|
ok = ok && getRawUInt64(dataPtr, rssize, &roffset, &receiptId);
|
||||||
if(ok) { size = rssize; offset = roffset; }
|
if(ok) { size = rssize; offset = roffset; }
|
||||||
else size = 0;
|
else size = 0;
|
||||||
return ok;
|
return ok;
|
||||||
@ -68,19 +70,19 @@ bool RsGxsMailSerializer::serialise(RsItem* item, void* data, uint32_t* size)
|
|||||||
{
|
{
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
RsGxsMailItem* i = dynamic_cast<RsGxsMailItem*>(item);
|
RsGxsMailItem* i = dynamic_cast<RsGxsMailItem*>(item);
|
||||||
ok = ok && i->serialize(dataPtr, itemSize, offset);
|
ok = i->serialize(dataPtr, itemSize, offset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GXS_MAIL_SUBTYPE_ACK:
|
case GXS_MAIL_SUBTYPE_RECEIPT:
|
||||||
{
|
{
|
||||||
RsGxsMailAckItem* i = dynamic_cast<RsGxsMailAckItem*>(item);
|
RsGxsMailPresignedReceipt* i =
|
||||||
ok = ok && setRsItemHeader(data, itemSize, item->PacketId(), itemSize);
|
dynamic_cast<RsGxsMailPresignedReceipt*>(item);
|
||||||
uint32_t offset = 8;
|
uint32_t offset = 0;
|
||||||
ok = ok && i->recipient.serialise(data, itemSize, offset);
|
ok = i->serialize(dataPtr, itemSize, offset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GXS_MAIL_SUBTYPE_GROUP:
|
case GXS_MAIL_SUBTYPE_GROUP:
|
||||||
ok = ok && setRsItemHeader(data, itemSize, item->PacketId(), itemSize);
|
ok = setRsItemHeader(data, itemSize, item->PacketId(), itemSize);
|
||||||
break;
|
break;
|
||||||
default: ok = false; break;
|
default: ok = false; break;
|
||||||
}
|
}
|
||||||
@ -94,3 +96,4 @@ bool RsGxsMailSerializer::serialise(RsItem* item, void* data, uint32_t* size)
|
|||||||
std::cout << "RsGxsMailSerializer::serialise(...) failed!" << std::endl;
|
std::cout << "RsGxsMailSerializer::serialise(...) failed!" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,16 +30,60 @@
|
|||||||
enum GxsMailItemsSubtypes
|
enum GxsMailItemsSubtypes
|
||||||
{
|
{
|
||||||
GXS_MAIL_SUBTYPE_MAIL = 1,
|
GXS_MAIL_SUBTYPE_MAIL = 1,
|
||||||
GXS_MAIL_SUBTYPE_ACK = 2,
|
GXS_MAIL_SUBTYPE_RECEIPT = 2,
|
||||||
GXS_MAIL_SUBTYPE_GROUP = 3
|
GXS_MAIL_SUBTYPE_GROUP = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RsNxsMailPresignedReceipt : RsNxsMsg
|
||||||
|
{
|
||||||
|
RsNxsMailPresignedReceipt() : RsNxsMsg(RS_SERVICE_TYPE_GXS_MAIL) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RsGxsMailPresignedReceipt : RsGxsMsgItem
|
||||||
|
{
|
||||||
|
RsGxsMailPresignedReceipt() :
|
||||||
|
RsGxsMsgItem( RS_SERVICE_TYPE_GXS_MAIL,
|
||||||
|
static_cast<uint8_t>(GXS_MAIL_SUBTYPE_RECEIPT) ),
|
||||||
|
receiptId(0) {}
|
||||||
|
|
||||||
|
uint64_t receiptId;
|
||||||
|
|
||||||
|
static uint32_t inline size()
|
||||||
|
{
|
||||||
|
return 8 + // Header
|
||||||
|
8; // receiptId
|
||||||
|
}
|
||||||
|
bool serialize(uint8_t* data, uint32_t size, uint32_t& offset) const
|
||||||
|
{
|
||||||
|
bool ok = setRsItemHeader(data, size, PacketId(), size);
|
||||||
|
ok = ok && (offset += 8); // Take header in account
|
||||||
|
ok = ok && setRawUInt64(data, size, &offset, receiptId);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
bool deserialize(const uint8_t* data, uint32_t& size, uint32_t& offset)
|
||||||
|
{
|
||||||
|
void* dataPtr = reinterpret_cast<void*>(const_cast<uint8_t*>(data));
|
||||||
|
uint32_t rssize = getRsItemSize(dataPtr);
|
||||||
|
uint32_t roffset = offset + 8; // Take header in account
|
||||||
|
bool ok = rssize <= size;
|
||||||
|
ok = ok && getRawUInt64(dataPtr, rssize, &roffset, &receiptId);
|
||||||
|
if(ok) { size = rssize; offset = roffset; }
|
||||||
|
else size = 0;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() { receiptId = 0; }
|
||||||
|
std::ostream &print(std::ostream &out, uint16_t /*indent = 0*/)
|
||||||
|
{ return out << receiptId; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct RsGxsMailBaseItem : RsGxsMsgItem
|
struct RsGxsMailBaseItem : RsGxsMsgItem
|
||||||
{
|
{
|
||||||
RsGxsMailBaseItem(GxsMailItemsSubtypes subtype) :
|
RsGxsMailBaseItem(GxsMailItemsSubtypes subtype) :
|
||||||
RsGxsMsgItem( RS_SERVICE_TYPE_GXS_MAIL,
|
RsGxsMsgItem( RS_SERVICE_TYPE_GXS_MAIL,
|
||||||
static_cast<uint8_t>(subtype) ),
|
static_cast<uint8_t>(subtype) ),
|
||||||
cryptoType(UNDEFINED_ENCRYPTION) {}
|
cryptoType(UNDEFINED_ENCRYPTION), receiptId(0) {}
|
||||||
|
|
||||||
/// Values must fit into uint8_t
|
/// Values must fit into uint8_t
|
||||||
enum EncryptionMode
|
enum EncryptionMode
|
||||||
@ -107,10 +151,13 @@ struct RsGxsMailBaseItem : RsGxsMsgItem
|
|||||||
|
|
||||||
const static RsGxsId allRecipientsHint;
|
const static RsGxsId allRecipientsHint;
|
||||||
|
|
||||||
|
uint64_t receiptId;
|
||||||
|
|
||||||
void inline clear()
|
void inline clear()
|
||||||
{
|
{
|
||||||
cryptoType = UNDEFINED_ENCRYPTION;
|
cryptoType = UNDEFINED_ENCRYPTION;
|
||||||
recipientsHint.clear();
|
recipientsHint.clear();
|
||||||
|
receiptId = 0;
|
||||||
meta = RsMsgMetaData();
|
meta = RsMsgMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +165,8 @@ struct RsGxsMailBaseItem : RsGxsMsgItem
|
|||||||
{
|
{
|
||||||
return 8 + // Header
|
return 8 + // Header
|
||||||
1 + // cryptoType
|
1 + // cryptoType
|
||||||
RsGxsId::serial_size(); // recipientsHint
|
RsGxsId::serial_size() + // recipientsHint
|
||||||
|
8; // receiptId
|
||||||
}
|
}
|
||||||
bool serialize(uint8_t* data, uint32_t size, uint32_t& offset) const;
|
bool serialize(uint8_t* data, uint32_t size, uint32_t& offset) const;
|
||||||
bool deserialize(const uint8_t* data, uint32_t& size, uint32_t& offset);
|
bool deserialize(const uint8_t* data, uint32_t& size, uint32_t& offset);
|
||||||
@ -160,16 +208,6 @@ struct RsGxsMailItem : RsGxsMailBaseItem
|
|||||||
const static uint32_t MAX_SIZE = 10*8*1024*1024;
|
const static uint32_t MAX_SIZE = 10*8*1024*1024;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RsGxsMailAckItem : RsGxsMailBaseItem
|
|
||||||
{
|
|
||||||
RsGxsMailAckItem() : RsGxsMailBaseItem(GXS_MAIL_SUBTYPE_ACK) {}
|
|
||||||
RsGxsId recipient;
|
|
||||||
|
|
||||||
void clear() { recipient.clear(); }
|
|
||||||
std::ostream &print(std::ostream &out, uint16_t /*indent = 0*/)
|
|
||||||
{ return out << recipient.toStdString(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RsGxsMailGroupItem : RsGxsGrpItem
|
struct RsGxsMailGroupItem : RsGxsGrpItem
|
||||||
{
|
{
|
||||||
RsGxsMailGroupItem() :
|
RsGxsMailGroupItem() :
|
||||||
@ -202,18 +240,8 @@ struct RsGxsMailSerializer : RsSerialType
|
|||||||
if(i) sz = i->size();
|
if(i) sz = i->size();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GXS_MAIL_SUBTYPE_ACK:
|
case GXS_MAIL_SUBTYPE_RECEIPT:
|
||||||
{
|
sz = RsGxsMailPresignedReceipt::size(); break;
|
||||||
RsGxsMailAckItem* i = dynamic_cast<RsGxsMailAckItem*>(item);
|
|
||||||
if(i)
|
|
||||||
{
|
|
||||||
sz = 8; // Header
|
|
||||||
sz += 4; // RsGxsMailBaseItem::recipient_hint
|
|
||||||
sz += 1; // RsGxsMailAckItem::read
|
|
||||||
sz += i->recipient.serial_size();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GXS_MAIL_SUBTYPE_GROUP: sz = 8; break;
|
case GXS_MAIL_SUBTYPE_GROUP: sz = 8; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@ -229,6 +257,7 @@ struct RsGxsMailSerializer : RsSerialType
|
|||||||
uint32_t rssize = getRsItemSize(data);
|
uint32_t rssize = getRsItemSize(data);
|
||||||
uint8_t pktv = getRsItemVersion(rstype);
|
uint8_t pktv = getRsItemVersion(rstype);
|
||||||
uint16_t srvc = getRsItemService(rstype);
|
uint16_t srvc = getRsItemService(rstype);
|
||||||
|
const uint8_t* dataPtr = reinterpret_cast<uint8_t*>(data);
|
||||||
|
|
||||||
if ( (RS_PKT_VERSION_SERVICE != pktv) || // 0x02
|
if ( (RS_PKT_VERSION_SERVICE != pktv) || // 0x02
|
||||||
(RS_SERVICE_TYPE_GXS_MAIL != srvc) || // 0x0230 = 560
|
(RS_SERVICE_TYPE_GXS_MAIL != srvc) || // 0x0230 = 560
|
||||||
@ -248,16 +277,15 @@ struct RsGxsMailSerializer : RsSerialType
|
|||||||
{
|
{
|
||||||
RsGxsMailItem* i = new RsGxsMailItem();
|
RsGxsMailItem* i = new RsGxsMailItem();
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
const uint8_t* dataPtr = reinterpret_cast<uint8_t*>(data);
|
|
||||||
ok = ok && i->deserialize(dataPtr, *size, offset);
|
ok = ok && i->deserialize(dataPtr, *size, offset);
|
||||||
ret = i;
|
ret = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GXS_MAIL_SUBTYPE_ACK:
|
case GXS_MAIL_SUBTYPE_RECEIPT:
|
||||||
{
|
{
|
||||||
RsGxsMailAckItem* i = new RsGxsMailAckItem();
|
RsGxsMailPresignedReceipt* i = new RsGxsMailPresignedReceipt();
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
ok &= i->recipient.deserialise(data, *size, offset);
|
ok &= i->deserialize(dataPtr, *size, offset);
|
||||||
ret = i;
|
ret = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,30 @@ bool RsNxsMsg::serialise(void *data, uint32_t& size) const
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RsNxsMsg::deserialize( const uint8_t* data, uint32_t& size,
|
||||||
|
uint32_t& offset )
|
||||||
|
{
|
||||||
|
void* dataPtr = reinterpret_cast<void*>(const_cast<uint8_t*>(data+offset));
|
||||||
|
|
||||||
|
uint32_t rssize = getRsItemSize(dataPtr);
|
||||||
|
uint32_t roffset = 8; // Take header in account
|
||||||
|
|
||||||
|
bool ok = rssize+offset <= size;
|
||||||
|
ok = ok && getRawUInt32(dataPtr, rssize, &roffset, &transactionNumber);
|
||||||
|
ok = ok && getRawUInt8(dataPtr, rssize, &roffset, &pos);
|
||||||
|
ok = ok && msgId.deserialise(dataPtr, rssize, roffset);
|
||||||
|
ok = ok && grpId.deserialise(dataPtr, rssize, roffset);
|
||||||
|
ok = ok && msg.GetTlv(dataPtr, rssize, &roffset);
|
||||||
|
ok = ok && meta.GetTlv(dataPtr, rssize, &roffset);
|
||||||
|
|
||||||
|
if(ok) { size = rssize; offset += roffset; }
|
||||||
|
else std::cerr << "RsNxsMsg::deserialize(, " << size << ", " << offset
|
||||||
|
<< ") failed at: " << roffset << " rssize: " << rssize
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool RsNxsGrp::serialise(void *data, uint32_t& size) const
|
bool RsNxsGrp::serialise(void *data, uint32_t& size) const
|
||||||
{
|
{
|
||||||
@ -519,20 +543,13 @@ RsNxsGrp* RsNxsSerialiser::deserialNxsGrpItem(void *data, uint32_t *size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RsNxsMsg* RsNxsSerialiser::deserialNxsMsgItem(void *data, uint32_t *size){
|
RsNxsMsg* RsNxsSerialiser::deserialNxsMsgItem(void *data, uint32_t *size)
|
||||||
|
{
|
||||||
|
if (!checkItemHeader(data, size, RS_PKT_SUBTYPE_NXS_MSG_ITEM)) return NULL;
|
||||||
|
|
||||||
bool ok = checkItemHeader(data,size,RS_PKT_SUBTYPE_NXS_MSG_ITEM);
|
RsNxsMsg* item = new RsNxsMsg(SERVICE_TYPE);
|
||||||
uint32_t offset = 8;
|
uint32_t offset = 0;
|
||||||
|
bool ok = item->deserialize(static_cast<const uint8_t*>(data), *size, offset);
|
||||||
RsNxsMsg* item = new RsNxsMsg(SERVICE_TYPE);
|
|
||||||
/* skip the header */
|
|
||||||
|
|
||||||
ok &= getRawUInt32(data, *size, &offset, &(item->transactionNumber));
|
|
||||||
ok &= getRawUInt8(data, *size, &offset, &(item->pos));
|
|
||||||
ok &= item->msgId.deserialise(data, *size, offset);
|
|
||||||
ok &= item->grpId.deserialise(data, *size, offset);
|
|
||||||
ok &= item->msg.GetTlv(data, *size, &offset);
|
|
||||||
ok &= item->meta.GetTlv(data, *size, &offset);
|
|
||||||
|
|
||||||
if (offset != *size)
|
if (offset != *size)
|
||||||
{
|
{
|
||||||
|
@ -399,50 +399,39 @@ public:
|
|||||||
* Used to respond to a RsGrpMsgsReq
|
* Used to respond to a RsGrpMsgsReq
|
||||||
* with message items satisfying request
|
* with message items satisfying request
|
||||||
*/
|
*/
|
||||||
class RsNxsMsg : public RsNxsItem
|
struct RsNxsMsg : RsNxsItem
|
||||||
{
|
{
|
||||||
public:
|
RsNxsMsg(uint16_t servtype) :
|
||||||
|
RsNxsItem(servtype, RS_PKT_SUBTYPE_NXS_MSG_ITEM), meta(servtype),
|
||||||
|
msg(servtype), metaData(NULL) { clear(); }
|
||||||
|
virtual ~RsNxsMsg() { delete metaData; }
|
||||||
|
|
||||||
RsNxsMsg(uint16_t servtype) : RsNxsItem(servtype, RS_PKT_SUBTYPE_NXS_MSG_ITEM), meta(servtype), msg(servtype),
|
virtual uint32_t serial_size() const;
|
||||||
metaData(NULL) {
|
virtual bool serialise(void *data,uint32_t& size) const;
|
||||||
// std::cout << "\nrefcount++ : " << ++refcount << std::endl;
|
bool deserialize(const uint8_t* data, uint32_t& size, uint32_t& offset);
|
||||||
clear(); return;
|
|
||||||
}
|
|
||||||
virtual ~RsNxsMsg()
|
|
||||||
{
|
|
||||||
//std::cout << "\nrefcount-- : " << --refcount << std::endl;
|
|
||||||
if(metaData){
|
|
||||||
//std::cout << "\ndeleted\n";
|
|
||||||
delete metaData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool serialise(void *data,uint32_t& size) const;
|
virtual void clear();
|
||||||
virtual uint32_t serial_size() const;
|
virtual std::ostream &print(std::ostream &out, uint16_t indent);
|
||||||
|
|
||||||
virtual void clear();
|
|
||||||
virtual std::ostream &print(std::ostream &out, uint16_t indent);
|
|
||||||
|
|
||||||
uint8_t pos; /// used for splitting up msg
|
uint8_t pos; /// used for splitting up msg
|
||||||
uint8_t count; /// number of split up messages
|
uint8_t count; /// number of split up messages
|
||||||
RsGxsGroupId grpId; /// group id, forms part of version id
|
RsGxsGroupId grpId; /// group id, forms part of version id
|
||||||
RsGxsMessageId msgId; /// msg id
|
RsGxsMessageId msgId; /// msg id
|
||||||
static int refcount;
|
static int refcount;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* This should contains all the data
|
* This should contains all the data
|
||||||
* which is not specific to the Gxs service data
|
* which is not specific to the Gxs service data
|
||||||
*/
|
*/
|
||||||
RsTlvBinaryData meta;
|
RsTlvBinaryData meta;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* This contains Gxs specific data
|
* This contains Gxs specific data
|
||||||
* only client of API knows who to decode this
|
* only client of API knows how to decode this
|
||||||
*/
|
*/
|
||||||
RsTlvBinaryData msg;
|
RsTlvBinaryData msg;
|
||||||
|
|
||||||
RsGxsMsgMetaData* metaData;
|
|
||||||
|
|
||||||
|
RsGxsMsgMetaData* metaData;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -24,17 +24,6 @@ bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|||||||
const RsGxsId& own_gxsid, const RsGxsId& recipient,
|
const RsGxsId& own_gxsid, const RsGxsId& recipient,
|
||||||
const uint8_t* data, uint32_t size,
|
const uint8_t* data, uint32_t size,
|
||||||
RsGxsMailBaseItem::EncryptionMode cm)
|
RsGxsMailBaseItem::EncryptionMode cm)
|
||||||
{
|
|
||||||
std::vector<const RsGxsId*> recipients;
|
|
||||||
recipients.push_back(&recipient);
|
|
||||||
return sendMail(service, own_gxsid, recipients, data, size, cm);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|
||||||
const RsGxsId& own_gxsid,
|
|
||||||
const std::vector<const RsGxsId*>& recipients,
|
|
||||||
const uint8_t* data, uint32_t size,
|
|
||||||
RsGxsMailBaseItem::EncryptionMode cm )
|
|
||||||
{
|
{
|
||||||
std::cout << "p3GxsMails::sendEmail(...)" << std::endl;
|
std::cout << "p3GxsMails::sendEmail(...)" << std::endl;
|
||||||
|
|
||||||
@ -53,25 +42,9 @@ bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<RsGxsId> rcps;
|
if(recipient.isNull())
|
||||||
typedef std::vector<const RsGxsId*>::const_iterator itT;
|
|
||||||
for(itT it = recipients.begin(); it != recipients.end(); it++)
|
|
||||||
{
|
{
|
||||||
const RsGxsId* gId = *it;
|
std::cerr << "p3GxsMails::sendEmail(...) got invalid recipient"
|
||||||
|
|
||||||
if(!gId || gId->isNull())
|
|
||||||
{
|
|
||||||
std::cerr << "p3GxsMails::sendEmail(...) got invalid recipient"
|
|
||||||
<< std::endl;
|
|
||||||
print_stacktrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rcps.insert(*gId);
|
|
||||||
}
|
|
||||||
if(rcps.empty())
|
|
||||||
{
|
|
||||||
std::cerr << "p3GxsMails::sendEmail(...) got no recipients"
|
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
print_stacktrace();
|
print_stacktrace();
|
||||||
return false;
|
return false;
|
||||||
@ -83,25 +56,22 @@ bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|||||||
item->meta.mAuthorId = own_gxsid;
|
item->meta.mAuthorId = own_gxsid;
|
||||||
item->meta.mGroupId = preferredGroupId;
|
item->meta.mGroupId = preferredGroupId;
|
||||||
item->cryptoType = cm;
|
item->cryptoType = cm;
|
||||||
|
item->saltRecipientHint(recipient);
|
||||||
|
item->saltRecipientHint(RsGxsId::random());
|
||||||
|
item->receiptId = RSRandom::random_u64();
|
||||||
|
|
||||||
typedef std::set<RsGxsId>::const_iterator siT;
|
RsNxsMailPresignedReceipt nrcpt;
|
||||||
for(siT it = rcps.begin(); it != rcps.end(); ++it)
|
preparePresignedReceipt(*item, nrcpt);
|
||||||
item->saltRecipientHint(*it);
|
|
||||||
|
|
||||||
// If there is jut one recipient salt with a random id to avoid leaking it
|
|
||||||
if(rcps.size() == 1) item->saltRecipientHint(RsGxsId::random());
|
|
||||||
|
|
||||||
/* At this point we do a lot of memory copying, it doesn't look pretty but
|
|
||||||
* ATM haven't thinked of an elegant way to have the GxsMailSubServices
|
|
||||||
* travelling encrypted withuot copying memory around or responsabilize the
|
|
||||||
* client service to embed it in data array that is awful */
|
|
||||||
|
|
||||||
uint16_t serv = static_cast<uint16_t>(service);
|
uint16_t serv = static_cast<uint16_t>(service);
|
||||||
uint32_t clearTextPldSize = size+2;
|
uint32_t rcptsize = nrcpt.serial_size();
|
||||||
item->payload.resize(clearTextPldSize);
|
item->payload.resize(2 + rcptsize + size);
|
||||||
uint32_t _discard = 0;
|
uint32_t offset = 0;
|
||||||
setRawUInt16(&item->payload[0], 2, &_discard, serv);
|
setRawUInt16(&item->payload[0], 2, &offset, serv);
|
||||||
memcpy(&item->payload[2], data, size);
|
nrcpt.serialise(&item->payload[offset], rcptsize); offset += rcptsize;
|
||||||
|
memcpy(&item->payload[offset], data, size); //offset += size;
|
||||||
|
|
||||||
|
std::cout << "p3GxsMails::sendMail(...) receipt size: " << rcptsize << std::endl;
|
||||||
|
|
||||||
switch (cm)
|
switch (cm)
|
||||||
{
|
{
|
||||||
@ -117,9 +87,9 @@ bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|||||||
uint8_t* encryptedData = NULL;
|
uint8_t* encryptedData = NULL;
|
||||||
uint32_t encryptedSize = 0;
|
uint32_t encryptedSize = 0;
|
||||||
uint32_t encryptError = 0;
|
uint32_t encryptError = 0;
|
||||||
if( idService.encryptData( &item->payload[0], clearTextPldSize,
|
if( idService.encryptData( &item->payload[0], item->payload.size(),
|
||||||
encryptedData, encryptedSize,
|
encryptedData, encryptedSize,
|
||||||
rcps, encryptError, true ) )
|
recipient, encryptError, true ) )
|
||||||
{
|
{
|
||||||
item->payload.resize(encryptedSize);
|
item->payload.resize(encryptedSize);
|
||||||
memcpy(&item->payload[0], encryptedData, encryptedSize);
|
memcpy(&item->payload[0], encryptedData, encryptedSize);
|
||||||
@ -143,8 +113,11 @@ bool p3GxsMails::sendMail( GxsMailsClient::GxsMailSubServices service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t token;
|
uint32_t token;
|
||||||
std::cout << "p3GxsMails::sendEmail(...) sending mail to:"<< *recipients[0]
|
std::cout << "p3GxsMails::sendEmail(...) sending mail to: "<< recipient
|
||||||
<< " payload size: : " << item->payload.size() << std::endl;
|
<< " with cryptoType: " << item->cryptoType
|
||||||
|
<< " recipientHint: " << item->recipientsHint
|
||||||
|
<< " receiptId: " << item->receiptId
|
||||||
|
<< " payload size: " << item->payload.size() << std::endl;
|
||||||
publishMsg(token, item);
|
publishMsg(token, item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -230,8 +203,6 @@ void p3GxsMails::handleResponse(uint32_t token, uint32_t req_type)
|
|||||||
for( vT::const_iterator mIt = mv.begin(); mIt != mv.end(); ++mIt )
|
for( vT::const_iterator mIt = mv.begin(); mIt != mv.end(); ++mIt )
|
||||||
{
|
{
|
||||||
RsGxsMsgItem* gItem = *mIt;
|
RsGxsMsgItem* gItem = *mIt;
|
||||||
std::cout << "p3GxsMails::handleResponse(...) MAILS_UPDATE "
|
|
||||||
<< (uint32_t)gItem->PacketSubType() << std::endl;
|
|
||||||
switch(gItem->PacketSubType())
|
switch(gItem->PacketSubType())
|
||||||
{
|
{
|
||||||
case GXS_MAIL_SUBTYPE_MAIL:
|
case GXS_MAIL_SUBTYPE_MAIL:
|
||||||
@ -246,13 +217,48 @@ void p3GxsMails::handleResponse(uint32_t token, uint32_t req_type)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout << "p3GxsMails::handleResponse(...) MAILS_UPDATE "
|
||||||
|
<< "GXS_MAIL_SUBTYPE_MAIL handling: "
|
||||||
|
<< msg->meta.mMsgId
|
||||||
|
<< " with cryptoType: "<< msg->cryptoType
|
||||||
|
<< " recipientHint: " << msg->recipientsHint
|
||||||
|
<< " receiptId: "<< msg->receiptId
|
||||||
|
<< " payload.size(): " << msg->payload.size()
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
handleEcryptedMail(msg);
|
handleEcryptedMail(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case GXS_MAIL_SUBTYPE_RECEIPT:
|
||||||
|
{
|
||||||
|
RsGxsMailPresignedReceipt* msg =
|
||||||
|
dynamic_cast<RsGxsMailPresignedReceipt*>(gItem);
|
||||||
|
if(!msg)
|
||||||
|
{
|
||||||
|
std::cerr << "p3GxsMails::handleResponse(...) "
|
||||||
|
<< "GXS_MAIL_SUBTYPE_RECEIPT cast error, "
|
||||||
|
<< "something really wrong is happening"
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "p3GxsMails::handleResponse(...) MAILS_UPDATE "
|
||||||
|
<< "GXS_MAIL_SUBTYPE_RECEIPT handling: "
|
||||||
|
<< msg->meta.mMsgId
|
||||||
|
<< "with receiptId: "<< msg->receiptId
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
/* TODO: Notify client services if the original mail was
|
||||||
|
* sent from this node and mark for deletion, otherwise
|
||||||
|
* just mark original mail for deletion. */
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
std::cerr << "p3GxsMails::handleResponse(...) MAILS_UPDATE "
|
std::cerr << "p3GxsMails::handleResponse(...) MAILS_UPDATE "
|
||||||
<< "Unknown mail subtype : "
|
<< "Unknown mail subtype : "
|
||||||
<< gItem->PacketSubType() << std::endl;
|
<< static_cast<uint32_t>(gItem->PacketSubType())
|
||||||
|
<< std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delete gItem;
|
delete gItem;
|
||||||
@ -262,7 +268,7 @@ void p3GxsMails::handleResponse(uint32_t token, uint32_t req_type)
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
std::cerr << "p3GxsMails::handleResponse(...) Unknown req_type: "
|
std::cerr << "p3GxsMails::handleResponse(...) Unknown req_type: "
|
||||||
<< req_type << std::endl;
|
<< req_type << std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,13 +291,13 @@ void p3GxsMails::service_tick()
|
|||||||
reinterpret_cast<const uint8_t*>(ciao.data()),
|
reinterpret_cast<const uint8_t*>(ciao.data()),
|
||||||
ciao.size(), RsGxsMailBaseItem::RSA );
|
ciao.size(), RsGxsMailBaseItem::RSA );
|
||||||
}
|
}
|
||||||
else if(idService.isOwnId(gxsidB))
|
// else if(idService.isOwnId(gxsidB))
|
||||||
{
|
// {
|
||||||
std::string ciao("CiBuono!");
|
// std::string ciao("CiBuono!");
|
||||||
sendMail( GxsMailsClient::TEST_SERVICE, gxsidB, gxsidA,
|
// sendMail( GxsMailsClient::TEST_SERVICE, gxsidB, gxsidA,
|
||||||
reinterpret_cast<const uint8_t*>(ciao.data()),
|
// reinterpret_cast<const uint8_t*>(ciao.data()),
|
||||||
ciao.size(), RsGxsMailBaseItem::RSA );
|
// ciao.size(), RsGxsMailBaseItem::RSA );
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsTokenQueue::checkRequests();
|
GxsTokenQueue::checkRequests();
|
||||||
@ -386,6 +392,8 @@ bool p3GxsMails::requestGroupsData(const std::list<RsGxsGroupId>* groupIds)
|
|||||||
|
|
||||||
bool p3GxsMails::handleEcryptedMail(const RsGxsMailItem* mail)
|
bool p3GxsMails::handleEcryptedMail(const RsGxsMailItem* mail)
|
||||||
{
|
{
|
||||||
|
std::cout << "p3GxsMails::handleEcryptedMail(...)" << std::endl;
|
||||||
|
|
||||||
std::set<RsGxsId> decryptIds;
|
std::set<RsGxsId> decryptIds;
|
||||||
std::list<RsGxsId> ownIds;
|
std::list<RsGxsId> ownIds;
|
||||||
idService.getOwnIds(ownIds);
|
idService.getOwnIds(ownIds);
|
||||||
@ -393,7 +401,11 @@ bool p3GxsMails::handleEcryptedMail(const RsGxsMailItem* mail)
|
|||||||
if(mail->maybeRecipient(*it)) decryptIds.insert(*it);
|
if(mail->maybeRecipient(*it)) decryptIds.insert(*it);
|
||||||
|
|
||||||
// Hint match none of our own ids
|
// Hint match none of our own ids
|
||||||
if(decryptIds.empty()) return true;
|
if(decryptIds.empty())
|
||||||
|
{
|
||||||
|
std::cout << "p3GxsMails::handleEcryptedMail(...) hint doesn't match" << std::endl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mail->cryptoType)
|
switch (mail->cryptoType)
|
||||||
{
|
{
|
||||||
@ -409,17 +421,21 @@ bool p3GxsMails::handleEcryptedMail(const RsGxsMailItem* mail)
|
|||||||
}
|
}
|
||||||
case RsGxsMailBaseItem::RSA:
|
case RsGxsMailBaseItem::RSA:
|
||||||
{
|
{
|
||||||
uint8_t* decrypted_data = NULL;
|
|
||||||
uint32_t decrypted_data_size = 0;
|
|
||||||
uint32_t decryption_error;
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
if( idService.decryptData( &mail->payload[0],
|
for( std::set<RsGxsId>::const_iterator it = decryptIds.begin();
|
||||||
mail->payload.size(), decrypted_data,
|
it != decryptIds.end(); ++it )
|
||||||
decrypted_data_size, decryptIds,
|
{
|
||||||
decryption_error ) )
|
uint8_t* decrypted_data = NULL;
|
||||||
ok = dispatchDecryptedMail( mail, decrypted_data,
|
uint32_t decrypted_data_size = 0;
|
||||||
decrypted_data_size );
|
uint32_t decryption_error;
|
||||||
free(decrypted_data);
|
if( idService.decryptData( &mail->payload[0],
|
||||||
|
mail->payload.size(), decrypted_data,
|
||||||
|
decrypted_data_size, *it,
|
||||||
|
decryption_error ) )
|
||||||
|
ok = ok && dispatchDecryptedMail( mail, decrypted_data,
|
||||||
|
decrypted_data_size );
|
||||||
|
free(decrypted_data);
|
||||||
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -433,13 +449,37 @@ bool p3GxsMails::dispatchDecryptedMail( const RsGxsMailItem* received_msg,
|
|||||||
const uint8_t* decrypted_data,
|
const uint8_t* decrypted_data,
|
||||||
uint32_t decrypted_data_size )
|
uint32_t decrypted_data_size )
|
||||||
{
|
{
|
||||||
std::cout << "p3GxsMails::dispatchDecryptedMail(, , " << decrypted_data_size << ")" << std::endl;
|
std::cout << "p3GxsMails::dispatchDecryptedMail(, , " << decrypted_data_size
|
||||||
|
<< ")" << std::endl;
|
||||||
|
|
||||||
uint16_t csri = 0;
|
uint16_t csri = 0;
|
||||||
uint32_t off = 0;
|
uint32_t offset = 0;
|
||||||
getRawUInt16( decrypted_data, decrypted_data_size, &off, &csri);
|
if(!getRawUInt16( decrypted_data, decrypted_data_size, &offset, &csri))
|
||||||
|
{
|
||||||
|
std::cerr << "p3GxsMails::dispatchDecryptedMail(...) (EE) fatal error "
|
||||||
|
<< "deserializing service type, something really wrong is "
|
||||||
|
<< "happening!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
GxsMailsClient::GxsMailSubServices rsrvc;
|
GxsMailsClient::GxsMailSubServices rsrvc;
|
||||||
rsrvc = static_cast<GxsMailsClient::GxsMailSubServices>(csri);
|
rsrvc = static_cast<GxsMailsClient::GxsMailSubServices>(csri);
|
||||||
|
|
||||||
|
RsNxsMailPresignedReceipt* receipt = new RsNxsMailPresignedReceipt();
|
||||||
|
uint32_t rcptsize = decrypted_data_size;
|
||||||
|
if(!receipt->deserialize(decrypted_data, rcptsize, offset))
|
||||||
|
{
|
||||||
|
std::cerr << "p3GxsMails::dispatchDecryptedMail(...) (EE) fatal error "
|
||||||
|
<< "deserializing presigned return receipt , something really"
|
||||||
|
<< " wrong is happening!" << std::endl;
|
||||||
|
delete receipt;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "p3GxsMails::dispatchDecryptedMail(...) dispatching receipt "
|
||||||
|
<< "with: msgId: " << receipt->msgId << std::endl;
|
||||||
|
|
||||||
|
std::vector<RsNxsMsg*> rcct; rcct.push_back(receipt);
|
||||||
|
RsGenExchange::notifyNewMessages(rcct);
|
||||||
|
|
||||||
GxsMailsClient* reecipientService = NULL;
|
GxsMailsClient* reecipientService = NULL;
|
||||||
{
|
{
|
||||||
RS_STACK_MUTEX(servClientsMutex);
|
RS_STACK_MUTEX(servClientsMutex);
|
||||||
@ -448,8 +488,8 @@ bool p3GxsMails::dispatchDecryptedMail( const RsGxsMailItem* received_msg,
|
|||||||
|
|
||||||
if(reecipientService)
|
if(reecipientService)
|
||||||
return reecipientService->receiveGxsMail( received_msg,
|
return reecipientService->receiveGxsMail( received_msg,
|
||||||
&decrypted_data[2],
|
&decrypted_data[offset],
|
||||||
decrypted_data_size-2 );
|
decrypted_data_size-offset );
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cerr << "p3GxsMails::dispatchReceivedMail(...) "
|
std::cerr << "p3GxsMails::dispatchReceivedMail(...) "
|
||||||
@ -459,3 +499,39 @@ bool p3GxsMails::dispatchDecryptedMail( const RsGxsMailItem* received_msg,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool p3GxsMails::preparePresignedReceipt(const RsGxsMailItem& mail, RsNxsMailPresignedReceipt& receipt)
|
||||||
|
{
|
||||||
|
RsGxsMailPresignedReceipt grcpt;
|
||||||
|
grcpt.meta = mail.meta;
|
||||||
|
grcpt.meta.mPublishTs = time(NULL);
|
||||||
|
grcpt.receiptId = mail.receiptId;
|
||||||
|
uint32_t groff = 0, grsz = grcpt.size();
|
||||||
|
std::vector<uint8_t> grsrz;
|
||||||
|
grsrz.resize(grsz);
|
||||||
|
grcpt.serialize(&grsrz[0], grsz, groff);
|
||||||
|
receipt.msg.setBinData(&grsrz[0], grsz);
|
||||||
|
|
||||||
|
receipt.grpId = preferredGroupId;
|
||||||
|
receipt.metaData = new RsGxsMsgMetaData();
|
||||||
|
*receipt.metaData = grcpt.meta;
|
||||||
|
|
||||||
|
if(createMessage(&receipt) != CREATE_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cout << "p3GxsMails::preparePresignedReceipt(...) receipt creation"
|
||||||
|
<< " failed!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t metaSize = receipt.metaData->serial_size();
|
||||||
|
std::vector<uint8_t> srx; srx.resize(metaSize);
|
||||||
|
receipt.metaData->serialise(&srx[0], &metaSize);
|
||||||
|
receipt.meta.setBinData(&srx[0], metaSize);
|
||||||
|
|
||||||
|
std::cout << "p3GxsMails::preparePresignedReceipt(...) prepared receipt"
|
||||||
|
<< "with: grcpt.meta.mMsgId: " << grcpt.meta.mMsgId
|
||||||
|
<< " msgId: " << receipt.msgId
|
||||||
|
<< " metaData.mMsgId: " << receipt.metaData->mMsgId
|
||||||
|
<< std::endl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -67,21 +67,6 @@ struct p3GxsMails : RsGenExchange, GxsTokenQueue
|
|||||||
RsGxsMailBaseItem::EncryptionMode cm = RsGxsMailBaseItem::RSA
|
RsGxsMailBaseItem::EncryptionMode cm = RsGxsMailBaseItem::RSA
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email to recipients, in the process author of the email is
|
|
||||||
* disclosed to the network (because the sent GXS item is signed), while
|
|
||||||
* recipients are not @see RsGxsMailBaseItem::recipientsHint for details on
|
|
||||||
* recipient protection.
|
|
||||||
* This method is part of the public interface of this service.
|
|
||||||
* @return true if the mail will be sent, false if not
|
|
||||||
*/
|
|
||||||
bool sendMail( GxsMailsClient::GxsMailSubServices service,
|
|
||||||
const RsGxsId& own_gxsid,
|
|
||||||
const std::vector<const RsGxsId*>& recipients,
|
|
||||||
const uint8_t* data, uint32_t size,
|
|
||||||
RsGxsMailBaseItem::EncryptionMode cm = RsGxsMailBaseItem::RSA
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a client service to p3GxsMails to receive mails via
|
* Register a client service to p3GxsMails to receive mails via
|
||||||
* GxsMailsClient::receiveGxsMail(...) callback
|
* GxsMailsClient::receiveGxsMail(...) callback
|
||||||
@ -185,6 +170,9 @@ private:
|
|||||||
bool dispatchDecryptedMail( const RsGxsMailItem* received_msg,
|
bool dispatchDecryptedMail( const RsGxsMailItem* received_msg,
|
||||||
const uint8_t* decrypted_data,
|
const uint8_t* decrypted_data,
|
||||||
uint32_t decrypted_data_size );
|
uint32_t decrypted_data_size );
|
||||||
|
|
||||||
|
bool preparePresignedReceipt( const RsGxsMailItem& mail,
|
||||||
|
RsNxsMailPresignedReceipt& receipt );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user