First experiments with GXS Mail

Periodically pool GXS backend for new groups and subscribe to all.

A group is created and an email can be sent to it but for some reason
the group and and the email are not received by friends.
This commit is contained in:
Gioacchino Mazzurco 2017-01-14 23:20:42 +01:00
parent c61ccda431
commit 53e3177c2b
5 changed files with 573 additions and 1 deletions

View File

@ -852,7 +852,11 @@ gxsphotoshare {
serialiser/rsphotoitems.cc \
}
rs_gxs_mail
{
HEADERS += serialiser/rsgxsmailitems.h services/p3gxsmails.h
SOURCES += services/p3gxsmails.cpp
}

View File

@ -0,0 +1,278 @@
#pragma once
/*
* GXS Mailing Service
* Copyright (C) 2016-2017 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include "rsgxsitems.h"
#include "serialiser/rsbaseserial.h"
#include "serialiser/rstlvidset.h"
#include "retroshare/rsgxsflags.h"
// TODO: move to rsserviceids.h
const uint16_t RS_SERVICE_TYPE_GXS_MAIL = 0x0230;
enum GxsMailSubtypes
{
GXS_MAIL_SUBTYPE_MAIL = 1,
GXS_MAIL_SUBTYPE_ACK,
GXS_MAIL_SUBTYPE_GROUP
};
struct RsGxsMailBaseItem : RsGxsMsgItem
{
RsGxsMailBaseItem(GxsMailSubtypes subtype) :
RsGxsMsgItem(RS_SERVICE_TYPE_GXS_MAIL, (uint8_t)subtype), flags(0) {}
enum RsGxsMailFlags { READ = 0x1 };
uint8_t flags;
/**
* @brief recipient_hint used instead of plain recipient id, so sender can
* decide the equilibrium between exposing the recipient and the cost of
* completely anonymize it. So a bunch of luky non recipient can conclude
* rapidly that they are not recipiend without trying to decrypt the
* message.
*
* To be able to decide how much metadata we disclose sending a message we
* send an hint instead of the recipient id in clear, the hint cannot be
* false (the recipient would discard the mail) but may be arbitrarly
* obscure like 0xFF...FF so potentially everyone could be the recipient, or
* may expose the complete recipient id or be a middle ground.
* To calculate arbitrary precise hint one do a bitwise OR of the recipients
* keys and an arbytrary salting mask, the more recipients has the mail and
* the more 1 bits has the mask the less accurate is the hint.
* This way the sender is able to adjust the metadata privacy needed for the
* message, in the more private case (recipient_hint == 0xFF...FF) no one
* has a clue about who is the actual recipient, while this imply the cost
* that every potencial recipient has to try to decrypt it to know if it is
* for herself. This way a bunch of non recipients can rapidly discover that
* the message is not directed to them without attempting it's decryption.
*
* To check if one id may be the recipient of the mail or not one need to
* bitwise compare the hint with the id, if at least one bit of the hint is
* 0 while the corrisponding bit in the id is 1 then the id cannot be the
* recipient of the mail.
*
* Note that by design one can prove that an id is not recipient of the mail
* but cannot prove it is.
* Also in the extreme case of using 0x00...00 as salt that is equivalent
* to not salting at all (aka the plain recipient id is used as hint) a
* malicious observer could not demostrate in a conclusive manner that the
* mail is directed to the actual recipient as the "apparently"
* corresponding hint may be fruit of a "luky" salting of another id.
*/
uint32_t recipient_hint;
/**
* @brief maybe_recipient given an id and an hint check if they match
* @see recipient_hint
* @note this is not the final implementation as id and hint are not 32bit
* integers it is just to not forget how to verify the hint/id matching
* fastly with boolean ops
* @return true if the id may be recipient of the hint, false otherwise
*/
static bool maybe_recipient(uint32_t id, uint32_t hint)
{ return (~id|hint) == 0xFFFFFFFF; }
};
struct RsGxsMailItem : RsGxsMailBaseItem
{
RsGxsMailItem() : RsGxsMailBaseItem(GXS_MAIL_SUBTYPE_MAIL) {}
RsTlvGxsIdSet recipients;
/**
* @brief body of the email
* Should we ue MIME for compatibility with fido RS-email gateway?
* https://github.com/zeroreserve/fido
* https://github.com/RetroShare/fido
* https://en.wikipedia.org/wiki/MIME
*/
std::string body;
void clear()
{
recipients.TlvClear();
body.clear();
}
std::ostream &print(std::ostream &out, uint16_t indent = 0)
{ return recipients.print(out, indent) << body; }
};
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
{
RsGxsMailGroupItem() :
RsGxsGrpItem(RS_SERVICE_TYPE_GXS_MAIL, GXS_MAIL_SUBTYPE_GROUP)
{
meta.mGroupFlags = GXS_SERV::FLAG_PRIVACY_PUBLIC;
meta.mGroupName = "Mail";
}
void clear() {}
std::ostream &print(std::ostream &out, uint16_t /*indent = 0*/)
{ return out; }
};
struct RsGxsMailSerializer : RsSerialType
{
RsGxsMailSerializer() : RsSerialType( RS_PKT_VERSION_SERVICE,
RS_SERVICE_TYPE_GXS_MAIL ) {}
~RsGxsMailSerializer() {}
uint32_t size(RsItem* item)
{
uint32_t s = 8; // Header
switch(item->PacketSubType())
{
case GXS_MAIL_SUBTYPE_MAIL:
{
RsGxsMailItem* i = dynamic_cast<RsGxsMailItem*>(item);
if(i)
{
s += 4; // RsGxsMailBaseItem::recipient_hint
s += i->recipients.TlvSize();
s += getRawStringSize(i->body);
}
break;
}
case GXS_MAIL_SUBTYPE_ACK:
{
RsGxsMailAckItem* i = dynamic_cast<RsGxsMailAckItem*>(item);
if(i)
{
s += 4; // RsGxsMailBaseItem::recipient_hint
s += 1; // RsGxsMailAckItem::read
s += i->recipient.serial_size();
}
break;
}
case GXS_MAIL_SUBTYPE_GROUP: break;
default: return 0;
}
std::cout << "RsGxsMailSerializer::size() " << item->PacketSubType() << " return " << s << std::endl;
return s;
}
bool serialise(RsItem* item, void* data, uint32_t* size)
{
uint32_t tlvsize = RsGxsMailSerializer::size(item);
uint32_t offset = 0;
if(*size < tlvsize) return false;
*size = tlvsize;
bool ok = true;
ok &= setRsItemHeader(data, tlvsize, item->PacketId(), tlvsize);
/* skip the header */
offset += 8;
switch(item->PacketSubType())
{
case GXS_MAIL_SUBTYPE_MAIL:
{
RsGxsMailItem* i = dynamic_cast<RsGxsMailItem*>(item);
if(i)
{
ok &= i->recipients.SetTlv(data, tlvsize, &offset);
ok &= setRawString(data, tlvsize, &offset, i->body);
}
break;
}
case GXS_MAIL_SUBTYPE_ACK:
{
RsGxsMailAckItem* i = dynamic_cast<RsGxsMailAckItem*>(item);
if(i)
{
ok &= i->recipient.serialise(data, tlvsize, offset);
ok &= setRawUInt8(data, tlvsize, &offset, i->flags);
}
break;
}
case GXS_MAIL_SUBTYPE_GROUP: break;
default: ok = false; break;
}
return ok;
}
RsItem* deserialise(void* data, uint32_t* size)
{
uint32_t rstype = getRsItemId(data);
uint32_t rssize = getRsItemSize(data);
uint32_t offset = 8;
if ( (RS_PKT_VERSION_SERVICE != getRsItemVersion(rstype)) ||
(RS_SERVICE_TYPE_GXS_MAIL != getRsItemService(rstype)) ||
(*size < rssize) )
return NULL;
*size = rssize;
bool ok = true;
RsItem* ret = NULL;
switch (getRsItemSubType(rstype))
{
case GXS_MAIL_SUBTYPE_MAIL:
{
RsGxsMailItem* i = new RsGxsMailItem();
ok &= i->recipients.GetTlv(data, *size, &offset);
ok &= getRawString(data, *size, &offset, i->body);
ret = i;
break;
}
case GXS_MAIL_SUBTYPE_ACK:
{
RsGxsMailAckItem* i = new RsGxsMailAckItem();
ok &= getRawUInt8(data, *size, &offset, &i->flags);
ok &= i->recipient.deserialise(data, *size, offset);
ret = i;
break;
}
case GXS_MAIL_SUBTYPE_GROUP:
{
ret = new RsGxsMailGroupItem();
break;
}
default:
ok = false;
break;
}
if(ok) return ret;
delete ret;
return NULL;
}
};

