xwords/xwords4/linux/relaycon.c
Eric House b5ae54f313 use common/native NetLaunchInfo to implement invite-via-relay that
works between linux and Android clients. Required renaming so struct
names and names of fields within match in c and java code. The point
is to test this as the foundation of rematch: now you have to type in
a deviceID in order to invite, which clearly sucks for users. Either
that goes away, or it's replaced with something that scans existing
games and lists past opponents as possible invitees.
2015-07-10 23:19:38 -07:00

604 lines
18 KiB
C

/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <netdb.h>
#include <errno.h>
#include <stdbool.h>
#include "relaycon.h"
#include "linuxmain.h"
#include "comtypes.h"
typedef struct _RelayConStorage {
int socket;
RelayConnProcs procs;
void* procsClosure;
struct sockaddr_in saddr;
uint32_t nextID;
XWPDevProto proto;
LaunchParams* params;
} RelayConStorage;
typedef struct _MsgHeader {
XWRelayReg cmd;
uint32_t packetID;
} MsgHeader;
static RelayConStorage* getStorage( LaunchParams* params );
static XP_U32 hostNameToIP( const XP_UCHAR* name );
static gboolean relaycon_receive( GIOChannel *source, GIOCondition condition,
gpointer data );
static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len );
static size_t addVLIStr( XP_U8* buf, size_t len, const XP_UCHAR* str );
static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf );
static XP_U16 getNetShort( const XP_U8** ptr );
static XP_U32 getNetLong( const XP_U8** ptr );
static int writeHeader( RelayConStorage* storage, XP_U8* dest, XWRelayReg cmd );
static bool readHeader( const XP_U8** buf, MsgHeader* header );
static size_t writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str );
static size_t writeShort( XP_U8* buf, size_t len, XP_U16 shrt );
static size_t writeLong( XP_U8* buf, size_t len, XP_U32 lng );
static size_t writeBytes( XP_U8* buf, size_t len, const XP_U8* bytes,
size_t nBytes );
static size_t writeVLI( XP_U8* out, uint32_t nn );
static size_t un2vli( int nn, uint8_t* buf );
static bool vli2un( const uint8_t** inp, uint32_t* outp );
void
relaycon_init( LaunchParams* params, const RelayConnProcs* procs,
void* procsClosure, const char* host, int port )
{
XP_ASSERT( !params->relayConStorage );
RelayConStorage* storage = getStorage( params );
XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) );
storage->procsClosure = procsClosure;
storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
(*procs->socketAdded)( storage, storage->socket, relaycon_receive );
XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) );
storage->saddr.sin_family = PF_INET;
storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) );
storage->saddr.sin_port = htons(port);
storage->params = params;
storage->proto = XWPDEV_PROTO_VERSION_1;
}
/* Send existing relay-assigned rDevID to relay, or empty string if we have
none. Send local devID and type, ID_TYPE_NONE, if we aren't providing an
update. It's an error for neither to be provided. */
void
relaycon_reg( LaunchParams* params, const XP_UCHAR* rDevID,
DevIDType typ, const XP_UCHAR* devID )
{
XP_LOGF( "%s(typ=%d)", __func__, typ );
XP_U8 tmpbuf[256];
int indx = 0;
RelayConStorage* storage = getStorage( params );
indx += writeHeader( storage, tmpbuf, XWPDEV_REG );
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, rDevID );
assert( ID_TYPE_RELAY != typ );
tmpbuf[indx++] = typ;
if ( ID_TYPE_NONE != typ ) {
indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID );
}
indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx,
INITIAL_CLIENT_VERS );
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, SVN_REV );
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, "linux box" );
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, "linux version" );
sendIt( storage, tmpbuf, indx );
}
void
relaycon_invite( LaunchParams* params, XP_U32 destDevID,
const XP_UCHAR* relayID, NetLaunchInfo* invit )
{
XP_U8 tmpbuf[256];
int indx = 0;
RelayConStorage* storage = getStorage( params );
indx += writeHeader( storage, tmpbuf, XWPDEV_INVITE );
XP_U32 me = linux_getDevIDRelay( params );
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, me );
/* write relayID <connname>/<hid>, or if we have an actual devID write a
null byte plus it. */
if ( 0 == destDevID ) {
XP_ASSERT( '\0' != relayID[0] );
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx,
(XP_U8*)relayID, 1 + XP_STRLEN( relayID ) );
} else {
tmpbuf[indx++] = '\0'; /* null byte: zero-len str */
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, destDevID );
}
XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool)
params->vtMgr, params,
CHANNEL_NONE, NULL );
invit_saveToStream( invit, stream );
XP_U16 len = stream_getSize( stream );
indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx, len );
XP_ASSERT( indx + len < sizeof(tmpbuf) );
const XP_U8* ptr = stream_getPtr( stream );
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, ptr, len );
stream_destroy( stream );
sendIt( storage, tmpbuf, indx );
LOG_RETURN_VOID();
}
XP_S16
relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen,
XP_U32 gameToken, const CommsAddrRec* XP_UNUSED(addrRec) )
{
XP_ASSERT( 0 != gameToken );
ssize_t nSent = -1;
RelayConStorage* storage = getStorage( params );
XP_U8 tmpbuf[1 + 4 + 1 + sizeof(gameToken) + buflen];
int indx = 0;
indx += writeHeader( storage, tmpbuf, XWPDEV_MSG );
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, gameToken );
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, buf, buflen );
nSent = sendIt( storage, tmpbuf, indx );
if ( nSent > buflen ) {
nSent = buflen;
}
LOG_RETURNF( "%zd", nSent );
return nSent;
}
XP_S16
relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen,
const XP_UCHAR* relayID, XP_U32 gameToken )
{
XP_LOGF( "%s(relayID=%s)", __func__, relayID );
XP_ASSERT( 0 != gameToken );
XP_U16 indx = 0;
ssize_t nSent = -1;
RelayConStorage* storage = getStorage( params );
XP_U16 idLen = XP_STRLEN( relayID );
XP_U8 tmpbuf[1 + 4 + 1 +
1 + idLen +
sizeof(gameToken) + buflen];
indx += writeHeader( storage, tmpbuf, XWPDEV_MSGNOCONN );
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, gameToken );
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx,
(const XP_U8*)relayID, idLen );
tmpbuf[indx++] = '\n';
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, buf, buflen );
nSent = sendIt( storage, tmpbuf, indx );
if ( nSent > buflen ) {
nSent = buflen;
}
LOG_RETURNF( "%zd", nSent );
return nSent;
}
void
relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID )
{
XP_LOGF( "%s(devID=%s)", __func__, devID );
RelayConStorage* storage = getStorage( params );
XP_U8 tmpbuf[128];
int indx = 0;
indx += writeHeader( storage, tmpbuf, XWPDEV_RQSTMSGS );
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID );
sendIt( storage, tmpbuf, indx );
}
void
relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID,
XP_U32 gameToken )
{
LOG_FUNC();
RelayConStorage* storage = getStorage( params );
XP_U8 tmpbuf[128];
int indx = 0;
indx += writeHeader( storage, tmpbuf, XWPDEV_DELGAME );
indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID );
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, gameToken );
sendIt( storage, tmpbuf, indx );
}
static void
sendAckIf( RelayConStorage* storage, const MsgHeader* header )
{
if ( header->cmd != XWPDEV_ACK ) {
XP_U8 tmpbuf[16];
int indx = writeHeader( storage, tmpbuf, XWPDEV_ACK );
indx += writeVLI( &tmpbuf[indx], header->packetID );
sendIt( storage, tmpbuf, indx );
}
}
static gboolean
relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
{
XP_ASSERT( 0 != (G_IO_IN & condition) ); /* FIX ME */
RelayConStorage* storage = (RelayConStorage*)data;
XP_U8 buf[512];
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int socket = g_io_channel_unix_get_fd( source );
XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket );
ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */
(struct sockaddr*)&from, &fromlen );
gchar* b64 = g_base64_encode( (const guchar*)buf,
((0 <= nRead)? nRead : 0) );
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
g_free( b64 );
#ifdef COMMS_CHECKSUM
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead );
XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum );
g_free( sum );
#endif
if ( 0 <= nRead ) {
const XP_U8* ptr = buf;
const XP_U8* end = buf + nRead;
MsgHeader header;
if ( readHeader( &ptr, &header ) ) {
sendAckIf( storage, &header );
switch( header.cmd ) {
case XWPDEV_REGRSP: {
uint32_t len;
if ( !vli2un( &ptr, &len ) ) {
assert(0);
}
XP_UCHAR devID[len+1];
getNetString( &ptr, len, devID );
XP_U16 maxInterval = getNetShort( &ptr );
XP_LOGF( "%s: maxInterval=%d", __func__, maxInterval );
(*storage->procs.devIDReceived)( storage->procsClosure, devID,
maxInterval );
}
break;
case XWPDEV_MSG: {
CommsAddrRec addr = {0};
addr_addType( &addr, COMMS_CONN_RELAY );
(*storage->procs.msgReceived)( storage->procsClosure, &addr,
ptr, end - ptr );
}
break;
case XWPDEV_BADREG:
(*storage->procs.devIDReceived)( storage->procsClosure, NULL, 0 );
break;
case XWPDEV_HAVEMSGS: {
(*storage->procs.msgNoticeReceived)( storage->procsClosure );
break;
}
case XWPDEV_UNAVAIL: {
#ifdef DEBUG
XP_U32 unavail = getNetLong( &ptr );
XP_LOGF( "%s: unavail = %u", __func__, unavail );
#endif
uint32_t len;
if ( !vli2un( &ptr, &len ) ) {
assert(0);
}
XP_UCHAR buf[len+1];
getNetString( &ptr, len, buf );
(*storage->procs.msgErrorMsg)( storage->procsClosure, buf );
break;
}
case XWPDEV_ACK: {
uint32_t packetID;
if ( !vli2un( &ptr, &packetID ) ) {
assert( 0 );
}
XP_USE( packetID );
XP_LOGF( "got ack for packetID %d", packetID );
break;
}
case XWPDEV_ALERT: {
uint32_t len;
if ( !vli2un( &ptr, &len ) ) {
assert(0);
}
XP_UCHAR buf[len + 1];
getNetString( &ptr, len, buf );
XP_LOGF( "%s: got message: %s", __func__, buf );
break;
}
case XWPDEV_GOTINVITE: {
XP_LOGF( "%s(): got XWPDEV_GOTINVITE", __func__ );
#ifdef DEBUG
XP_U32 sender =
#endif
getNetLong( &ptr );
XP_U16 len = getNetShort( &ptr );
XWStreamCtxt* stream = mem_stream_make( MPPARM(storage->params->mpool)
storage->params->vtMgr, storage,
CHANNEL_NONE, NULL );
stream_putBytes( stream, ptr, len );
NetLaunchInfo invit;
XP_Bool success = invit_makeFromStream( &invit, stream );
XP_LOGF( "sender: %d; invit.devID: %d", sender, invit.devID );
XP_ASSERT( sender == invit.devID );
stream_destroy( stream );
if ( success ) {
(*storage->procs.inviteReceived)( storage->procsClosure,
&invit );
}
}
break;
default:
XP_LOGF( "%s: Unexpected cmd %d", __func__, header.cmd );
XP_ASSERT( 0 );
}
}
} else {
XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__,
errno, strerror(errno) );
}
return TRUE;
}
void
relaycon_cleanup( LaunchParams* params )
{
XP_FREEP( params->mpool, &params->relayConStorage );
}
static RelayConStorage*
getStorage( LaunchParams* params )
{
XP_ASSERT( params->useUdp );
RelayConStorage* storage = (RelayConStorage*)params->relayConStorage;
if ( NULL == storage ) {
storage = XP_CALLOC( params->mpool, sizeof(*storage) );
storage->socket = -1;
params->relayConStorage = storage;
}
return storage;
}
static XP_U32
hostNameToIP( const XP_UCHAR* name )
{
XP_U32 ip;
struct hostent* host;
XP_LOGF( "%s: looking up %s", __func__, name );
host = gethostbyname( name );
if ( NULL == host ) {
XP_WARNF( "gethostbyname returned NULL\n" );
} else {
XP_MEMCPY( &ip, host->h_addr_list[0], sizeof(ip) );
ip = ntohl(ip);
}
XP_LOGF( "%s found %x for %s", __func__, ip, name );
return ip;
}
static ssize_t
sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len )
{
ssize_t nSent = sendto( storage->socket, msgbuf, len, 0, /* flags */
(struct sockaddr*)&storage->saddr,
sizeof(storage->saddr) );
#ifdef COMMS_CHECKSUM
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, msgbuf, len );
XP_LOGF( "%s: sent %d bytes with sum %s", __func__, len, sum );
g_free( sum );
#else
XP_LOGF( "%s()=>%zd", __func__, nSent );
#endif
return nSent;
}
static size_t
addVLIStr( XP_U8* buf, size_t buflen, const XP_UCHAR* str )
{
uint32_t len = !!str? strlen( str ) : 0;
uint8_t nbuf[5];
size_t nsize = un2vli( len, nbuf );
if ( nsize + len <= buflen ) {
memcpy( buf, nbuf, nsize );
buf += nsize;
XP_MEMCPY( buf, str, len );
}
return nsize + len;
}
static size_t
writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str )
{
return addVLIStr( buf, len, str );
}
static size_t
writeShort( XP_U8* buf, size_t len, XP_U16 shrt )
{
shrt = htons( shrt );
assert( sizeof( shrt ) <= len );
XP_MEMCPY( buf, &shrt, sizeof(shrt) );
return sizeof(shrt);
}
static size_t
writeLong( XP_U8* buf, size_t len, XP_U32 lng )
{
lng = htonl( lng );
assert( sizeof( lng ) <= len );
memcpy( buf, &lng, sizeof(lng) );
return sizeof(lng);
}
static size_t
writeBytes( XP_U8* buf, size_t len, const XP_U8* bytes, size_t nBytes )
{
assert( nBytes <= len );
XP_MEMCPY( buf, bytes, nBytes );
return nBytes;
}
static size_t
writeVLI( XP_U8* out, uint32_t nn )
{
uint8_t buf[5];
size_t numSiz = un2vli( nn, buf );
memcpy( out, buf, numSiz );
return numSiz;
}
static XP_U16
getNetShort( const XP_U8** ptr )
{
XP_U16 result;
memcpy( &result, *ptr, sizeof(result) );
*ptr += sizeof(result);
return ntohs( result );
}
static XP_U32
getNetLong( const XP_U8** ptr )
{
XP_U32 result;
memcpy( &result, *ptr, sizeof(result) );
*ptr += sizeof(result);
return ntohl( result );
}
static void
getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf )
{
memcpy( buf, *ptr, len );
*ptr += len;
buf[len] = '\0';
}
static int
writeHeader( RelayConStorage* storage, XP_U8* dest, XWRelayReg cmd )
{
int indx = 0;
dest[indx++] = storage->proto;
XP_LOGF( "%s: wrote proto %d", __func__, storage->proto );
uint32_t packetNum = 0;
if ( XWPDEV_ACK != cmd ) {
packetNum = storage->nextID++;
}
if ( XWPDEV_PROTO_VERSION_1 == storage->proto ) {
indx += writeVLI( &dest[indx], packetNum );
} else {
assert( 0 );
}
dest[indx++] = cmd;
return indx;
}
static bool
readHeader( const XP_U8** buf, MsgHeader* header )
{
const XP_U8* ptr = *buf;
bool ok = XWPDEV_PROTO_VERSION_1 == *ptr++;
assert( ok );
if ( !vli2un( &ptr, &header->packetID ) ) {
assert( 0 );
}
XP_LOGF( "%s: got packet %d", __func__, header->packetID );
header->cmd = *ptr++;
*buf = ptr;
return ok;
}
/* Utilities */
#define TOKEN_MULT 1000000
XP_U32
makeClientToken( sqlite3_int64 rowid, XP_U16 seed )
{
XP_ASSERT( rowid < 0x0000FFFF );
XP_U32 result = rowid;
result *= TOKEN_MULT; /* so visible when displayed as base-10 */
result += seed;
return result;
}
void
rowidFromToken( XP_U32 clientToken, sqlite3_int64* rowid, XP_U16* seed )
{
*rowid = clientToken / TOKEN_MULT;
*seed = clientToken % TOKEN_MULT;
}
static size_t
un2vli( int nn, uint8_t* buf )
{
int indx = 0;
bool done = false;
do {
uint8_t byt = nn & 0x7F;
nn >>= 7;
done = 0 == nn;
if ( done ) {
byt |= 0x80;
}
buf[indx++] = byt;
} while ( !done );
return indx;
}
static bool
vli2un( const uint8_t** inp, uint32_t* outp )
{
uint32_t result = 0;
const uint8_t* in = *inp;
const uint8_t* end = in + 5;
int count;
for ( count = 0; in < end; ++count ) {
unsigned int byt = *in++;
bool done = 0 != (byt & 0x80);
if ( done ) {
byt &= 0x7F;
}
result |= byt << (7 * count);
if ( done ) {
break;
}
}
bool success = in < end;
if ( success ) {
*inp = in;
*outp = result;
}
return success;
}