Files
XPlaneConnectCSP/C/src/xplaneConnect.c
Jason Watkins 1a8078af87 Various changes to improve consistency and readability of low level UDP functions.
- Tweaked variable names to be more concise.
 - Moved variable declarations from top of function to point of use.
 - Replaced memcpy with strncpy where appropriate.
 - Removed socket reuse options on *nix builds.
 - Improved error reporting.
 - Most failures when opening and closing sockets now cause the client to die.
 - sendUDP now ignores the length byte.
2015-04-17 10:55:59 -07:00

693 lines
16 KiB
C
Executable File

//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the
//National Aeronautics and Space Administration. All Rights Reserved.
//
//DISCLAIMERS
// No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
// EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY
// THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED,
// WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
// ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS,
// HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT
// SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING
// THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
//
// Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES
// GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF
// RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES
// OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING
// FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE
// UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT,
// TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE
// IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
// X-Plane Connect Client
//
// DESCRIPTION
// Communicates with the XPC plugin to facilitate controling and gathering data from X-Plane.
//
// INSTRUCTIONS
// See Readme.md in the root of this repository or the wiki hosted on GitHub at
// https://github.com/nasa/XPlaneConnect/wiki for requirements, installation instructions,
// and detailed documentation.
//
// CONTACT
// For questions email Christopher Teubert (christopher.a.teubert@nasa.gov)
//
// CONTRIBUTORS
// CT: Christopher Teubert (christopher.a.teubert@nasa.gov)
// JW: Jason Watkins (jason.w.watkins@nasa.gov)
#include "xplaneConnect.h"
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#ifdef _WIN32 /* WIN32 SYSTEM */
#include <WS2tcpip.h>
// From http://www.c-plusplus.de/forum/109539-full
void usleep(__int64 usec)
{
HANDLE timer;
LARGE_INTEGER ft;
ft.QuadPart = -(10*usec); // Convert to 100 nanosecond interval, negative value indicates relative time
timer = CreateWaitableTimer(NULL, TRUE, NULL);
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
}
#endif
void printError(char *functionName, char *errorMessage);
short sendRequest(XPCSocket recfd, char DREFArray[][100], short DREFSizes[], short listLength);
void printError(char *functionName, char *errorMessage)
{
printf("[%s] ERROR: %s\n", functionName, errorMessage);
}
/*****************************************************************************/
/**** Low Level UDP functions ****/
/*****************************************************************************/
XPCSocket openUDP(unsigned short port, const char *xpIP, unsigned short xpPort)
{
XPCSocket sock;
// Setup Port
struct sockaddr_in recvaddr;
recvaddr.sin_family = AF_INET;
recvaddr.sin_addr.s_addr = INADDR_ANY;
recvaddr.sin_port = htons(port);
// Set X-Plane Port and IP
if (strcmp(xpIP, "localhost") == 0)
{
xpIP = "127.0.0.1";
}
strncpy(sock.xpIP, xpIP, 16);
sock.xpPort = xpPort == 0 ? 49009 : xpPort;
#ifdef _WIN32
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printError("OpenUDP", "WSAStartup failed");
exit(EXIT_FAILURE);
}
#endif
if ((sock.sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
{
printError("OpenUDP", "Socket creation failed");
exit(EXIT_FAILURE);
}
if (bind(sock.sock, (struct sockaddr*)&recvaddr, sizeof(recvaddr)) == -1)
{
printError("OpenUDP", "Socket bind failed");
exit(EXIT_FAILURE);
}
#ifdef _WIN32
DWORD timeout = 1; // Minimum socket timeout in Windows is 1ms
#else
struct timeval timeout;
tv.tv_sec = 0;
tv.tv_usec = 500;
#endif
if (setsockopt(sock.sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0)
{
printError("OpenUDP", "Failed to set timeout");
}
return sock;
}
void closeUDP(XPCSocket socketNumber)
{
#ifdef _WIN32
int result = closesocket(socketNumber.sock);
#else
int result = close(socketNumber.sock);
#endif
if (result < 0)
{
printError("closeUDP", "Failed to close socket");
exit(EXIT_FAILURE);
}
}
int sendUDP(XPCSocket sock, char buffer[], int len)
{
// Preconditions
if (len <= 0)
{
printError("sendUDP", "Message length must be positive.");
return -1;
}
// Set up destination address
struct sockaddr_in dst;
dst.sin_family = AF_INET;
dst.sin_port = htons(sock.xpPort);
inet_pton(AF_INET, sock.xpIP, &dst.sin_addr.s_addr);
int result = sendto(sock.sock, buffer, len, 0, (const struct sockaddr*)&dst, sizeof(dst));
if (result < 0)
{
printError("sendUDP", "Send operation failed.");
return -2;
}
if (result < len)
{
printError("sendUDP", "Unexpected number of bytes sent.");
}
return result;
}
int readUDP(XPCSocket sock, char buffer[], int len, struct sockaddr* recvaddr)
{
socklen_t recvaddrlen = sizeof(*recvaddr);
#ifdef _WIN32
// Windows readUDP needs the select command- minimum timeout is 1ms.
// Without this playback becomes choppy
// Definitions
FD_SET stReadFDS;
FD_SET stExceptFDS;
// Setup for Select
FD_ZERO(&stReadFDS);
FD_SET(sock.sock, &stReadFDS);
FD_ZERO(&stExceptFDS);
FD_SET(sock.sock, &stExceptFDS);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 250;
// Select Command
int status = select(-1, &stReadFDS, (FD_SET*)0, &stExceptFDS, &tv);
if (status < 0)
{
printError("readUDP", "Select command error");
return -1;
}
if (status == 0)
{
// No data
return 0;
}
status = recvfrom(sock.sock, buffer, len, 0, (struct sockaddr*)&recvaddr, &recvaddrlen);
#else
// For apple or linux-just read - will timeout in 0.5 ms
status = (int)recvfrom(sock.sock, dataRef, len, 0, recvaddr, &recvaddrlen);
#endif
if (status < 0)
{
printError("readUDP", "Error reading socket");
}
return status;
}
/*****************************************************************************/
/**** End Low Level UDP functions ****/
/*****************************************************************************/
short sendDATA(XPCSocket recfd, float dataRef[][9], unsigned short rows)
{
int i;
unsigned short length = rows*9*sizeof(float) + 5;
char message[5000];
unsigned short step = 9*sizeof(float);
strncpy(message,"DATA",4);
for (i=0;i<rows;i++)
{
message[5+i*step] = (char) dataRef[i][0];
message[6+i*step] = (char) 0;
message[7+i*step] = (char) 0;
message[8+i*step] = (char) 0;
memcpy(&message[9+i*step], &dataRef[i][1], 8*sizeof(float));
}
sendUDP(recfd, message, length);
return 0;
}
short sendDREF(XPCSocket recfd, const char *dataRef, unsigned short length_of_DREF, float *values, unsigned short number_of_values) {
char message[5000];
int length = 7+length_of_DREF+number_of_values*sizeof(float);
//HEADER
strncpy(message,"DREF",4);
//DREF
message[5] = (char) length_of_DREF;
memcpy(&message[6],dataRef,length_of_DREF);
//VALUES
message[length_of_DREF+6] = (char) number_of_values;
memcpy(&message[length_of_DREF+7],&values[0],sizeof(float)*number_of_values);
sendUDP(recfd, message, length);
return 0;
}
short sendRequest(XPCSocket recfd, char DREFArray[][100], short DREFSizes[], short listLength)
{
int i;
char message[5000]={0};
int length = 6;
//HEADER
strncpy(message,"GETD",4);
message[5] = (char) listLength; //Number of Values
//The rest
for (i=0; i < listLength; i++)
{
message[length] = (char) DREFSizes[i];
memcpy(&message[length+1],DREFArray[i],message[length]);
length += message[length] + 1;
}
sendUDP(recfd, message, length);
return 0;
}
short requestDREF(XPCSocket sendfd, XPCSocket recfd, char DREFArray[][100], short DREFSizes[], short listLength, float *resultArray[], short arraySizes[])
{
int i;
sendRequest(sendfd, DREFArray, DREFSizes, listLength);
for (i=0; i<80; i++)
{
int size = readDREF(recfd, resultArray, arraySizes);
if (size != -1) // Received input
{
return size;
}
}
return -1;
}
short pauseSim(XPCSocket recfd, short pause)
{
char message[7];
//Preconditions
if (pause != 0 && pause != 1)
{
return errorReport("pauseSim", "Please set pause to 0 or 1");
}
strncpy(message,"SIMU",4);
message[5] = (char) pause;
message[6] = 0;
sendUDP(recfd, message,6);
return 0;
}
short setCONN(XPCSocket recfd, unsigned short recPort)
{
char message[10] = {0};
int length;
strncpy(message,"CONN",4);
memcpy(&message[5],&recPort,sizeof(recPort));
length = 5 + sizeof(recPort);
sendUDP(recfd,message, length);
return 0;
}
short sendPOSI(XPCSocket recfd, short ACNum, short numArgs, float valueArray[])
{
char message[40] = {0};
int i;
short position = 6;
// Input Verification
if (numArgs < 1)
{
return errorReport("POSI", "Must have atleast one argument");
}
// Header
strncpy(message,"POSI",4);
// Aircraft
message[5] = (char) ACNum;
// States
for (i=0; i<7; i++)
{
float val = -998.5;
if ( i < numArgs )
{
val = valueArray[i];
}
memcpy(&message[position],&val,sizeof(float));
position += sizeof(float);
}
sendUDP(recfd, message, position);
return 0;
}
short sendCTRL(XPCSocket recfd, short numArgs, float valueArray[])
{
return sendpCTRL(recfd, numArgs, valueArray, 0);
}
short sendpCTRL(XPCSocket recfd, short numArgs, float valueArray[], char acNum)
{
char message[27] = { 0 };
int i;
short position = 5;
// Input Verification
if (numArgs < 1)
{
return errorReport("CTRL", "Must have atleast one argument");
}
// Header
strncpy(message,"CTRL",4);
// States
for (i=0;i<6;i++)
{
float val = -998.5;
if (i<numArgs)
{
val = valueArray[i];
}
if (i==4) // Integer-gear
{
message[position] = (short int) val;
position += 1;
}
else // float
{
// Float Values
memcpy(&message[position],&val,sizeof(float));
position += sizeof(float);
}
}
message[position] = acNum;
sendUDP(recfd, message, 27);
return 0;
}
short sendTEXT(XPCSocket sendfd, char* msg, int x, int y)
{
char buf[269] = { 0 };
size_t msgLen = strnlen(msg, 255);
size_t len = 14 + msgLen;
strcpy(buf, "TEXT");
memcpy(buf + 5, &x, sizeof(int));
memcpy(buf + 9, &y, sizeof(int));
buf[13] = msgLen;
strncpy(buf + 14, msg, msgLen);
sendUDP(sendfd, buf, len);
return 0;
}
short sendWYPT(XPCSocket sendfd, WYPT_OP op, float points[], int numPoints)
{
char buf[255] = "WYPT";
//Preconditions
//Validate operation
if (op < xpc_WYPT_ADD || op > xpc_WYPT_CLR)
{
return -1;
}
//Validate number of points
else if (numPoints > 19)
{
return -2;
}
//Everything checks out; send the message
else
{
buf[5] = op;
buf[6] = numPoints;
size_t len = sizeof(float) * 3 * numPoints;
memcpy(buf + 7, points, len);
sendUDP(sendfd, buf, len + 7);
return 0;
}
}
//READ
//----------------------------------------
short readDATA(XPCSocket recfd, float dataRef[][9])
{
char buf[5000];
int buflen = readUDP(recfd,buf, NULL);
if (buf[0]!= '\0')
{
return parseDATA(buf, buflen, dataRef);
}
return -1;
}
short readRequest(XPCSocket recfd, float *dataRef[], short arraySizes[], struct sockaddr *recvaddr)
{
char buf[5000];
readUDP(recfd,buf, recvaddr);
if (buf[0]!= '\0')
{
return parseRequest(buf, dataRef, arraySizes);
}
return -1;
}
short readDREF(XPCSocket recfd, float *resultArray[], short arraySizes[])
{
char buf[5000] = {0};
readUDP(recfd,buf, NULL);
if (buf[0]!= '\0')
{
return parseRequest(buf, resultArray, arraySizes);
}
return -1;
}
short readPOSI(XPCSocket recfd, float resultArray[], int arraySize, float *gear)
{
char buf[5000] = {0};
readUDP(recfd,buf, NULL);
if (buf[0]!= '\0') // Buffer is not empty
{
return parsePOSI(buf, resultArray, arraySize, gear);
}
return -1;
}
xpcCtrl readCTRL(XPCSocket recfd)
{
xpcCtrl result;
char buf[5000] = { 0 };
readUDP(recfd, buf, NULL);
if (buf[0] != '\0') // Buffer is not empty
{
result = parseCTRL(buf);
}
else
{
result.aircraft = -1;
}
return result;
}
//PARSE
//---------------------
short parseDATA(const char my_message[], short messageLength, float dataRef[][9])
{
int i, j;
unsigned short totalColumns = ((messageLength-5)/36);
float tmp;
float data[20][9];
// Input Validation
for (i=0;i<totalColumns;i++)
{
data[i][0] = my_message[5+36*i];
for (j=1;j<9;j++)
{
memcpy(&tmp,&my_message[5]+4*j+36*i,4);
data[i][j] = tmp;
}
}
memcpy(dataRef,data,sizeof(data));
return totalColumns;
}
short parseDREF(const char my_message[], char *dataRef, unsigned short *length_of_DREF, float *values, unsigned short *number_of_values)
{
*length_of_DREF = (unsigned short) my_message[5];
memcpy(dataRef,&(my_message[6]),*length_of_DREF);
*number_of_values = (unsigned short) my_message[6+*length_of_DREF];
memcpy(values,&my_message[*length_of_DREF+7],*number_of_values*sizeof(float));
return 0;
}
int parseGETD(const char my_message[], char *DREFArray[], int DREFSizes[])
{
int i;
int listLength = my_message[5];
int counter = 6;
for (i=0; i<listLength;i++)
{
DREFSizes[i] = my_message[counter];
memcpy(DREFArray[i],&my_message[counter+1],DREFSizes[i]);
counter += DREFSizes[i]+1;
}
return listLength;
}
short parseRequest(const char my_message[], float *resultArray[], short arraySizes[])
{
int i;
short count = my_message[5];
short place = 6;
for (i=0; i<count; i++)
{
arraySizes[i] = my_message[place];
//if (resultArray[i] != NULL)
//{
// free(resultArray[i]);
//}
resultArray[i] = malloc(arraySizes[i]*sizeof(float));
memcpy(resultArray[i],&my_message[place + 1],arraySizes[i]*sizeof(float));
place += 1 + arraySizes[i]*sizeof(float);
}
return count;
}
short parsePOSI(const char my_message[], float resultArray[], int arraySize, float *gear)
{
int i;
// Input Validation
if (arraySize < 1) return -1;
memcpy(gear,&my_message[30],4);
for (i=0; (i<arraySize && i < 6); i++)
{
memcpy(&resultArray[i],&my_message[i*4+6],4);
}
return my_message[5]; // Aircraft
}
xpcCtrl parseCTRL(const char data[])
{
xpcCtrl result;
unsigned char len = data[4];
//Preconditions
//Validate message prefix to ensure we are looking at the right kind of packet.
if (strncmp(data, "CTRL", 4) != 0)
{
result.aircraft = -1;
}
//Legacy packets that don't specify an aircraft number should be 22 bytes long.
//Packets specifying an A/C num should be 24 bytes.
else if (len != 26 && len != 27)
{
result.aircraft = -1;
}
//Everything checks out, so we can skip over the header and copy the raw data
//into the struct.
else
{
//NOTE: It's tempting to just do a single memcpy here, but we can't do that because the
// compiler is allowed to add padding to the struct type.
result.pitch = *((float*)(data + 5));
result.roll = *((float*)(data + 9));
result.yaw = *((float*)(data + 13));
result.throttle = *((float*)(data + 17));
result.gear = data[21];
result.flaps = *((float*)(data + 22));
if (len == 27)
{
result.aircraft = data[26];
}
}
return result;
}
xpcWypt parseWYPT(const char data[])
{
xpcWypt result;
unsigned char len = data[4];
//Preconditions
//Validate message prefix to ensure we are looking at the right kind of packet.
if (strncmp(data, "WYPT", 4) != 0)
{
result.op = -1;
}
//Validate operation
else if (data[5] < xpc_WYPT_ADD || data[5] > xpc_WYPT_CLR)
{
result.op = -1;
}
//Validate number of points
else if (data[6] > 19)
{
result.op = -2;
}
//Everything checks out; copy the points into result
else
{
result.op = data[5];
result.numPoints = data[6];
char* ptr = data + 7;
for (size_t i = 0; i < result.numPoints; ++i)
{
result.points[i].latitude = *((float*)ptr);
result.points[i].longitude = *((float*)(ptr + 4));
result.points[i].altitude = *((float*)(ptr + 8));
ptr += 12;
}
}
return result;
}