View File

@ -0,0 +1,180 @@
/*
* GXS Mailing Service
* Copyright (C) 2016-2017 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "p3gxsmails.h"
bool p3GxsMails::sendEmail(const RsGxsId& own_gxsid, const RsGxsId& recipient, const std::string& body)
{
std::cout << "p3GxsMails::sendEmail(...)" << std::endl;
if(preferredGroupId.isNull())
{
requestGroupsList();
return false;
}
RsGxsMailItem* m = new RsGxsMailItem;
m->meta.mAuthorId = own_gxsid;
m->meta.mGroupId = preferredGroupId;
m->recipients.ids.insert(recipient);
m->body = body;
uint32_t token;
publishMsg(token, m);
return true;
}
void p3GxsMails::handleResponse(uint32_t token, uint32_t req_type)
{
//std::cout << "p3GxsMails::handleResponse(" << token << ", " << req_type << ")" << std::endl;
switch (req_type)
{
case GROUPS_LIST:
{
std::vector<RsGxsGrpItem*> groups;
getGroupData(token, groups);
for( std::vector<RsGxsGrpItem *>::iterator it = groups.begin();
it != groups.end(); ++it )
{
RsGxsGrpItem* grp = *it;
if(!IS_GROUP_SUBSCRIBED(grp->meta.mSubscribeFlags))
{
std::cout << "p3GxsMails::handleResponse(...) subscribing to group: " << grp->meta.mGroupId << std::endl;
uint32_t token;
subscribeToGroup(token, grp->meta.mGroupId, true);
}
supersedePreferredGroup(grp->meta.mGroupId);
}
if(preferredGroupId.isNull())
{
std::cout << "p3GxsMails::handleResponse(...) preferredGroupId.isNull()" << std::endl;
// TODO: Should check if we have friends before of doing this?
uint32_t token;
publishGroup(token, new RsGxsMailGroupItem());
queueRequest(token, GROUP_CREATE);
}
break;
}
case GROUP_CREATE:
{
std::cout << "p3GxsMails::handleResponse(...) GROUP_CREATE" << std::endl;
RsGxsGroupId grpId;
acknowledgeTokenGrp(token, grpId);
supersedePreferredGroup(grpId);
break;
}
default:
std::cout << "p3GxsMails::handleResponse(...) Unknown req_type: " << req_type << std::endl;
break;
}
/*
GxsMsgDataMap msgItems;
if(!RsGenExchange::getMsgData(token, msgItems))
{
std::cerr << "p3GxsMails::handleResponse(...) Cannot get msg data. "
<< "Something's weird." << std::endl;
}
*/
}
void p3GxsMails::service_tick()
{
static int tc = 0;
++tc;
if((tc % 100) == 0)
{
// std::cout << "p3GxsMails::service_tick() " << tc << " "
// << preferredGroupId << std::endl;
requestGroupsList();
}
#if 0
if(tc == 500)
{
RsGxsId own_gxsid("d0df7474bdde0464679e6ef787890287");
RsGxsId recipient("d060bea09dfa14883b5e6e517eb580cd");
sendEmail(own_gxsid, recipient, "Ciao!");
}
#endif
GxsTokenQueue::checkRequests();
}
RsGenExchange::ServiceCreate_Return p3GxsMails::service_CreateGroup(RsGxsGrpItem* grpItem, RsTlvSecurityKeySet& /*keySet*/)
{
std::cout << "p3GxsMails::service_CreateGroup(...) " << grpItem->meta.mGroupId << std::endl;
return SERVICE_CREATE_SUCCESS;
}
void p3GxsMails::notifyChanges(std::vector<RsGxsNotify*>& changes)
{
std::cout << "p3GxsMails::notifyChanges(...)" << std::endl;
for( std::vector<RsGxsNotify*>::const_iterator it = changes.begin();
it != changes.end(); ++it )
{
RsGxsGroupChange* grpChange = dynamic_cast<RsGxsGroupChange *>(*it);
RsGxsMsgChange* msgChange = dynamic_cast<RsGxsMsgChange *>(*it);
if (grpChange)
{
typedef std::list<RsGxsGroupId>::const_iterator itT;
for( itT it = grpChange->mGrpIdList.begin();
it != grpChange->mGrpIdList.end(); ++it )
{
const RsGxsGroupId& grpId = *it;
std::cout << "p3GxsMails::notifyChanges(...) got group "
<< grpId << std::endl;
supersedePreferredGroup(grpId);
}
}
else if(msgChange)
{
typedef std::map<RsGxsGroupId, std::vector<RsGxsMessageId> > mT;
for( mT::const_iterator it = msgChange->msgChangeMap.begin();
it != msgChange->msgChangeMap.end(); ++it )
{
const RsGxsGroupId& grpId = it->first;
const std::vector<RsGxsMessageId>& msgsIds = it->second;
typedef std::vector<RsGxsMessageId>::const_iterator itT;
for(itT vit = msgsIds.begin(); vit != msgsIds.end(); ++vit)
{
const RsGxsMessageId& msgId = *vit;
std::cout << "p3GxsMails::notifyChanges(...) got "
<< "new message " << msgId << " in group "
<< grpId << std::endl;
}
}
}
}
}
bool p3GxsMails::requestGroupsList()
{
// std::cout << "p3GxsMails::requestGroupsList() GXS_REQUEST_TYPE_GROUP_META" << std::endl;
uint32_t token;
RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
RsGenExchange::getTokenService()->requestGroupInfo(token, 0xcaca, opts);
GxsTokenQueue::queueRequest(token, GROUPS_LIST);
return true;
}

