Skip to content

Instantly share code, notes, and snippets.

@dead
Last active March 31, 2016 11:42
Show Gist options
  • Save dead/5c65902e16dc290fc8b63c2e1baf6b2f to your computer and use it in GitHub Desktop.
Save dead/5c65902e16dc290fc8b63c2e1baf6b2f to your computer and use it in GitHub Desktop.
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "LinkedBlockingQueue.h"
#include "Input.h"
#include <openssl/evp.h>
#include <openssl/aes.h>
static SOCKET inputSock = INVALID_SOCKET;
static int initialized;
static LINKED_BLOCKING_QUEUE packetQueue;
static PLT_THREAD inputSendThread;
static char* _aesKeyData;
static char* _aesIv;
#define MAX_INPUT_PACKET_SIZE 128
#define INPUT_STREAM_TIMEOUT_SEC 10
// Contains input stream packets
typedef struct _PACKET_HOLDER {
int packetLength;
union {
NV_KEYBOARD_PACKET keyboard;
NV_MOUSE_MOVE_PACKET mouseMove;
NV_MOUSE_BUTTON_PACKET mouseButton;
NV_CONTROLLER_PACKET controller;
NV_MULTI_CONTROLLER_PACKET multiController;
NV_SCROLL_PACKET scroll;
} packet;
LINKED_BLOCKING_QUEUE_ENTRY entry;
} PACKET_HOLDER, *PPACKET_HOLDER;
// Initializes the input stream
int initializeInputStream(char* aesKeyData, int aesKeyDataLength,
char* aesIv, int aesIvLength) {
if (aesKeyDataLength != 16) {
Limelog("Invalid key size.");
return -1;
}
_aesKeyData = (char*)malloc(aesKeyDataLength);
_aesIv = (char*)malloc(aesIvLength);
memcpy(_aesKeyData, aesKeyData, aesKeyDataLength);
memcpy(_aesIv, aesIv, aesIvLength);
LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
initialized = 1;
return 0;
}
int encryptData(const unsigned char* packet, size_t packetLen, char *encryptedBuffer, size_t *encryptedSize)
{
EVP_CIPHER_CTX *ctx;
int ret = -1;
int len;
int tagOffset = 0;
char aad[] = {};
printf("Initialize context\n");
if (!(ctx = EVP_CIPHER_CTX_new()))
return -1;
printf("Init alg\n");
if (ServerMajorVersion < 7)
ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL);
else
ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL);
if (ret != 1)
goto clean;
if (ServerMajorVersion >= 7) {
printf("Set iv len\n");
if ((ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL)) != 1)
goto clean;
tagOffset = 16;
}
printf("Set iv and key\n");
if ((ret = EVP_EncryptInit_ex(ctx, NULL, NULL, _aesKeyData, _aesIv)) != 1)
goto clean;
printf("Set aad\n");
if (ServerMajorVersion >= 7 && (ret = EVP_EncryptUpdate(ctx, NULL, &len, aad, 0)) != 1)
goto clean;
printf("Encrypt\n");
if ((ret = EVP_EncryptUpdate(ctx, encryptedBuffer + tagOffset, &len, packet, packetLen)) != 1)
goto clean;
*encryptedSize = len;
printf("Len1 %d\n", *encryptedSize);
printf("Final Encrypt\n");
if ((ret = EVP_EncryptFinal_ex(ctx, encryptedBuffer + tagOffset + len, &len)) != 1)
goto clean;
*encryptedSize += len;
if (ServerMajorVersion >= 7) {
printf("Set tag\n");
if ((ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, encryptedBuffer)) != 1)
goto clean;
*encryptedSize += tagOffset;
}
printf("Len %d\n", *encryptedSize);
clean:
EVP_CIPHER_CTX_free(ctx);
return ret;
}
// Destroys and cleans up the input stream
void destroyInputStream(void) {
PLINKED_BLOCKING_QUEUE_ENTRY entry, nextEntry;
entry = LbqDestroyLinkedBlockingQueue(&packetQueue);
while (entry != NULL) {
nextEntry = entry->flink;
// The entry is stored in the data buffer
free(entry->data);
entry = nextEntry;
}
initialized = 0;
}
// Checks if values are compatible with controller batching
static int checkDirs(short currentVal, short newVal, int* dir) {
if (currentVal == newVal) {
return 1;
}
// We want to send a new packet if we've now zeroed an axis
if (newVal == 0) {
return 0;
}
if (*dir == 0) {
if (newVal < currentVal) {
*dir = -1;
}
else {
*dir = 1;
}
}
else if (*dir == -1) {
return newVal < currentVal;
}
else if (newVal < currentVal) {
return 0;
}
return 1;
}
#define OAES_DATA_OFFSET 32
// Input thread proc
static void inputSendThreadProc(void* context) {
SOCK_RET err;
PPACKET_HOLDER holder;
char encryptedBuffer[MAX_INPUT_PACKET_SIZE];
size_t encryptedSize;
while (!PltIsThreadInterrupted(&inputSendThread)) {
int encryptedLengthPrefix;
err = LbqWaitForQueueElement(&packetQueue, (void**)&holder);
if (err != LBQ_SUCCESS) {
return;
}
// If it's a multi-controller packet we can do batching
if (holder->packet.multiController.header.packetType == htonl(PACKET_TYPE_MULTI_CONTROLLER)) {
PPACKET_HOLDER controllerBatchHolder;
PNV_MULTI_CONTROLLER_PACKET origPkt;
int dirs[6];
memset(dirs, 0, sizeof(dirs));
origPkt = &holder->packet.multiController;
for (;;) {
PNV_MULTI_CONTROLLER_PACKET newPkt;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a controller packet, we're done
if (controllerBatchHolder->packet.multiController.header.packetType != htonl(PACKET_TYPE_MULTI_CONTROLLER)) {
break;
}
// Check if it's able to be batched
newPkt = &controllerBatchHolder->packet.multiController;
if (newPkt->buttonFlags != origPkt->buttonFlags ||
newPkt->controllerNumber != origPkt->controllerNumber ||
!checkDirs(origPkt->leftTrigger, newPkt->leftTrigger, &dirs[0]) ||
!checkDirs(origPkt->rightTrigger, newPkt->rightTrigger, &dirs[1]) ||
!checkDirs(origPkt->leftStickX, newPkt->leftStickX, &dirs[2]) ||
!checkDirs(origPkt->leftStickY, newPkt->leftStickY, &dirs[3]) ||
!checkDirs(origPkt->rightStickX, newPkt->rightStickX, &dirs[4]) ||
!checkDirs(origPkt->rightStickY, newPkt->rightStickY, &dirs[5])) {
// Batching not allowed
break;
}
// Remove the batchable controller packet
if (LbqPollQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// Update the original packet
origPkt->leftTrigger = newPkt->leftTrigger;
origPkt->rightTrigger = newPkt->rightTrigger;
origPkt->leftStickX = newPkt->leftStickX;
origPkt->leftStickY = newPkt->leftStickY;
origPkt->rightStickX = newPkt->rightStickX;
origPkt->rightStickY = newPkt->rightStickY;
// Free the batched packet holder
free(controllerBatchHolder);
}
}
// If it's a mouse move packet, we can also do batching
else if (holder->packet.mouseMove.header.packetType == htonl(PACKET_TYPE_MOUSE_MOVE)) {
PPACKET_HOLDER mouseBatchHolder;
int totalDeltaX = (short)htons(holder->packet.mouseMove.deltaX);
int totalDeltaY = (short)htons(holder->packet.mouseMove.deltaY);
for (;;) {
int partialDeltaX;
int partialDeltaY;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a mouse move packet, we're done
if (mouseBatchHolder->packet.mouseMove.header.packetType != htonl(PACKET_TYPE_MOUSE_MOVE)) {
break;
}
partialDeltaX = (short)htons(mouseBatchHolder->packet.mouseMove.deltaX);
partialDeltaY = (short)htons(mouseBatchHolder->packet.mouseMove.deltaY);
// Check for overflow
if (partialDeltaX + totalDeltaX > INT16_MAX ||
partialDeltaX + totalDeltaX < INT16_MIN ||
partialDeltaY + totalDeltaY > INT16_MAX ||
partialDeltaY + totalDeltaY < INT16_MIN) {
// Total delta would overflow our 16-bit short
break;
}
// Remove the batchable mouse move packet
if (LbqPollQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) {
break;
}
totalDeltaX += partialDeltaX;
totalDeltaY += partialDeltaY;
// Free the batched packet holder
free(mouseBatchHolder);
}
// Update the original packet
holder->packet.mouseMove.deltaX = htons((short)totalDeltaX);
holder->packet.mouseMove.deltaY = htons((short)totalDeltaY);
}
encryptedSize = sizeof(encryptedBuffer);
err = encryptData((const unsigned char*)&holder->packet, holder->packetLength,
((unsigned char*)encryptedBuffer) + 4, &encryptedSize);
free(holder);
if (err != 1) {
Limelog("Input: Encryption failed: %d\n", (int)err);
ListenerCallbacks.connectionTerminated(err);
return;
}
// Overwrite the last 4 bytes before the encrypted data with the length so
// we can send the message all at once. GFE can choke if it gets the header
// before the rest of the message.
encryptedLengthPrefix = htonl((unsigned long)encryptedSize);
memcpy(&encryptedBuffer, &encryptedLengthPrefix, sizeof(encryptedLengthPrefix));
if (ServerMajorVersion == 7 && encryptedSize - sizeof(encryptedLengthPrefix) >= 32) {
printf("Update IV\n");
memcpy(_aesIv, encryptedBuffer + sizeof(encryptedLengthPrefix) - 16, 16);
}
if (ServerMajorVersion < 5) {
// Send the encrypted payload
err = send(inputSock, (const char*) &encryptedBuffer,
(int) (encryptedSize + sizeof(encryptedLengthPrefix)), 0);
if (err <= 0) {
Limelog("Input: send() failed: %d\n", (int) LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
else {
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*) &encryptedBuffer,
(int) (encryptedSize + sizeof(encryptedLengthPrefix)));
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(LastSocketError());
return;
}
}
}
}
// Begin the input stream
int startInputStream(void) {
int err;
// After Gen 5, we send input on the control stream
if (ServerMajorVersion < 5) {
inputSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
35043, INPUT_STREAM_TIMEOUT_SEC);
if (inputSock == INVALID_SOCKET) {
return LastSocketFail();
}
enableNoDelay(inputSock);
}
err = PltCreateThread(inputSendThreadProc, NULL, &inputSendThread);
if (err != 0) {
return err;
}
return err;
}
// Stops the input stream
int stopInputStream(void) {
// Signal the input send thread
LbqSignalQueueShutdown(&packetQueue);
PltInterruptThread(&inputSendThread);
if (inputSock != INVALID_SOCKET) {
shutdownTcpSocket(inputSock);
}
PltJoinThread(&inputSendThread);
PltCloseThread(&inputSendThread);
if (inputSock != INVALID_SOCKET) {
closeSocket(inputSock);
inputSock = INVALID_SOCKET;
}
return 0;
}
// Send a mouse move event to the streaming machine
int LiSendMouseMoveEvent(short deltaX, short deltaY) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_MOUSE_MOVE_PACKET);
holder->packet.mouseMove.header.packetType = htonl(PACKET_TYPE_MOUSE_MOVE);
holder->packet.mouseMove.magic = MOUSE_MOVE_MAGIC;
// On Gen 5 servers, the header code is incremented by one
if (ServerMajorVersion >= 5) {
holder->packet.mouseMove.magic++;
}
holder->packet.mouseMove.deltaX = htons(deltaX);
holder->packet.mouseMove.deltaY = htons(deltaY);
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a mouse button event to the streaming machine
int LiSendMouseButtonEvent(char action, int button) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_MOUSE_BUTTON_PACKET);
holder->packet.mouseButton.header.packetType = htonl(PACKET_TYPE_MOUSE_BUTTON);
holder->packet.mouseButton.action = action;
if (ServerMajorVersion >= 5) {
holder->packet.mouseButton.action++;
}
holder->packet.mouseButton.button = htonl(button);
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a key press event to the streaming machine
int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_KEYBOARD_PACKET);
holder->packet.keyboard.header.packetType = htonl(PACKET_TYPE_KEYBOARD);
holder->packet.keyboard.keyAction = keyAction;
holder->packet.keyboard.zero1 = 0;
holder->packet.keyboard.keyCode = keyCode;
holder->packet.keyboard.modifiers = modifiers;
holder->packet.keyboard.zero2 = 0;
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
static int sendControllerEventInternal(short controllerNumber, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
if (ServerMajorVersion == 3) {
// Generation 3 servers don't support multiple controllers so we send
// the legacy packet
holder->packetLength = sizeof(NV_CONTROLLER_PACKET);
holder->packet.controller.header.packetType = htonl(PACKET_TYPE_CONTROLLER);
holder->packet.controller.headerA = C_HEADER_A;
holder->packet.controller.headerB = C_HEADER_B;
holder->packet.controller.buttonFlags = buttonFlags;
holder->packet.controller.leftTrigger = leftTrigger;
holder->packet.controller.rightTrigger = rightTrigger;
holder->packet.controller.leftStickX = leftStickX;
holder->packet.controller.leftStickY = leftStickY;
holder->packet.controller.rightStickX = rightStickX;
holder->packet.controller.rightStickY = rightStickY;
holder->packet.controller.tailA = C_TAIL_A;
holder->packet.controller.tailB = C_TAIL_B;
}
else {
// Generation 4+ servers support passing the controller number
holder->packetLength = sizeof(NV_MULTI_CONTROLLER_PACKET);
holder->packet.multiController.header.packetType = htonl(PACKET_TYPE_MULTI_CONTROLLER);
holder->packet.multiController.headerA = MC_HEADER_A;
// On Gen 5 servers, the header code is decremented by one
if (ServerMajorVersion >= 5) {
holder->packet.multiController.headerA--;
}
holder->packet.multiController.headerB = MC_HEADER_B;
holder->packet.multiController.controllerNumber = controllerNumber;
holder->packet.multiController.midA = MC_ACTIVE_CONTROLLER_FLAGS;
holder->packet.multiController.midB = MC_MID_B;
holder->packet.multiController.buttonFlags = buttonFlags;
holder->packet.multiController.leftTrigger = leftTrigger;
holder->packet.multiController.rightTrigger = rightTrigger;
holder->packet.multiController.leftStickX = leftStickX;
holder->packet.multiController.leftStickY = leftStickY;
holder->packet.multiController.rightStickX = rightStickX;
holder->packet.multiController.rightStickY = rightStickY;
holder->packet.multiController.tailA = MC_TAIL_A;
holder->packet.multiController.tailB = MC_TAIL_B;
}
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
// Send a controller event to the streaming machine
int LiSendControllerEvent(short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(0, buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a controller event to the streaming machine
int LiSendMultiControllerEvent(short controllerNumber, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(controllerNumber, buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a scroll event to the streaming machine
int LiSendScrollEvent(signed char scrollClicks) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = malloc(sizeof(*holder));
if (holder == NULL) {
return -1;
}
holder->packetLength = sizeof(NV_SCROLL_PACKET);
holder->packet.scroll.header.packetType = htonl(PACKET_TYPE_SCROLL);
holder->packet.scroll.magicA = MAGIC_A;
// On Gen 5 servers, the header code is incremented by one
if (ServerMajorVersion >= 5) {
holder->packet.scroll.magicA++;
}
holder->packet.scroll.zero1 = 0;
holder->packet.scroll.zero2 = 0;
holder->packet.scroll.scrollAmt1 = htons(scrollClicks * 120);
holder->packet.scroll.scrollAmt2 = holder->packet.scroll.scrollAmt1;
holder->packet.scroll.zero3 = 0;
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) {
free(holder);
}
return err;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment