From a6784464ff5515952bab527027801b1aafdf70ad Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 16 Jan 2013 06:46:33 -0800 Subject: [PATCH] first set of networking changes testing per-device communication with relay on behalf of a number of games. Works as long as all the games are open. --- xwords4/linux/Makefile | 1 + xwords4/linux/cursesmain.h | 7 -- xwords4/linux/gamesdb.c | 41 +++++++- xwords4/linux/gamesdb.h | 5 + xwords4/linux/gtkboard.c | 50 +++++++++- xwords4/linux/gtkboard.h | 2 +- xwords4/linux/gtkdraw.c | 3 +- xwords4/linux/gtkmain.c | 107 +++++++++++++++++++- xwords4/linux/linuxmain.c | 103 ++++++++++---------- xwords4/linux/main.h | 15 +++ xwords4/linux/relaycon.c | 193 +++++++++++++++++++++++++++++++++++++ xwords4/linux/relaycon.h | 36 +++++++ 12 files changed, 498 insertions(+), 65 deletions(-) create mode 100644 xwords4/linux/relaycon.c create mode 100644 xwords4/linux/relaycon.h diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 38861248a..9a74cefbc 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -204,6 +204,7 @@ OBJ = \ $(BUILD_PLAT_DIR)/linuxdict.o \ $(BUILD_PLAT_DIR)/linuxutl.o \ $(BUILD_PLAT_DIR)/gamesdb.o \ + $(BUILD_PLAT_DIR)/relaycon.o \ $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) LIBS = -lm -luuid $(GPROFFLAG) diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index 74c5da17b..29e63e1e3 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -94,13 +94,6 @@ struct CursesAppGlobals { #endif }; -#ifdef USE_GLIBLOOP -typedef struct _SourceData { - GIOChannel* channel; - gint watch; -} SourceData; -#endif - DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); /* Ports: Client and server pick a port at startup on which they'll listen. diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index d52409c32..c507bde76 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -32,7 +32,7 @@ openGamesDB( const char* dbName ) int result = sqlite3_open( dbName, &pDb ); XP_ASSERT( SQLITE_OK == result ); - const char* createStr = + const char* createGamesStr = "CREATE TABLE games ( " "game BLOB" ",room VARCHAR(32)" @@ -41,10 +41,12 @@ openGamesDB( const char* dbName ) ",nmoves INT" ",nmissing INT(2)" ")"; + result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); - result = sqlite3_exec( pDb, createStr, NULL, NULL, NULL ); + const char* createValuesStr = + "CREATE TABLE pairs ( key TEXT UNIQUE,value TEXT )"; + result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); XP_LOGF( "sqlite3_exec=>%d", result ); - // XP_ASSERT( SQLITE_OK == result ); return pDb; } @@ -214,6 +216,39 @@ loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) return XP_TRUE; } +void +store( sqlite3* pDb, const gchar* key, const gchar* value ) +{ + char buf[256]; + snprintf( buf, sizeof(buf), + "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", + key, value ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + +void +fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +{ + char query[256]; + snprintf( query, sizeof(query), + "SELECT value from pairs where key = '%s'", key ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + if ( SQLITE_ROW == result ) { + getColumnText( ppStmt, 0, buf, buflen ); + } else { + buf[0] = '\0'; + } + sqlite3_finalize( ppStmt ); +} + static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int len ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 323a1ece2..7d517a655 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -48,4 +48,9 @@ GSList* listGames( GtkAppGlobals* apg ); XP_Bool getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); +#define KEY_RDEVID "RDEVID" + +void store( sqlite3* dbp, const gchar* key, const gchar* value ); +void fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); + #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 8bdef1dcf..6f9f0b119 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1,6 +1,7 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 2000-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 @@ -498,6 +499,9 @@ createOrLoadObjects( GtkGameGlobals* globals ) #endif } + /* Need to save in order to have a valid selRow for the first send */ + saveGame( cGlobals ); + #ifndef XWFEATURE_STANDALONE_ONLY /* This may trigger network activity */ if ( !!cGlobals->game.comms ) { @@ -530,6 +534,12 @@ createOrLoadObjects( GtkGameGlobals* globals ) } } + if ( !params->fileName ) { + XP_UCHAR buf[64]; + snprintf( buf, sizeof(buf), "clientToken: %lld", cGlobals->selRow ); + gtk_window_set_title( GTK_WINDOW(globals->window), buf ); + } + #ifndef XWFEATURE_STANDALONE_ONLY if ( !!globals->cGlobals.game.comms ) { comms_start( globals->cGlobals.game.comms ); @@ -693,6 +703,7 @@ destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)data; + comms_stop( globals->cGlobals.game.comms ); saveGame( &globals->cGlobals ); windowDestroyed( globals ); // gtk_main_quit(); @@ -2219,7 +2230,7 @@ newConnectionInput( GIOChannel *source, return keepSource; /* FALSE means to remove event source */ } /* newConnectionInput */ -typedef struct SockInfo { +typedef struct _SockInfo { GIOChannel* channel; guint watch; int socket; @@ -2547,4 +2558,37 @@ makeNewGame( GtkGameGlobals* globals ) return success; } +void +gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ) +{ + XP_Bool redraw = XP_FALSE; + + XWStreamCtxt* stream = stream_from_msgbuf( &globals->cGlobals, buf, len ); + if ( !!stream ) { + if ( comms_checkIncomingStream( globals->cGlobals.game.comms, + stream, NULL ) ) { + redraw = + server_receiveMessage( globals->cGlobals.game.server, + stream ); + if ( redraw ) { + saveGame( &globals->cGlobals ); + } + } + stream_destroy( stream ); + } + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( redraw ) { + gtk_util_requestTime( globals->cGlobals.util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } +} + #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index c2e834242..e2ad10dac 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -169,7 +169,7 @@ typedef struct GtkGameGlobals { void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); - +void gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ); #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index 1f6099518..6a89bb740 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -515,7 +515,8 @@ gtk_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* letter, { GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; XP_Rect rectInset = *rect; - XP_Bool showGrid = dctx->globals->gridOn; + GtkGameGlobals* globals = dctx->globals; + XP_Bool showGrid = globals->gridOn; XP_Bool highlight = (flags & CELL_HIGHLIGHT) != 0; GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 18a3693aa..2512e826c 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -20,11 +20,12 @@ #ifdef PLATFORM_GTK -#include "xptypes.h" #include "main.h" +#include "gtkmain.h" #include "gamesdb.h" #include "gtkboard.h" #include "linuxmain.h" +#include "relaycon.h" static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); @@ -54,6 +55,22 @@ gameIsOpen( GtkAppGlobals* apg, sqlite3_int64 rowid ) return found; } +static GtkGameGlobals* +findGame( const GtkAppGlobals* apg, XP_U32 clientToken ) +{ + GtkGameGlobals* result = NULL; + GSList* iter; + for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; + CommonGlobals* cGlobals = &globals->cGlobals; + if ( cGlobals->selRow == clientToken ) { + result = globals; + break; + } + } + return result; +} + enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, MISSING_ITEM, N_ITEMS }; @@ -279,6 +296,66 @@ onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) } } +static gboolean +app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { + GtkAppGlobals* apg = (GtkAppGlobals*)data; + int socket = g_io_channel_unix_get_fd( source ); + GList* iter; + for ( iter = apg->sources; !!iter; iter = iter->next ) { + SourceData* sd = (SourceData*)iter->data; + if ( sd->channel == source ) { + (*sd->proc)( sd->procClosure, socket ); + break; + } + } + XP_ASSERT( !!iter ); /* didn't fail to find it */ + } + return TRUE; +} + +static void +socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), SockReceiver proc, + void* procClosure ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, app_socket_proc, apg ); + sd->proc = proc; + sd->procClosure = procClosure; + apg->sources = g_list_append( apg->sources, sd ); +} + +static void +gtkGotBuf( void* closure, XP_U8* buf, XP_U16 len ) +{ + LOG_FUNC(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_U32 gameToken; + XP_ASSERT( sizeof(gameToken) < len ); + gameToken = ntohl(*(XP_U32*)&buf[0]); + buf += sizeof(gameToken); + len -= sizeof(gameToken); + + GtkGameGlobals* globals = findGame( apg, gameToken ); + if ( !!globals ) { + gameGotBuf( globals, buf, len ); + } else { + XP_LOGF( "%s: game with token %lu not found; not open or deleted", + __func__, gameToken ); + } +} + +static void +gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_LOGF( "%s(devID=%s)", __func__, devID ); + store( apg->pDb, KEY_RDEVID, devID ); +} + void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) @@ -295,11 +372,39 @@ gtkmain( LaunchParams* params ) apg.selRow = -1; apg.params = params; apg.pDb = openGamesDB( params->dbName ); + params->socketChanged = socketChanged; + params->socketChangedClosure = &apg; + + RelayConnProcs procs = { + .msgReceived = gtkGotBuf, + .devIDChanged = gtkDevIDChanged, + }; + + XP_UCHAR devIDBuf[64] = {0}; + XP_UCHAR* devID; + DevIDType typ = ID_TYPE_RELAY; + if ( !!params->rDevID ) { + devID = params->rDevID; + } else { + fetch( apg.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + devID = devIDBuf; + } else { + devID = params->devID; + typ = ID_TYPE_LINUX; + } + } + + relaycon_init( params, &procs, &apg, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort, + devID, typ ); (void)makeGamesWindow( &apg ); gtk_main(); closeGamesDB( apg.pDb ); + relaycon_cleanup( params ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 5a8e0d73a..340af33ef 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -59,6 +59,7 @@ #include "main.h" #include "gamesdb.h" #include "linuxdict.h" +#include "relaycon.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif @@ -849,44 +850,49 @@ linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec ) } /* linux_init_relay_socket */ static XP_S16 -linux_tcp_send( const XP_U8* buf, XP_U16 buflen, - CommonGlobals* globals, const CommsAddrRec* addrRec ) +linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec ) { XP_S16 result = 0; - int sock = globals->socket; - - if ( sock == -1 ) { - XP_LOGF( "%s: socket uninitialized", __func__ ); - sock = linux_init_relay_socket( globals, addrRec ); - if ( sock != -1 ) { - assert( globals->socket == sock ); - (*globals->socketChanged)( globals->socketChangedClosure, - -1, sock, &globals->storage ); - } - } - - if ( sock != -1 ) { - XP_U16 netLen = htons( buflen ); - errno = 0; - - result = send( sock, &netLen, sizeof(netLen), 0 ); - if ( result == sizeof(netLen) ) { - result = send( sock, buf, buflen, 0 ); - } - if ( result <= 0 ) { - XP_STATUSF( "closing non-functional socket" ); - close( sock ); - (*globals->socketChanged)( globals->socketChangedClosure, - sock, -1, &globals->storage ); - globals->socket = -1; - } - - XP_STATUSF( "%s: send(sock=%d) returned %d of %d (err=%d)", - __func__, sock, result, buflen, errno ); + if ( !!cGlobals->pDb ) { + XP_ASSERT( -1 != cGlobals->selRow ); + result = relaycon_send( cGlobals->params, buf, buflen, + cGlobals->selRow, addrRec ); } else { - XP_LOGF( "%s: socket still -1", __func__ ); + int sock = cGlobals->socket; + + if ( sock == -1 ) { + XP_LOGF( "%s: socket uninitialized", __func__ ); + sock = linux_init_relay_socket( cGlobals, addrRec ); + if ( sock != -1 ) { + assert( cGlobals->socket == sock ); + (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, + -1, sock, &cGlobals->storage ); + } + } + + if ( sock != -1 ) { + XP_U16 netLen = htons( buflen ); + errno = 0; + + result = send( sock, &netLen, sizeof(netLen), 0 ); + if ( result == sizeof(netLen) ) { + result = send( sock, buf, buflen, 0 ); + } + if ( result <= 0 ) { + XP_STATUSF( "closing non-functional socket" ); + close( sock ); + (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, + sock, -1, &cGlobals->storage ); + cGlobals->socket = -1; + } + + XP_STATUSF( "%s: send(sock=%d) returned %d of %d (err=%d)", + __func__, sock, result, buflen, errno ); + } else { + XP_LOGF( "%s: socket still -1", __func__ ); + } } - return result; } /* linux_tcp_send */ #endif /* XWFEATURE_RELAY */ @@ -928,54 +934,53 @@ linux_reset( void* closure ) #endif XP_S16 -linux_send( const XP_U8* buf, XP_U16 buflen, - const CommsAddrRec* addrRec, +linux_send( const XP_U8* buf, XP_U16 buflen, const CommsAddrRec* addrRec, XP_U32 XP_UNUSED(gameID), void* closure ) { XP_S16 nSent = -1; - CommonGlobals* globals = (CommonGlobals*)closure; + CommonGlobals* cGlobals = (CommonGlobals*)closure; CommsConnType conType; if ( !!addrRec ) { conType = addrRec->conType; } else { - conType = globals->params->conType; + conType = cGlobals->params->conType; } if ( 0 ) { #ifdef XWFEATURE_RELAY } else if ( conType == COMMS_CONN_RELAY ) { - nSent = linux_tcp_send( buf, buflen, globals, addrRec ); - if ( nSent == buflen && globals->params->duplicatePackets ) { + nSent = linux_tcp_send( cGlobals, buf, buflen, addrRec ); + if ( nSent == buflen && cGlobals->params->duplicatePackets ) { #ifdef DEBUG XP_S16 sentAgain = #endif - linux_tcp_send( buf, buflen, globals, addrRec ); + linux_tcp_send( cGlobals, buf, buflen, addrRec ); XP_ASSERT( sentAgain == nSent ); } #endif #if defined XWFEATURE_BLUETOOTH } else if ( conType == COMMS_CONN_BT ) { - XP_Bool isServer = comms_getIsServer( globals->game.comms ); - linux_bt_open( globals, isServer ); - nSent = linux_bt_send( buf, buflen, addrRec, globals ); + XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + linux_bt_open( cGlobals, isServer ); + nSent = linux_bt_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_IP_DIRECT } else if ( conType == COMMS_CONN_IP_DIRECT ) { CommsAddrRec addr; - comms_getAddr( globals->game.comms, &addr ); - linux_udp_open( globals, &addr ); - nSent = linux_udp_send( buf, buflen, addrRec, globals ); + comms_getAddr( cGlobals->game.comms, &addr ); + linux_udp_open( cGlobals, &addr ); + nSent = linux_udp_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_SMS } else if ( COMMS_CONN_SMS == conType ) { CommsAddrRec addr; if ( !addrRec ) { - comms_getAddr( globals->game.comms, &addr ); + comms_getAddr( cGlobals->game.comms, &addr ); addrRec = &addr; } - nSent = linux_sms_send( globals, buf, buflen, + nSent = linux_sms_send( cGlobals, buf, buflen, addrRec->u.sms.phone, addrRec->u.sms.port ); #endif } else { diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index b2a68a694..4a4832419 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -44,6 +44,10 @@ typedef struct LinuxUtilCtxt { UtilVtable* vtable; } LinuxUtilCtxt; +typedef void (*SockReceiver)( void* closure, int socket ); +typedef void (*NewSocketProc)( void* closure, int newSock, int oldSock, + SockReceiver proc, void* procClosure ); + typedef struct LaunchParams { /* CommPipeCtxt* pipe; */ CurGameInfo pgi; @@ -51,12 +55,15 @@ typedef struct LaunchParams { GSList* dictDirs; char* fileName; char* dbName; + NewSocketProc socketChanged; + void* socketChangedClosure; XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE char* dbFileName; XP_U32 dbFileID; #endif + void* relayConStorage; /* opaque outside of relaycon.c */ char* pipe; char* nbs; char* bonusFile; @@ -218,11 +225,19 @@ struct CommonGlobals { XP_U16 curSaveToken; }; +typedef struct _SourceData { + GIOChannel* channel; + gint watch; + SockReceiver proc; + void* procClosure; +} SourceData; + typedef struct _GtkAppGlobals { sqlite3* pDb; sqlite3_int64 selRow; LaunchParams* params; GSList* globalsList; + GList* sources; GtkWidget* listWidget; } GtkAppGlobals; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c new file mode 100644 index 000000000..5893f9047 --- /dev/null +++ b/xwords4/linux/relaycon.c @@ -0,0 +1,193 @@ +/* -*- 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 +#include + +#include "relaycon.h" +#include "comtypes.h" + +typedef struct _RelayConStorage { + int socket; + RelayConnProcs procs; + void* procsClosure; +} RelayConStorage; + +static RelayConStorage* getStorage( LaunchParams* params ); +static void addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ); +static XP_U32 addrForHost( const CommsAddrRec* addr ); +static XP_U32 hostNameToIP( const XP_UCHAR* name ); +static void relaycon_receive( void* closure, int socket ); + +void +relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port, + const XP_UCHAR* devID, DevIDType typ ) +{ + 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 ); + (*params->socketChanged)( params, storage->socket, -1, + relaycon_receive, params ); + + XP_ASSERT( !!devID ); + XP_U16 idLen = XP_STRLEN( devID ); + XP_U16 lenNBO = XP_HTONS( idLen ); + XP_U8 tmpbuf[1 + 1 + 1 + sizeof(lenNBO) + idLen]; + tmpbuf[0] = XWREG_PROTO_VERSION; + tmpbuf[1] = XWRREG_REG; + tmpbuf[2] = typ; + XP_MEMCPY( &tmpbuf[3], &lenNBO, sizeof(lenNBO) ); + XP_MEMCPY( &tmpbuf[5], devID, idLen ); + + struct sockaddr_in to = {0}; + to.sin_family = PF_INET; + to.sin_addr.s_addr = htonl( hostNameToIP(host) ); + to.sin_port = htons(port); + + (void)sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ + (struct sockaddr*)&to, sizeof(to) ); +} + +XP_S16 +relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameToken, const CommsAddrRec* addrRec ) +{ + ssize_t nSent = -1; + RelayConStorage* storage = getStorage( params ); + + struct sockaddr_in to = {0}; + addressToServer( &to, addrRec ); + + XP_U8 tmpbuf[1 + 1 + sizeof(gameToken) + buflen]; + tmpbuf[0] = XWREG_PROTO_VERSION; + tmpbuf[1] = XWRREG_MSG; + XP_U32 inNBO = htonl(gameToken); + XP_MEMCPY( &tmpbuf[2], &inNBO, sizeof(inNBO) ); + XP_MEMCPY( &tmpbuf[1 + 1 + sizeof(gameToken)], buf, buflen ); + nSent = sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ + (struct sockaddr*)&to, sizeof(to) ); + if ( 1 + 1 + sizeof(gameToken) < nSent ) { + nSent -= 1 + 1 + sizeof(gameToken); + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} + +static void +relaycon_receive( void* closure, int socket ) +{ + LaunchParams* params = (LaunchParams*)closure; + XP_ASSERT( !!params->relayConStorage ); + RelayConStorage* storage = getStorage( params ); + XP_U8 buf[512]; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket ); + + ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */ + (struct sockaddr*)&from, &fromlen ); + XP_LOGF( "%s: read %d bytes", __func__, nRead ); + if ( 0 <= nRead ) { + XP_U8* ptr = buf; + const XP_U8* end = buf + nRead; + XP_ASSERT( XWREG_PROTO_VERSION == *ptr++ ); + XWRelayReg cmd = *ptr++; + switch( cmd ) { + case XWRREG_REGRSP: { + XP_U16 len; + XP_MEMCPY( &len, ptr, sizeof(len) ); + len = ntohs( len ); + ptr += sizeof( len ); + XP_UCHAR devID[len+1]; + XP_MEMCPY( devID, ptr, len ); + devID[len] = '\0'; + (*storage->procs.devIDChanged)( storage->procsClosure, devID ); + } + break; + case XWRREG_MSG: + (*storage->procs.msgReceived)( storage->procsClosure, + ptr, end - ptr ); + break; + default: + XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); + XP_ASSERT( 0 ); + } + } else { + XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__, + errno, strerror(errno) ); + } +} + +void +relaycon_cleanup( LaunchParams* params ) +{ + XP_FREEP( params->mpool, ¶ms->relayConStorage ); +} + +static RelayConStorage* +getStorage( LaunchParams* params ) +{ + RelayConStorage* storage = (RelayConStorage*)params->relayConStorage; + if ( NULL == storage ) { + storage = XP_CALLOC( params->mpool, sizeof(*storage) ); + storage->socket = -1; + params->relayConStorage = storage; + } + return storage; +} + +static void +addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ) +{ + to->sin_family = PF_INET; + to->sin_addr.s_addr = htonl( addrForHost(addr) ); + to->sin_port = htons(addr->u.ip_relay.port); +} + +static XP_U32 +addrForHost( const CommsAddrRec* addr ) +{ + XP_U32 ip = addr->u.ip_relay.ipAddr; + if ( 0L == ip ) { + ip = hostNameToIP( addr->u.ip_relay.hostName ); + } + return ip; +} /* addrForHost */ + +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 %lx for %s", __func__, ip, name ); + return ip; +} diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h new file mode 100644 index 000000000..64c547580 --- /dev/null +++ b/xwords4/linux/relaycon.h @@ -0,0 +1,36 @@ +/* -*- 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. + */ + +#ifndef _RELAYCON_H_ +#define _RELAYCON_H_ + +#include "main.h" + +typedef struct _Procs { + void (*msgReceived)( void* closure, XP_U8* buf, XP_U16 len ); + void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); +} RelayConnProcs; + +void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port, + const XP_UCHAR* devID, DevIDType typ ); +XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameID, const CommsAddrRec* addrRec ); +void relaycon_cleanup( LaunchParams* params ); +#endif