953 lines
26 KiB
C
953 lines
26 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
xirm_server.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
XInput remote server program
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.h"
|
||
|
#include "xinput_remote.h"
|
||
|
#include "list.h"
|
||
|
|
||
|
// Our module name for debug spews
|
||
|
#define MYMODNAME "XIRM"
|
||
|
|
||
|
BOOL dosetup;
|
||
|
SOCKET sock, listensock;
|
||
|
HANDLE recvevt;
|
||
|
CHAR recvbuf[XIRM_MAX_PACKET_SIZE];
|
||
|
struct sockaddr_in clientaddr;
|
||
|
#define CLIENTADDR clientaddr.sin_addr.s_addr
|
||
|
|
||
|
BOOL autonetmode;
|
||
|
CHAR clientname[XIRM_MACHINE_NAME_MAXLEN];
|
||
|
DWORD connecttime;
|
||
|
DWORD clientisn, clientseq;
|
||
|
DWORD serverseq_una, serverseq_nxt;
|
||
|
INT fastrexmit, rexmit_timer, rexmit_count;
|
||
|
BOOL rexmit_disabled;
|
||
|
LIST_ENTRY sendq;
|
||
|
|
||
|
struct ConnectionRequest {
|
||
|
DWORD clientisn;
|
||
|
struct sockaddr_in clientaddr;
|
||
|
CHAR clientname[XIRM_MACHINE_NAME_MAXLEN];
|
||
|
} connection_request;
|
||
|
CRITICAL_SECTION connection_request_lock;
|
||
|
|
||
|
#define ConnectReqLock() EnterCriticalSection(&connection_request_lock)
|
||
|
#define ConnectReqUnlock() LeaveCriticalSection(&connection_request_lock)
|
||
|
#define HasPendingConnectReq() (connection_request.clientaddr.sin_addr.s_addr != 0)
|
||
|
|
||
|
__inline BOOL GetPendingConnectReq(struct ConnectionRequest* connreq) {
|
||
|
ConnectReqLock();
|
||
|
*connreq = connection_request;
|
||
|
connection_request.clientaddr.sin_addr.s_addr = 0;
|
||
|
ConnectReqUnlock();
|
||
|
|
||
|
return (connreq->clientaddr.sin_addr.s_addr != 0);
|
||
|
}
|
||
|
|
||
|
#define GetSendqHead() ((SendBuf*) sendq.Flink)
|
||
|
#define GetSendqLen() ((INT) (serverseq_nxt - serverseq_una))
|
||
|
#define IsSendqFull() (GetSendqLen() >= XIRM_SEND_WINDOW)
|
||
|
#define IsSendqEmpty() (serverseq_una == serverseq_nxt)
|
||
|
|
||
|
typedef struct _SendBuf {
|
||
|
LIST_ENTRY links;
|
||
|
INT datalen;
|
||
|
XIrmPacketHeader data;
|
||
|
} SendBuf;
|
||
|
|
||
|
DWORD active_device_masks;
|
||
|
DWORD pending_insertions;
|
||
|
DWORD pending_removals;
|
||
|
struct DeviceData {
|
||
|
HANDLE handle;
|
||
|
XINPUT_STATE current_states;
|
||
|
XINPUT_STATE last_states;
|
||
|
XINPUT_CAPABILITIES devicecaps;
|
||
|
} devices[XIRM_PORTMAX];
|
||
|
|
||
|
//
|
||
|
// Main server loop sampling interval (10 msecs = 100Hz)
|
||
|
//
|
||
|
#define SAMPLING_INTERVAL 10
|
||
|
|
||
|
//
|
||
|
// Read the processor timestamp counter in milliseconds
|
||
|
//
|
||
|
__inline DWORD ReadTimestamp() {
|
||
|
__asm {
|
||
|
rdtsc
|
||
|
mov ebx, 733000
|
||
|
div ebx
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Read the client configuration file
|
||
|
//
|
||
|
#define CONFIGFILENAME "d:\\xirmsrv.cfg"
|
||
|
|
||
|
BOOL
|
||
|
ReadConfig()
|
||
|
{
|
||
|
CHAR buf[256];
|
||
|
FILE* fin = fopen(CONFIGFILENAME, "r");
|
||
|
|
||
|
if (!fin) {
|
||
|
XDBGTRC(MYMODNAME, "cannot open config file");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
while (fgets(buf, sizeof(buf), fin)) {
|
||
|
CHAR *p, *val;
|
||
|
|
||
|
p = strchr(buf, '=');
|
||
|
if (!p) continue;
|
||
|
|
||
|
*p++ = '\0';
|
||
|
while (*p && isspace(*p)) p++;
|
||
|
val = p;
|
||
|
|
||
|
while (*p && *p != '\r' && *p != '\n' && !isspace(*p)) p++;
|
||
|
*p = '\0';
|
||
|
|
||
|
if (*val == '\0') continue;
|
||
|
if (strcmp(buf, "name") == 0) {
|
||
|
if (strlen(val) < XIRM_MACHINE_NAME_MAXLEN) {
|
||
|
strcpy(clientname, val);
|
||
|
}
|
||
|
} else if (strcmp(buf, "ipaddr") == 0) {
|
||
|
ULONG addr = inet_addr(val);
|
||
|
if (addr != INADDR_NONE)
|
||
|
CLIENTADDR = addr;
|
||
|
}
|
||
|
}
|
||
|
fclose(fin);
|
||
|
|
||
|
XDBGTRC(MYMODNAME, "client config: %s %u.%u.%u.%u", clientname, clientaddr.sin_addr.S_un.S_un_b.s_b1, clientaddr.sin_addr.S_un.S_un_b.s_b2, clientaddr.sin_addr.S_un.S_un_b.s_b3, clientaddr.sin_addr.S_un.S_un_b.s_b4);
|
||
|
return (clientname[0] != '\0' || CLIENTADDR != 0);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Write the client configuration file
|
||
|
//
|
||
|
BOOL
|
||
|
WriteConfig()
|
||
|
{
|
||
|
FILE* fout = fopen(CONFIGFILENAME, "w");
|
||
|
if (!fout) return FALSE;
|
||
|
|
||
|
fprintf(fout, "name=%s\n", clientname[0] ? clientname : "<NONE>");
|
||
|
fprintf(fout, "ipaddr=%u.%u.%u.%u\n", clientaddr.sin_addr.S_un.S_un_b.s_b1, clientaddr.sin_addr.S_un.S_un_b.s_b2, clientaddr.sin_addr.S_un.S_un_b.s_b3, clientaddr.sin_addr.S_un.S_un_b.s_b4);
|
||
|
fclose(fout);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Close the specified port
|
||
|
//
|
||
|
VOID ClosePort(DWORD port) {
|
||
|
struct DeviceData* device = &devices[port];
|
||
|
if (device->handle) {
|
||
|
XInputClose(device->handle);
|
||
|
memset(device, 0, sizeof(*device));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Open the specified port
|
||
|
//
|
||
|
BOOL OpenPort(DWORD port) {
|
||
|
struct DeviceData* device = &devices[port];
|
||
|
DWORD err;
|
||
|
|
||
|
ASSERT(device->handle == NULL);
|
||
|
device->handle = XInputOpen(
|
||
|
XDEVICE_TYPE_GAMEPAD,
|
||
|
port,
|
||
|
XDEVICE_NO_SLOT,
|
||
|
NULL);
|
||
|
|
||
|
if (device->handle == NULL) {
|
||
|
XDBGWRN(MYMODNAME, "XInputOpen failed: %d", GetLastError());
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
err = XInputGetCapabilities(device->handle, &device->devicecaps);
|
||
|
if (err != ERROR_SUCCESS) {
|
||
|
XDBGWRN(MYMODNAME, "XInputGetCapabilities failed: %d", err);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Poll the input states
|
||
|
//
|
||
|
VOID
|
||
|
PollInput()
|
||
|
{
|
||
|
DWORD insertions, removals;
|
||
|
DWORD port;
|
||
|
|
||
|
// Check if there has been any device changes
|
||
|
if (XGetDeviceChanges(XDEVICE_TYPE_GAMEPAD, &insertions, &removals)) {
|
||
|
// close devices that have been removed
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (removals & (1 << port)) {
|
||
|
if (devices[port].handle) {
|
||
|
ClosePort(port);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// open devices that have been inserted
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (insertions & (1 << port)) {
|
||
|
if (!OpenPort(port)) {
|
||
|
// If we failed to open the port, treat it as not plugged in
|
||
|
removals |= (1 << port);
|
||
|
insertions &= ~(1 << port);
|
||
|
ClosePort(port);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
active_device_masks &= ~removals;
|
||
|
active_device_masks |= insertions;
|
||
|
|
||
|
pending_removals |= removals;
|
||
|
pending_insertions &= ~removals;
|
||
|
pending_insertions |= insertions;
|
||
|
}
|
||
|
|
||
|
// Now for currently inserted devices, update the state information
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
struct DeviceData* device = &devices[port];
|
||
|
if (device->handle) {
|
||
|
DWORD err = XInputGetState(device->handle, &device->current_states);
|
||
|
if (err != ERROR_SUCCESS) {
|
||
|
XDBGWRN(MYMODNAME, "XInputGetState failed: %d", err);
|
||
|
device->current_states = device->last_states;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize the input module
|
||
|
//
|
||
|
#define MAGIC_KEYCOMBO (XINPUT_GAMEPAD_START | XINPUT_GAMEPAD_BACK)
|
||
|
|
||
|
VOID
|
||
|
InitInput()
|
||
|
{
|
||
|
DWORD port;
|
||
|
|
||
|
XInitDevices(0, NULL);
|
||
|
Sleep(2000);
|
||
|
|
||
|
// Get the initial device states
|
||
|
PollInput();
|
||
|
|
||
|
// Look for the special key combination to enter Setup mode
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (devices[port].handle &&
|
||
|
devices[port].current_states.Gamepad.wButtons == MAGIC_KEYCOMBO &&
|
||
|
devices[port].current_states.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_X]) {
|
||
|
dosetup = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Transmit the first packet in the send queue
|
||
|
//
|
||
|
#define SetRexmitTimer() (rexmit_timer = ReadTimestamp() + XIRM_RETRANSMIT_TIMEOUT)
|
||
|
__inline VOID EnableRexmit() {
|
||
|
if (rexmit_disabled) {
|
||
|
rexmit_disabled = FALSE;
|
||
|
rexmit_count = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SendqXmit(
|
||
|
SendBuf* sendbuf
|
||
|
)
|
||
|
{
|
||
|
INT count;
|
||
|
|
||
|
count = send(sock, (CHAR*) &sendbuf->data, sendbuf->datalen, 0);
|
||
|
if (count != sendbuf->datalen) {
|
||
|
XDBGWRN(MYMODNAME, "send failed: %d, %d", count, GetLastError());
|
||
|
}
|
||
|
|
||
|
if (sendbuf == GetSendqHead()) {
|
||
|
SetRexmitTimer();
|
||
|
}
|
||
|
rexmit_count++;
|
||
|
fastrexmit = 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Do retransmission if needed
|
||
|
//
|
||
|
VOID
|
||
|
CheckRexmit()
|
||
|
{
|
||
|
if (rexmit_disabled || IsSendqEmpty()) return;
|
||
|
|
||
|
if ((INT) (rexmit_timer - ReadTimestamp()) < 0) {
|
||
|
if (rexmit_count >= XIRM_RETRANSMIT_RETRY) {
|
||
|
// If too many retries, we assume the client is down
|
||
|
// and stop retransmitting until we receive a packet
|
||
|
// from the client or there is an input state change.
|
||
|
XDBGTRC(MYMODNAME, "client down?");
|
||
|
rexmit_disabled = TRUE;
|
||
|
} else {
|
||
|
SendqXmit(GetSendqHead());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Insert a new send buffer to the end of the send queue
|
||
|
// and then transmit it out to the client
|
||
|
//
|
||
|
VOID
|
||
|
InsertSendBuf(
|
||
|
SendBuf* sendbuf
|
||
|
)
|
||
|
{
|
||
|
if (IsListEmpty(&sendq)) rexmit_count = 0;
|
||
|
|
||
|
InsertTailList(&sendq, &sendbuf->links);
|
||
|
SendqXmit(sendbuf);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate memory buffer for a new outgoing packet
|
||
|
//
|
||
|
SendBuf*
|
||
|
AllocSendBuf(
|
||
|
INT size
|
||
|
)
|
||
|
{
|
||
|
SendBuf* sendbuf = (SendBuf*) malloc(offsetof(SendBuf, data) + size);
|
||
|
|
||
|
if (sendbuf) {
|
||
|
sendbuf->datalen = size;
|
||
|
sendbuf->data.sendseq = serverseq_nxt++;
|
||
|
sendbuf->data.ackseq = clientseq;
|
||
|
} else {
|
||
|
XDBGWRN(MYMODNAME, "out of memory");
|
||
|
}
|
||
|
return sendbuf;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Send device changes to the client
|
||
|
//
|
||
|
VOID
|
||
|
SendDeviceChanges()
|
||
|
{
|
||
|
SendBuf* sendbuf;
|
||
|
XIrmPacketDeviceChanges* devchgpkt;
|
||
|
XINPUT_CAPABILITIES* devcaps;
|
||
|
INT pktlen;
|
||
|
DWORD port;
|
||
|
|
||
|
// Return immediately if:
|
||
|
// there has been no changes, or
|
||
|
// the send queue is full
|
||
|
if ((pending_insertions | pending_removals) == 0 || IsSendqFull())
|
||
|
return;
|
||
|
|
||
|
pktlen = offsetof(XIrmPacketDeviceChanges, devicecaps) +
|
||
|
XIrmCountDevices(active_device_masks) * sizeof(XINPUT_CAPABILITIES);
|
||
|
|
||
|
sendbuf = AllocSendBuf(pktlen);
|
||
|
if (!sendbuf) return;
|
||
|
|
||
|
devchgpkt = (XIrmPacketDeviceChanges*) &sendbuf->data;
|
||
|
devchgpkt->header.type = PKTTYPE_DEVICE_CHANGES;
|
||
|
devchgpkt->active_devices = active_device_masks;
|
||
|
devchgpkt->insertions = pending_insertions;
|
||
|
devchgpkt->removals = pending_removals;
|
||
|
|
||
|
// NOTE: We don't bother with keyboard support on silver box.
|
||
|
devchgpkt->keyboard_device = 0;
|
||
|
|
||
|
pending_insertions = pending_removals = 0;
|
||
|
devcaps = devchgpkt->devicecaps;
|
||
|
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (active_device_masks & (1 << port)) {
|
||
|
*devcaps++ = devices[port].devicecaps;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Start retransmission again if input state has changed
|
||
|
EnableRexmit();
|
||
|
InsertSendBuf(sendbuf);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Send input state changes to the client
|
||
|
//
|
||
|
VOID
|
||
|
SendStateChanges()
|
||
|
{
|
||
|
DWORD port, masks = 0;
|
||
|
INT pktlen = 0;
|
||
|
SendBuf* sendbuf;
|
||
|
XIrmPacketStateChanges* stchgpkt;
|
||
|
XINPUT_STATE* states;
|
||
|
|
||
|
if (IsSendqFull()) return;
|
||
|
|
||
|
// Check if there has been any state changes
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (devices[port].handle) {
|
||
|
ASSERT(active_device_masks & (1 << port));
|
||
|
if (memcmp(&devices[port].current_states,
|
||
|
&devices[port].last_states,
|
||
|
sizeof(XINPUT_STATE)) != 0) {
|
||
|
masks |= (1 << port);
|
||
|
pktlen++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (masks == 0) return;
|
||
|
|
||
|
pktlen = offsetof(XIrmPacketStateChanges, states) + pktlen * sizeof(XINPUT_STATE);
|
||
|
sendbuf = AllocSendBuf(pktlen);
|
||
|
if (!sendbuf) return;
|
||
|
|
||
|
stchgpkt = (XIrmPacketStateChanges*) &sendbuf->data;
|
||
|
stchgpkt->header.type = PKTTYPE_STATE_CHANGES;
|
||
|
stchgpkt->device_masks = masks;
|
||
|
states = stchgpkt->states;
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
if (masks & (1 << port)) {
|
||
|
*states++ = devices[port].last_states = devices[port].current_states;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Start retransmission again if input state has changed
|
||
|
EnableRexmit();
|
||
|
InsertSendBuf(sendbuf);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize the connection with the client
|
||
|
//
|
||
|
VOID
|
||
|
InitClient(
|
||
|
struct ConnectionRequest* connreq
|
||
|
)
|
||
|
{
|
||
|
SendBuf* sendbuf;
|
||
|
DWORD port;
|
||
|
INT err;
|
||
|
|
||
|
strcpy(clientname, connreq->clientname);
|
||
|
clientaddr = connreq->clientaddr;
|
||
|
|
||
|
XDBGTRC(MYMODNAME,
|
||
|
"connected to client: %s %u.%u.%u.%u:%d",
|
||
|
clientname,
|
||
|
clientaddr.sin_addr.S_un.S_un_b.s_b1, clientaddr.sin_addr.S_un.S_un_b.s_b2, clientaddr.sin_addr.S_un.S_un_b.s_b3, clientaddr.sin_addr.S_un.S_un_b.s_b4,
|
||
|
ntohs(clientaddr.sin_port));
|
||
|
|
||
|
err = connect(sock, (struct sockaddr*) &clientaddr, sizeof(clientaddr));
|
||
|
ASSERT(err == NO_ERROR);
|
||
|
|
||
|
connecttime = ReadTimestamp();
|
||
|
rexmit_disabled = FALSE;
|
||
|
clientisn = connreq->clientisn;
|
||
|
clientseq = clientisn+1;
|
||
|
serverseq_una = serverseq_nxt = XIrmGetISN();
|
||
|
|
||
|
while (!IsListEmpty(&sendq)) {
|
||
|
LIST_ENTRY* p = RemoveHeadList(&sendq);
|
||
|
free(p);
|
||
|
}
|
||
|
|
||
|
sendbuf = AllocSendBuf(sizeof(XIrmPacketHeader));
|
||
|
ASSERT(sendbuf != NULL);
|
||
|
sendbuf->data.type = PKTTYPE_SERVER_INIT;
|
||
|
InsertSendBuf(sendbuf);
|
||
|
|
||
|
// Send the initial device caps and states
|
||
|
pending_insertions = active_device_masks;
|
||
|
pending_removals = 0;
|
||
|
for (port=0; port < XIRM_PORTMAX; port++) {
|
||
|
memset(&devices[port].last_states, 0, sizeof(devices[port].last_states));
|
||
|
}
|
||
|
|
||
|
SendDeviceChanges();
|
||
|
SendStateChanges();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Process a set-state packet from the client
|
||
|
//
|
||
|
VOID
|
||
|
ProcessClientSetState(
|
||
|
XIrmPacketSetState* pkt
|
||
|
)
|
||
|
{
|
||
|
static XINPUT_FEEDBACK feedback;
|
||
|
DWORD err;
|
||
|
|
||
|
if (pkt->port < XIRM_PORTMAX &&
|
||
|
devices[pkt->port].handle &&
|
||
|
feedback.Header.dwStatus != ERROR_IO_PENDING) {
|
||
|
feedback = pkt->feedback;
|
||
|
err = XInputSetState(devices[pkt->port].handle, &feedback);
|
||
|
if (err != ERROR_SUCCESS &&
|
||
|
err != ERROR_IO_PENDING &&
|
||
|
err != ERROR_NOT_SUPPORTED) {
|
||
|
XDBGWRN(MYMODNAME, "XInputSetState failed: %d", err);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if we should serve an incoming connection request from a client
|
||
|
//
|
||
|
__inline BOOL IsAutonetAddr(ULONG addr) {
|
||
|
// autonet address range is 169.254.x.x/16
|
||
|
addr = ntohl(addr);
|
||
|
return (addr & 0xffff0000) == 0xa9fe0000;
|
||
|
}
|
||
|
|
||
|
INT
|
||
|
MatchClient(
|
||
|
CHAR* name,
|
||
|
ULONG addr
|
||
|
)
|
||
|
{
|
||
|
XNADDR XnAddr;
|
||
|
static struct in_addr myaddr;
|
||
|
|
||
|
// Try name match first
|
||
|
if (clientname[0] && strcmp(clientname, name) == 0) return 1;
|
||
|
|
||
|
// Then try explicit address match
|
||
|
if (CLIENTADDR && CLIENTADDR == addr) return 2;
|
||
|
|
||
|
// Now check to see if both client and server are using autonet addresses
|
||
|
if (IsAutonetAddr(addr)) {
|
||
|
if (myaddr.s_addr == 0) {
|
||
|
XNetGetTitleXnAddr(&XnAddr);
|
||
|
CopyMemory(&myaddr, &XnAddr.ina, sizeof(myaddr));
|
||
|
}
|
||
|
if (IsAutonetAddr(myaddr.s_addr)) return 3;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Process a client-init packet from the client
|
||
|
//
|
||
|
VOID
|
||
|
ProcessClientInit()
|
||
|
{
|
||
|
struct ConnectionRequest connreq;
|
||
|
|
||
|
if (GetPendingConnectReq(&connreq) &&
|
||
|
MatchClient(connreq.clientname, connreq.clientaddr.sin_addr.s_addr)) {
|
||
|
// Figure out how long has it been since the last connect
|
||
|
INT time = ReadTimestamp() - connecttime;
|
||
|
if (connreq.clientisn == clientisn && time >= 0 && time <= 3000) {
|
||
|
// The packet is a duplicate - just drop it
|
||
|
} else {
|
||
|
// The client must have rebooted, reestablish connection
|
||
|
XDBGTRC(MYMODNAME, "client must have rebooted");
|
||
|
InitClient(&connreq);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Process an acknowledgement packet from the client
|
||
|
//
|
||
|
VOID
|
||
|
ProcessClientAck(
|
||
|
XIrmPacketHeader* pkt
|
||
|
)
|
||
|
{
|
||
|
DWORD ack;
|
||
|
INT index, sent;
|
||
|
|
||
|
ack = pkt->ackseq;
|
||
|
index = (INT) (ack - serverseq_una);
|
||
|
sent = GetSendqLen();
|
||
|
|
||
|
if (index > 0 && index <= sent) {
|
||
|
// The acknowledgement is valid
|
||
|
ASSERT(!IsListEmpty(&sendq));
|
||
|
serverseq_una = ack;
|
||
|
|
||
|
do {
|
||
|
SendBuf* sendbuf = GetSendqHead();
|
||
|
if (sendbuf->data.sendseq == ack) {
|
||
|
SetRexmitTimer();
|
||
|
rexmit_count = 1;
|
||
|
fastrexmit = 0;
|
||
|
break;
|
||
|
}
|
||
|
RemoveHeadList(&sendq);
|
||
|
free(sendbuf);
|
||
|
} while (!IsListEmpty(&sendq));
|
||
|
|
||
|
} else if (index == 0 && sent != 0) {
|
||
|
// Fast retransmit
|
||
|
if (++fastrexmit >= 2) {
|
||
|
XDBGTRC(MYMODNAME, "fast rexmit");
|
||
|
SendqXmit(GetSendqHead());
|
||
|
}
|
||
|
} else {
|
||
|
XDBGTRC(MYMODNAME,
|
||
|
"ack ignored: %u, %u, %u",
|
||
|
ack, serverseq_una, serverseq_nxt);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Service network traffic from the client
|
||
|
//
|
||
|
VOID
|
||
|
ServiceClient()
|
||
|
{
|
||
|
static BOOL pending = FALSE;
|
||
|
static WSAOVERLAPPED overlapped;
|
||
|
DWORD bytesrecv, flags;
|
||
|
INT err;
|
||
|
|
||
|
while (TRUE) {
|
||
|
// Issue the overlapped recv request if necessary
|
||
|
if (!pending) {
|
||
|
WSABUF wsabuf;
|
||
|
wsabuf.buf = recvbuf;
|
||
|
wsabuf.len = sizeof(recvbuf);
|
||
|
flags = 0;
|
||
|
overlapped.hEvent = recvevt;
|
||
|
err = WSARecv(sock, &wsabuf, 1, &bytesrecv, &flags, &overlapped, NULL);
|
||
|
if (err == SOCKET_ERROR) {
|
||
|
err = WSAGetLastError();
|
||
|
if (err == WSA_IO_PENDING) {
|
||
|
pending = TRUE;
|
||
|
} else {
|
||
|
XDBGWRN(MYMODNAME, "failed to issue recv request: %d", err);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If an overlapped recv request is pending,
|
||
|
// check to see if it's completed yet.
|
||
|
if (pending) {
|
||
|
if (!WSAGetOverlappedResult(sock, &overlapped, &bytesrecv, FALSE, &flags)) {
|
||
|
err = WSAGetLastError();
|
||
|
if (err == WSA_IO_INCOMPLETE) return;
|
||
|
|
||
|
//
|
||
|
// This is very bad -
|
||
|
// cancel the pending recv request and try to issue a new one
|
||
|
//
|
||
|
XDBGWRN(MYMODNAME, "get overlapped result failed: %d", err);
|
||
|
WSACancelOverlappedIO(sock);
|
||
|
pending = FALSE;
|
||
|
continue;
|
||
|
}
|
||
|
pending = FALSE;
|
||
|
}
|
||
|
|
||
|
// Client must be alive again - enable retransmission
|
||
|
EnableRexmit();
|
||
|
|
||
|
if (bytesrecv >= sizeof(XIrmPacketHeader)) {
|
||
|
XIrmPacketHeader* pkt = (XIrmPacketHeader*) recvbuf;
|
||
|
|
||
|
if (pkt->sendseq != clientseq) {
|
||
|
XDBGTRC(MYMODNAME,
|
||
|
"client packet seq? %d %u, %u",
|
||
|
pkt->type, pkt->sendseq, clientseq);
|
||
|
}
|
||
|
|
||
|
switch (pkt->type) {
|
||
|
case PKTTYPE_ACK:
|
||
|
if (bytesrecv == sizeof(XIrmPacketHeader)) {
|
||
|
ProcessClientAck(pkt);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PKTTYPE_SET_STATE:
|
||
|
if (bytesrecv == sizeof(XIrmPacketSetState)) {
|
||
|
ProcessClientSetState((XIrmPacketSetState*) pkt);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
XDBGTRC(MYMODNAME, "client packet type? %d", pkt->type);
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
XDBGWRN(MYMODNAME, "client packet size? %d", bytesrecv);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for a client connection
|
||
|
//
|
||
|
VOID
|
||
|
WaitForClient()
|
||
|
{
|
||
|
struct ConnectionRequest connreq;
|
||
|
INT match = 0;
|
||
|
|
||
|
XDBGTRC(MYMODNAME,
|
||
|
"waiting for client connection: %u.%u.%u.%u %s",
|
||
|
clientname,
|
||
|
clientaddr.sin_addr.S_un.S_un_b.s_b1, clientaddr.sin_addr.S_un.S_un_b.s_b2, clientaddr.sin_addr.S_un.S_un_b.s_b3, clientaddr.sin_addr.S_un.S_un_b.s_b4);
|
||
|
|
||
|
do {
|
||
|
Sleep(SAMPLING_INTERVAL);
|
||
|
if (HasPendingConnectReq() && GetPendingConnectReq(&connreq)) {
|
||
|
match = MatchClient(connreq.clientname, connreq.clientaddr.sin_addr.s_addr);
|
||
|
if (!match) {
|
||
|
// the connection request wasn't valid
|
||
|
XDBGTRC(MYMODNAME, "client-init doesn't match");
|
||
|
}
|
||
|
}
|
||
|
} while (!match);
|
||
|
|
||
|
if (match == 2 && connreq.clientname[0]) {
|
||
|
// We have an address match and the client has a name.
|
||
|
// Update the config file with the client name so
|
||
|
// that we can do name match next time around.
|
||
|
strcpy(clientname, connreq.clientname);
|
||
|
WriteConfig();
|
||
|
}
|
||
|
|
||
|
InitClient(&connreq);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Server loop
|
||
|
//
|
||
|
VOID
|
||
|
RunServer()
|
||
|
{
|
||
|
DWORD timer;
|
||
|
|
||
|
recvevt = WSACreateEvent();
|
||
|
ASSERT(recvevt != NULL);
|
||
|
ServiceClient();
|
||
|
|
||
|
timer = ReadTimestamp() + SAMPLING_INTERVAL;
|
||
|
while (TRUE) {
|
||
|
INT timeout = timer - ReadTimestamp();
|
||
|
|
||
|
if (timeout > 0) {
|
||
|
DWORD wait = WaitForSingleObject(recvevt, timeout);
|
||
|
if (wait == WAIT_OBJECT_0) {
|
||
|
// Process packets from the client
|
||
|
ServiceClient();
|
||
|
}
|
||
|
} else {
|
||
|
if (HasPendingConnectReq()) {
|
||
|
ProcessClientInit();
|
||
|
}
|
||
|
|
||
|
// Do retransmit if necessary
|
||
|
CheckRexmit();
|
||
|
|
||
|
// Check input state changes
|
||
|
PollInput();
|
||
|
SendDeviceChanges();
|
||
|
SendStateChanges();
|
||
|
|
||
|
timer = ReadTimestamp() + SAMPLING_INTERVAL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Client setup mode
|
||
|
//
|
||
|
VOID
|
||
|
DoSetup()
|
||
|
{
|
||
|
struct ConnectionRequest connreq;
|
||
|
|
||
|
XDBGTRC(MYMODNAME, "enter setup mode");
|
||
|
memset(clientname, 0, sizeof(clientname));
|
||
|
CLIENTADDR = 0;
|
||
|
|
||
|
while (TRUE) {
|
||
|
Sleep(SAMPLING_INTERVAL);
|
||
|
if (HasPendingConnectReq()) {
|
||
|
if (!GetPendingConnectReq(&connreq)) continue;
|
||
|
|
||
|
// If both the client and server are using autonet address,
|
||
|
// we'll automatically accept the client connection request.
|
||
|
if (MatchClient(connreq.clientname, connreq.clientaddr.sin_addr.s_addr)) {
|
||
|
autonetmode = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Display the client name / address in the UI and
|
||
|
// ask the user for confirmation.
|
||
|
// BUGBUG - not yet implemented
|
||
|
XDBGWRN(MYMODNAME,
|
||
|
"accept connection from %s %u.%u.%u.%u?",
|
||
|
connreq.clientname,
|
||
|
connreq.clientaddr.sin_addr.S_un.S_un_b.s_b1, connreq.clientaddr.sin_addr.S_un.S_un_b.s_b2, connreq.clientaddr.sin_addr.S_un.S_un_b.s_b3, connreq.clientaddr.sin_addr.S_un.S_un_b.s_b4);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
InitClient(&connreq);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize network stack
|
||
|
//
|
||
|
VOID
|
||
|
InitNet()
|
||
|
{
|
||
|
INT err;
|
||
|
WSADATA wsadata;
|
||
|
struct sockaddr_in sockname;
|
||
|
|
||
|
err = XNetStartup(NULL);
|
||
|
ASSERT(err == NO_ERROR);
|
||
|
|
||
|
err = WSAStartup(WINSOCK_VERSION, &wsadata);
|
||
|
ASSERT(err == NO_ERROR);
|
||
|
|
||
|
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||
|
ASSERT(sock != INVALID_SOCKET);
|
||
|
|
||
|
memset(&sockname, 0, sizeof(sockname));
|
||
|
sockname.sin_family = AF_INET;
|
||
|
err = bind(sock, (struct sockaddr*) &sockname, sizeof(sockname));
|
||
|
ASSERT(err == NO_ERROR);
|
||
|
|
||
|
listensock = socket(AF_INET, SOCK_DGRAM, 0);
|
||
|
ASSERT(listensock != INVALID_SOCKET);
|
||
|
|
||
|
sockname.sin_port = htons(XIRM_SERVER_PORT);
|
||
|
err = bind(listensock, (struct sockaddr*) &sockname, sizeof(sockname));
|
||
|
ASSERT(err == NO_ERROR);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Server thread that listens for incoming client connection requests
|
||
|
//
|
||
|
DWORD WINAPI
|
||
|
ListenThreadProc(
|
||
|
VOID* param
|
||
|
)
|
||
|
{
|
||
|
CHAR buf[XIRM_MAX_PACKET_SIZE];
|
||
|
struct sockaddr sockname;
|
||
|
struct sockaddr_in* sockin;
|
||
|
XIrmPacketClientInit* initpkt;
|
||
|
INT err;
|
||
|
|
||
|
initpkt = (XIrmPacketClientInit*) buf;
|
||
|
memset(&sockname, 0, sizeof(sockname));
|
||
|
sockin = (struct sockaddr_in*) &sockname;
|
||
|
|
||
|
while (TRUE) {
|
||
|
INT fromlen = sizeof(sockname);
|
||
|
INT count = recvfrom(listensock, buf, sizeof(buf), 0, &sockname, &fromlen);
|
||
|
|
||
|
if (count != sizeof(*initpkt) || initpkt->header.type != PKTTYPE_CLIENT_INIT) {
|
||
|
if (count >= sizeof(XIrmPacketHeader)) {
|
||
|
XDBGTRC(MYMODNAME, "packet from %u.%u.%u.%u:%d discard: %d",
|
||
|
sockin->sin_addr.S_un.S_un_b.s_b1, sockin->sin_addr.S_un.S_un_b.s_b2, sockin->sin_addr.S_un.S_un_b.s_b3, sockin->sin_addr.S_un.S_un_b.s_b4,
|
||
|
ntohs(sockin->sin_port),
|
||
|
initpkt->header.type);
|
||
|
} else {
|
||
|
XDBGWRN(MYMODNAME, "recv error? %d, %d", count, GetLastError());
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
XDBGTRC(MYMODNAME,
|
||
|
"client-init from %u.%u.%u.%u:%d - %x %u",
|
||
|
sockin->sin_addr.S_un.S_un_b.s_b1, sockin->sin_addr.S_un.S_un_b.s_b2, sockin->sin_addr.S_un.S_un_b.s_b3, sockin->sin_addr.S_un.S_un_b.s_b4,
|
||
|
ntohs(sockin->sin_port),
|
||
|
initpkt->protocol_version,
|
||
|
initpkt->header.sendseq);
|
||
|
|
||
|
if (initpkt->protocol_version != XIRM_PROTOCOL_VERSION) {
|
||
|
XDBGTRC(MYMODNAME, "client version mismatch: %x %x");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ConnectReqLock();
|
||
|
connection_request.clientisn = initpkt->header.sendseq;
|
||
|
connection_request.clientaddr = *sockin;
|
||
|
memcpy(connection_request.clientname, initpkt->client_name, XIRM_MACHINE_NAME_MAXLEN-1);
|
||
|
ConnectReqUnlock();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void __cdecl main()
|
||
|
{
|
||
|
HANDLE thread;
|
||
|
DWORD tid;
|
||
|
|
||
|
InitializeListHead(&sendq);
|
||
|
|
||
|
// Initialize network stack
|
||
|
InitNet();
|
||
|
|
||
|
// Initialize input
|
||
|
InitInput();
|
||
|
|
||
|
// Create server listening thread
|
||
|
InitializeCriticalSection(&connection_request_lock);
|
||
|
thread = CreateThread(NULL, 0, ListenThreadProc, NULL, 0, &tid);
|
||
|
ASSERT(thread != NULL);
|
||
|
CloseHandle(thread);
|
||
|
|
||
|
// Check if the config file is present
|
||
|
if (!dosetup) {
|
||
|
if (!ReadConfig()) dosetup = TRUE;
|
||
|
}
|
||
|
|
||
|
if (dosetup) {
|
||
|
DoSetup();
|
||
|
} else {
|
||
|
// Server loop
|
||
|
WaitForClient();
|
||
|
}
|
||
|
|
||
|
RunServer();
|
||
|
Sleep(INFINITE);
|
||
|
}
|
||
|
|