View File

@ -0,0 +1,104 @@
#pragma once
/*
* GXS Mailing Service
* Copyright (C) 2016-2017 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include "retroshare/rsgxsifacetypes.h" // For RsGxsId, RsGxsCircleId
#include "gxs/gxstokenqueue.h" // For GxsTokenQueue
#include "serialiser/rsgxsmailitems.h" // For RS_SERVICE_TYPE_GXS_MAIL
#include "services/p3idservice.h" // For p3IdService
class p3GxsMails : public RsGenExchange, public GxsTokenQueue
{
public:
p3GxsMails( RsGeneralDataService* gds, RsNetworkExchangeService* nes,
p3IdService *identities ) :
RsGenExchange( gds, nes, new RsGxsMailSerializer(),
RS_SERVICE_TYPE_GXS_MAIL, identities,
AuthenPolicy(),
RS_GXS_DEFAULT_MSG_STORE_PERIOD ), // TODO: Discuss with Cyril about this
GxsTokenQueue(this) {}
/// Public interface
bool sendEmail( const RsGxsId& own_gxsid, const RsGxsId& recipient,
const std::string& body );
/**
* @see GxsTokenQueue::handleResponse(uint32_t token, uint32_t req_type)
*/
virtual void handleResponse(uint32_t token, uint32_t req_type);
/// @see RsGenExchange::service_tick()
virtual void service_tick();
/// @see RsGenExchange::getServiceInfo()
virtual RsServiceInfo getServiceInfo() { return RsServiceInfo( RS_SERVICE_TYPE_GXS_MAIL, "GXS Mails", 0, 1, 0, 1 ); }
/// @see RsGenExchange::service_CreateGroup(...)
RsGenExchange::ServiceCreate_Return service_CreateGroup(RsGxsGrpItem* grpItem, RsTlvSecurityKeySet&);
protected:
/// @see RsGenExchange::notifyChanges(std::vector<RsGxsNotify *> &changes)
void notifyChanges(std::vector<RsGxsNotify *> &changes);
private:
/// @brief AuthenPolicy check nothing ATM TODO talk with Cyril how this should be
static uint32_t AuthenPolicy() { return 0; }
/// Types to mark GXS queries and answhers
enum GxsReqResTypes { GROUPS_LIST, GROUP_CREATE };
RsGxsGroupId preferredGroupId;
/// Request groups list to GXS backend. Async method.
bool requestGroupsList();
/**
* Check if current preferredGroupId is the best against potentialGrId, if
* the passed one is better update it.
* Useful when GXS backend notifies groups changes, or when a reponse to an
* async grop request (@see GXS_REQUEST_TYPE_GROUP_*) is received.
* @return true if preferredGroupId has been supeseded by potentialGrId
* false otherwise.
*/
bool inline supersedePreferredGroup(const RsGxsGroupId& potentialGrId)
{
std::cout << "supersedePreferredGroup(const RsGxsGroupId& potentialGrId) " << preferredGroupId << " <? " << potentialGrId << std::endl;
if(preferredGroupId < potentialGrId)
{
preferredGroupId = potentialGrId;
return true;
}
return false;
}
};
/*
19:27:29 G10h4ck: About the scalability stuff i have thinked we can implement TTL mechanism like IP does
19:27:48 G10h4ck: better HTL Hop To Live
19:28:09 G10h4ck: one send an email with an associated HTL for example 2
19:28:32 G10h4ck: when a node receive that it decrement the HTL and store and forward it
19:29:03 G10h4ck: if a received mail has an HTL greater then the last we received we store that one
19:29:18 G10h4ck: this way we can control how much the mail should spread in the net
19:29:47 G10h4ck: HTL should be upper limited to a maximum
19:30:03 G10h4ck: so an abuser cant spread mails with 10000000 as HTL
19:30:30 G10h4ck: if a mail is received witha suspicios HTL it may be dropped or the HTL reduced to MAX_HTL
*/

View File

@ -60,6 +60,12 @@ rs_nodeprecatedwarning:CONFIG -= no_rs_nodeprecatedwarning
CONFIG *= no_rs_nocppwarning
rs_nocppwarning:CONFIG -= no_rs_nocppwarning
# To enable GXS mail append the following assignation to qmake command line
# "CONFIG+=rs_gxs_mail"
CONFIG *= no_rs_gxs_mail
rs_gxs_mail:CONFIG -= no_rs_gxs_mail
unix {
isEmpty(PREFIX) { PREFIX = "/usr" }
isEmpty(BIN_DIR) { BIN_DIR = "$${PREFIX}/bin" }