Initial import of project to git

This commit is contained in:
Geoffrey McRae 2017-10-19 15:15:49 +11:00
commit 202985097e
12 changed files with 1570 additions and 0 deletions

42
client/KVMGFXHeader.h Normal file
View File

@ -0,0 +1,42 @@
#define KVMGFX_HEADER_MAGIC "[[KVMGFXHeader]]"
typedef enum FrameType
{
FRAME_TYPE_INVALID ,
FRAME_TYPE_ARGB , // ABGR interleaved: A,R,G,B 32bpp
FRAME_TYPE_RGB , // RGB interleaved : R,G,B 24bpp
FRAME_TYPE_YUV420P , // YUV420 12bpp
FRAME_TYPE_ARGB10 , // rgb 10 bit packed, a2 b10 r10
FRAME_TYPE_XOR , // xor of the previous frame: R, G, B
FRAME_TYPE_MAX , // sentinel value
} FrameType;
typedef enum FrameComp
{
FRAME_COMP_NONE , // no compression
FRAME_COMP_BLACK_RLE , // basic run length encoding of black pixels for XOR mode
FRAME_COMP_MAX , // sentinel valule
} FrameComp;
struct KVMGFXHeader
{
char magic[sizeof(KVMGFX_HEADER_MAGIC)];
uint32_t version; // version of this structure
FrameType frameType; // the frame type
FrameComp compType; // frame compression mode
uint32_t width; // the width
uint32_t height; // the height
uint32_t stride; // the row stride
uint64_t frames; // total frame count
uint64_t clientFrame; // current client frame
uint32_t dataLen; // total lengh of the data after this header
};
#pragma pack(push,1)
struct RLEHeader
{
uint8_t magic[3];
uint16_t length;
};
#pragma pack(pop)

13
client/Makefile Normal file
View File

@ -0,0 +1,13 @@
CFLAGS=-g -Og -std=gnu99 -march=native -Wall -Werror
LDFLAGS=-lrt -lGL
CFLAGS+=`pkg-config --cflags sdl2`
LDFLAGS+=`pkg-config --libs sdl2`
CFLAGS+=`pkg-config --cflags libssl openssl`
LDFLAGS+=`pkg-config --libs libssl openssl`
CFLAGS+=`pkg-config --cflags spice-protocol`
all:
gcc ${CFLAGS} -o main main.c spice.c ${LDFLAGS}

16
client/debug.h Normal file
View File

@ -0,0 +1,16 @@
#ifdef DEBUG
#define DEBUG_PRINT(type, fmt, args...) do {fprintf(stderr, type " %-30s : %-5u | " fmt "\n", __FUNCTION__, __LINE__, ##args);} while (0)
#else
#define DEBUG_PRINT(type, fmt, args...) do {} while(0)
#endif
#define DEBUG_INFO(fmt, args...) DEBUG_PRINT("[I]", fmt, ##args)
#define DEBUG_WARN(fmt, args...) DEBUG_PRINT("[W]", fmt, ##args)
#define DEBUG_ERROR(fmt, args...) DEBUG_PRINT("[E]", fmt, ##args)
#define DEBUG_FIXME(fmt, args...) DEBUG_PRINT("[F]", fmt, ##args)
#ifdef DEBUG_SPICE
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)
#else
#define DEBUG_PROTO(fmt, args...) do {} while(0)
#endif

3
client/i3start.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
i3-msg 'workspace 10; move workspace to DP-0; exec /home/geoff/Projects/kvm-shm/main'

37
client/kb.h Normal file
View File

@ -0,0 +1,37 @@
static uint32_t usb_to_ps2[] =
{
0x000000, 0x000000, 0x000000, 0x000000, 0x00001e, 0x000030, 0x00002e,
0x000020, 0x000012, 0x000021, 0x000022, 0x000023, 0x000017, 0x000024,
0x000025, 0x000026, 0x000032, 0x000031, 0x000018, 0x000019, 0x000010,
0x000013, 0x00001f, 0x000014, 0x000016, 0x00002f, 0x000011, 0x00002d,
0x000015, 0x00002c, 0x000002, 0x000003, 0x000004, 0x000005, 0x000006,
0x000007, 0x000008, 0x000009, 0x00000a, 0x00000b, 0x00001c, 0x000001,
0x00000e, 0x00000f, 0x000039, 0x00000c, 0x00000d, 0x00001a, 0x00001b,
0x00002b, 0x00002b, 0x000027, 0x000028, 0x000029, 0x000033, 0x000034,
0x000035, 0x00003a, 0x00003b, 0x00003c, 0x00003d, 0x00003e, 0x00003f,
0x000040, 0x000041, 0x000042, 0x000043, 0x000044, 0x000057, 0x000058,
0x00e037, 0x000046, 0x00e046, 0x00e052, 0x00e047, 0x00e049, 0x00e053,
0x00e04f, 0x00e051, 0x00e04d, 0x00e04b, 0x00e050, 0x00e048, 0x000045,
0x00e035, 0x000037, 0x00004a, 0x00004e, 0x00e01c, 0x00004f, 0x000050,
0x000051, 0x00004b, 0x00004c, 0x00004d, 0x000047, 0x000048, 0x000049,
0x000052, 0x000053, 0x000056, 0x00e05d, 0x000000, 0x000059, 0x00005d,
0x00005e, 0x00005f, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x00007e, 0x000000, 0x000073, 0x000070, 0x00007d, 0x000079, 0x00007b,
0x00005c, 0x000000, 0x000000, 0x000000, 0x0000f2, 0x0000f1, 0x000078,
0x000077, 0x000076, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x00001d, 0x00002a, 0x000038, 0x00e05b, 0x00e01d, 0x000036, 0x00e038,
0x00e05c
};

BIN
client/main Executable file

Binary file not shown.

553
client/main.c Normal file
View File

@ -0,0 +1,553 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <GL/gl.h>
#define DEBUG
#include "debug.h"
#include "KVMGFXHeader.h"
#include "spice.h"
#include "kb.h"
#define MAP_SIZE (16*1024*1024)
#define COPY_THREADS 4
typedef void (*CompFunc)(uint8_t * dst, const uint8_t * src, const size_t len);
typedef void (*DrawFunc)(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src);
typedef struct
{
SDL_mutex * mutex;
SDL_cond * cond;
SDL_Thread * thread;
bool rdy;
void *dst;
const void * src;
size_t len;
}
CopyJob;
struct KVMGFXState
{
bool running;
SDL_Window * window;
SDL_Renderer * renderer;
struct KVMGFXHeader * shm;
SDL_sem * cpySem;
SDL_Thread * cpyThreads[COPY_THREADS];
CopyJob cpyJobs [COPY_THREADS];
};
struct KVMGFXState state;
int copyThread(void * arg)
{
CopyJob * job = (CopyJob *)arg;
while(state.running)
{
SDL_LockMutex(job->mutex);
while(!job->rdy)
SDL_CondWait(job->cond, job->mutex);
job->rdy = false;
SDL_UnlockMutex(job->mutex);
memcpy(job->dst, job->src, job->len);
// return a lock to the pool
SDL_SemPost(state.cpySem);
}
return 0;
}
bool startCopyThreads()
{
state.cpySem = SDL_CreateSemaphore(COPY_THREADS);
for(int i = 0; i < COPY_THREADS; ++i)
{
// take a lock from the pool
SDL_SemWait(state.cpySem);
CopyJob * job = &state.cpyJobs[i];
job->mutex = SDL_CreateMutex();
job->cond = SDL_CreateCond();
job->rdy = false;
job->dst = NULL;
job->src = NULL;
job->len = 0;
job->thread = SDL_CreateThread(
copyThread, "copyThread", &state.cpyJobs[i]);
}
return true;
}
void stopCopyThreads()
{
}
void compFunc_NONE(uint8_t * dst, const uint8_t * src, const size_t len)
{
const size_t part = len / COPY_THREADS;
for(int i = 0; i < COPY_THREADS; ++i)
{
CopyJob * job = &state.cpyJobs[i];
job->dst = dst + i * part;
job->src = src + i * part;
job->len = part;
job->rdy = true;
SDL_CondSignal(job->cond);
}
// wait for the threads to complete
for(int i = 0; i < COPY_THREADS; ++i)
SDL_SemWait(state.cpySem);
}
void compFunc_BLACK_RLE(uint8_t * dst, const uint8_t * src, const size_t len)
{
const size_t pixels = len / 3;
for(size_t i = 0; i < pixels;)
{
if (!src[0] && !src[1] && !src[2])
{
struct RLEHeader * h = (struct RLEHeader *)src;
dst += h->length * 3;
i += h->length;
src += sizeof(struct RLEHeader);
continue;
}
memcpy(dst, src, 3);
dst += 3;
src += 3;
++i;
}
}
inline bool areFormatsSame(const struct KVMGFXHeader s1, const struct KVMGFXHeader s2)
{
return
(s1.version == s2.version ) &&
(s1.frameType == s2.frameType) &&
(s1.compType == s2.compType ) &&
(s1.width == s2.width ) &&
(s1.height == s2.height );
}
void drawFunc_ARGB10(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
{
SDL_UpdateTexture(texture, NULL, src, state.shm->stride * 4);
SDL_RenderClear(state.renderer);
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
SDL_RenderPresent(state.renderer);
}
void drawFunc_ARGB(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
{
compFunc(dst, src, state.shm->height * state.shm->stride * 4);
SDL_UnlockTexture(texture);
SDL_RenderClear(state.renderer);
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
SDL_RenderPresent(state.renderer);
}
void drawFunc_RGB(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
{
compFunc(dst, src, state.shm->height * state.shm->stride * 3);
SDL_UnlockTexture(texture);
SDL_RenderClear(state.renderer);
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
SDL_RenderPresent(state.renderer);
}
void drawFunc_XOR(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
{
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_XOR);
compFunc(dst, src, state.shm->height * state.shm->stride * 3);
SDL_UnlockTexture(texture);
if (state.shm->frames == 1)
SDL_RenderClear(state.renderer);
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
SDL_RenderPresent(state.renderer);
// clear the buffer for the next frame
memset(dst, 0, state.shm->height * state.shm->stride * 3);
}
void drawFunc_YUV420P(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
{
const unsigned int pixels = state.shm->width * state.shm->height;
SDL_UpdateYUVTexture(texture, NULL,
src , state.shm->stride,
src + pixels , state.shm->stride / 2,
src + pixels + pixels / 4, state.shm->stride / 2
);
SDL_RenderClear(state.renderer);
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
SDL_RenderPresent(state.renderer);
}
int renderThread(void * unused)
{
bool startup = true;
struct KVMGFXHeader format;
SDL_Texture *texture = NULL;
uint8_t *pixels = (uint8_t*)(state.shm + 1);
uint8_t *texPixels = NULL;
DrawFunc drawFunc = NULL;
CompFunc compFunc = NULL;
format.version = 1;
format.frameType = FRAME_TYPE_INVALID;
format.width = 0;
format.height = 0;
format.stride = 0;
format.frames = 0;
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
DEBUG_ERROR("SDL_Init Failed");
return -1;
}
state.window = SDL_CreateWindow("KVM-GFX Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 100, 100, SDL_WINDOW_BORDERLESS);
if (!state.window)
{
DEBUG_ERROR("failed to create window");
return -1;
}
state.renderer = SDL_CreateRenderer(state.window , -1, 0);
if (!state.renderer)
{
DEBUG_ERROR("failed to create window");
return -1;
}
startCopyThreads();
while(state.running)
{
// ensure the header magic is valid, this will help prevent crash out when the memory hasn't yet been initialized
if (memcmp(state.shm->magic, KVMGFX_HEADER_MAGIC, sizeof(KVMGFX_HEADER_MAGIC)) != 0)
continue;
// if the frame count hasn't changed, we don't do anything
if (!startup && format.frames == state.shm->frames)
{
if (!state.running)
break;
usleep(100);
continue;
}
startup = false;
// if the format is invalid or it has changed
if (format.frameType == FRAME_TYPE_INVALID || !areFormatsSame(format, *state.shm))
{
if (texture)
{
SDL_DestroyTexture(texture);
texture = NULL;
}
Uint32 sdlFormat;
switch(state.shm->frameType)
{
case FRAME_TYPE_ARGB : sdlFormat = SDL_PIXELFORMAT_ARGB8888 ; drawFunc = drawFunc_ARGB ; break;
case FRAME_TYPE_RGB : sdlFormat = SDL_PIXELFORMAT_RGB24 ; drawFunc = drawFunc_RGB ; break;
case FRAME_TYPE_YUV420P: sdlFormat = SDL_PIXELFORMAT_YV12 ; drawFunc = drawFunc_YUV420P; break;
case FRAME_TYPE_ARGB10 : sdlFormat = SDL_PIXELFORMAT_ARGB2101010; drawFunc = drawFunc_ARGB10 ; break;
case FRAME_TYPE_XOR : sdlFormat = SDL_PIXELFORMAT_RGB24 ; drawFunc = drawFunc_XOR ; break;
default:
format.frameType = FRAME_TYPE_INVALID;
continue;
}
switch(state.shm->compType)
{
case FRAME_COMP_NONE : compFunc = compFunc_NONE ; break;
case FRAME_COMP_BLACK_RLE: compFunc = compFunc_BLACK_RLE; break;
default:
format.frameType = FRAME_TYPE_INVALID;
continue;
}
// update the window size and create the render texture
SDL_SetWindowSize(state.window, state.shm->width, state.shm->height);
SDL_SetWindowPosition(state.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
texture = SDL_CreateTexture(state.renderer, sdlFormat, SDL_TEXTUREACCESS_STREAMING, state.shm->width, state.shm->height);
// this doesnt "lock" anything, pre-fetch the pointers for later use
int unused;
SDL_LockTexture(texture, NULL, (void**)&texPixels, &unused);
memcpy(&format, state.shm, sizeof(format));
}
if (format.frames != state.shm->frames - 1)
DEBUG_INFO("dropped %lu", state.shm->frames - format.frames);
format.frames = state.shm->frames;
glDisable(GL_COLOR_LOGIC_OP);
drawFunc(compFunc, texture, texPixels, pixels);
state.shm->clientFrame = format.frames;
// dont waste CPU, frames don't come that fast!
usleep(10);
}
SDL_DestroyTexture(texture);
stopCopyThreads();
return 0;
}
int spiceThread(void * arg)
{
while(state.running)
if (!spice_process())
{
state.running = false;
DEBUG_ERROR("Failed to process spice messages");
break;
}
spice_disconnect();
return 0;
}
static inline const uint32_t mapScancode(SDL_Scancode scancode)
{
uint32_t ps2;
if (scancode > (sizeof(usb_to_ps2) / sizeof(uint32_t)) || (ps2 = usb_to_ps2[scancode]) == 0)
{
DEBUG_WARN("Unable to map USB scan code: %x\n", scancode);
return 0;
}
return ps2;
}
int eventThread(void * arg)
{
int mouseX = 0;
int mouseY = 0;
// default to server mode
bool serverMode = true;
spice_mouse_mode(true);
SDL_SetRelativeMouseMode(true);
// work around SDL_ShowCursor being non functional
SDL_Cursor *cursor;
int32_t cursorData[2] = {0, 0};
cursor = SDL_CreateCursor((uint8_t*)cursorData, (uint8_t*)cursorData, 8, 8, 4, 4);
SDL_SetCursor(cursor);
SDL_ShowCursor(SDL_DISABLE);
// ensure mouse acceleration is identical in server mode
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
SDL_Event event;
while(state.running)
{
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
state.running = false;
break;
case SDL_KEYDOWN:
{
SDL_Scancode sc = event.key.keysym.scancode;
if (sc == SDL_SCANCODE_SCROLLLOCK)
{
serverMode = !serverMode;
spice_mouse_mode(serverMode);
SDL_SetRelativeMouseMode(serverMode);
break;
}
uint32_t scancode = mapScancode(sc);
if (scancode == 0)
break;
spice_key_down(scancode);
break;
}
case SDL_KEYUP:
{
SDL_Scancode sc = event.key.keysym.scancode;
if (sc == SDL_SCANCODE_SCROLLLOCK)
break;
uint32_t scancode = mapScancode(sc);
if (scancode == 0)
break;
spice_key_up(scancode);
break;
}
case SDL_MOUSEWHEEL:
spice_mouse_press (event.wheel.y == 1 ? 4 : 5);
spice_mouse_release(event.wheel.y == 1 ? 4 : 5);
break;
case SDL_MOUSEMOTION:
if (serverMode)
spice_mouse_motion(event.motion.xrel, event.motion.yrel);
else
spice_mouse_motion(
(int)event.motion.x - mouseX,
(int)event.motion.y - mouseY
);
mouseX = event.motion.x;
mouseY = event.motion.y;
break;
case SDL_MOUSEBUTTONDOWN:
spice_mouse_position(event.button.x, event.button.y);
spice_mouse_press(event.button.button);
break;
case SDL_MOUSEBUTTONUP:
spice_mouse_position(event.button.x, event.button.y);
spice_mouse_release(event.button.button);
break;
default:
break;
}
}
usleep(1000);
}
SDL_FreeCursor(cursor);
return 0;
}
int main(int argc, char * argv[])
{
memset(&state, 0, sizeof(state));
state.running = true;
int shm_fd = 0;
SDL_Thread *t_spice = NULL;
SDL_Thread *t_event = NULL;
while(1)
{
umask(0);
const mode_t mode =
S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH;
if ((shm_fd = shm_open("ivshmem", O_CREAT | O_RDWR, mode)) < 0)
{
DEBUG_ERROR("failed to open shared memory: %d %s", errno, strerror(errno));
break;
}
if (ftruncate(shm_fd, MAP_SIZE) != 0)
{
DEBUG_ERROR("failed to truncate memory region");
break;
}
if (!spice_connect("127.0.0.1", 5900, ""))
{
DEBUG_ERROR("Failed to connect to spice server");
return 0;
}
while(state.running && !spice_ready())
if (!spice_process())
{
state.running = false;
DEBUG_ERROR("Failed to process spice messages");
break;
}
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
{
DEBUG_ERROR("spice create thread failed");
break;
}
state.shm = (struct KVMGFXHeader *)mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (!state.shm)
{
DEBUG_ERROR("Failed to map memory");
break;
}
if (!(t_event = SDL_CreateThread(eventThread, "eventThread", NULL)))
{
DEBUG_ERROR("gpu create thread failed");
break;
}
while(state.running)
renderThread(NULL);
break;
}
state.running = false;
if (t_event)
SDL_WaitThread(t_event, NULL);
if (t_spice)
SDL_WaitThread(t_spice, NULL);
if (state.renderer)
SDL_DestroyRenderer(state.renderer);
if (state.window)
SDL_DestroyWindow(state.window);
if (state.shm)
munmap(state.shm, MAP_SIZE);
if (shm_fd)
close(shm_fd);
//shm_unlink("kvm-windows");
SDL_Quit();
return 0;
}

1
client/spice-common Submodule

@ -0,0 +1 @@
Subproject commit 739a859d79428fe188cd6516508ba013a162a810

787
client/spice.c Normal file
View File

@ -0,0 +1,787 @@
#include "spice.h"
#define DEBUG
#include "debug.h"
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <spice/protocol.h>
#include <spice/error_codes.h>
#include "spice/messages.h"
// ============================================================================
// internal structures
struct SpiceChannel
{
bool connected;
bool initDone;
uint8_t channelType;
int socket;
uint32_t ackFrequency;
uint32_t ackCount;
uint32_t serial;
};
struct SpiceKeyboard
{
uint32_t modifiers;
};
struct SpiceMouse
{
uint32_t buttonState;
};
struct Spice
{
char password[32];
struct sockaddr_in addr;
uint32_t sessionID;
uint32_t channelID;
struct SpiceChannel scMain;
struct SpiceChannel scInputs;
struct SpiceKeyboard kb;
struct SpiceMouse mouse;
};
// globals
struct Spice spice =
{
.sessionID = 0,
.scMain .connected = false,
.scMain .channelType = SPICE_CHANNEL_MAIN,
.scInputs.connected = false,
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
};
// internal forward decls
bool spice_connect_channel (struct SpiceChannel * channel);
void spice_disconnect_channel(struct SpiceChannel * channel);
bool spice_process_ack(struct SpiceChannel * channel);
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
bool spice_on_main_channel_read ();
bool spice_on_inputs_channel_read();
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
// ============================================================================
bool spice_connect(const char * host, const short port, const char * password)
{
strncpy(spice.password, password, sizeof(spice.password));
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
inet_pton(AF_INET, host, &spice.addr.sin_addr);
spice.addr.sin_family = AF_INET;
spice.addr.sin_port = htons(port);
spice.channelID = 0;
if (!spice_connect_channel(&spice.scMain))
{
DEBUG_ERROR("connect main channel failed");
return false;
}
return true;
}
// ============================================================================
void spice_disconnect()
{
spice_disconnect_channel(&spice.scMain );
spice_disconnect_channel(&spice.scInputs);
spice.sessionID = 0;
}
// ============================================================================
bool spice_ready()
{
return spice.scMain.connected &&
spice.scInputs.connected;
}
// ============================================================================
bool spice_process()
{
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(spice.scMain.socket , &readSet);
FD_SET(spice.scInputs.socket, &readSet);
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
if (rc < 0)
{
DEBUG_ERROR("select failure");
return false;
}
for(int i = 0; i < FD_SETSIZE; ++i)
if (FD_ISSET(i, &readSet))
{
if (i == spice.scMain.socket)
{
if (spice_on_main_channel_read())
{
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
{
DEBUG_ERROR("failed to process ack on main channel");
return false;
}
continue;
}
else
{
DEBUG_ERROR("failed to perform read on main channel");
return false;
}
}
if (spice.scInputs.connected && i == spice.scInputs.socket)
{
if (spice_on_inputs_channel_read())
{
if (!spice_process_ack(&spice.scInputs))
{
DEBUG_ERROR("failed to process ack on inputs channel");
return false;
}
continue;
}
else
{
DEBUG_ERROR("failed to perform read on inputs channel");
return false;
}
}
}
return true;
}
// ============================================================================
bool spice_process_ack(struct SpiceChannel * channel)
{
if (channel->ackFrequency == 0)
return true;
if (channel->ackCount++ != channel->ackFrequency)
return true;
channel->ackCount = 0;
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
}
// ============================================================================
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
{
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
{
DEBUG_ERROR("read failure");
*handled = false;
return false;
}
#if 0
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
channel->socket,
header->serial, header->type, header->size, header->sub_list);
#endif
if (!channel->initDone)
{
*handled = false;
return true;
}
switch(header->type)
{
case SPICE_MSG_MIGRATE:
case SPICE_MSG_MIGRATE_DATA:
{
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
*handled = true;
DEBUG_WARN("migration is not supported");
return false;
}
case SPICE_MSG_SET_ACK:
{
DEBUG_INFO("SPICE_MSG_SET_ACK");
*handled = true;
SpiceMsgSetAck in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
channel->ackFrequency = in.window;
SpiceMsgcAckSync out;
out.generation = in.generation;
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
return false;
return true;
}
case SPICE_MSG_PING:
{
DEBUG_PROTO("SPICE_MSG_PING");
*handled = true;
SpiceMsgPing in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
if (!spice_discard(channel, header->size - sizeof(in)))
{
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
return false;
}
SpiceMsgcPong out;
out.id = in.id;
out.timestamp = in.timestamp;
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
return false;
return true;
}
case SPICE_MSG_WAIT_FOR_CHANNELS:
case SPICE_MSG_DISCONNECTING :
{
*handled = true;
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
return false;
}
case SPICE_MSG_NOTIFY:
{
DEBUG_PROTO("SPICE_MSG_NOTIFY");
SpiceMsgNotify in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
char msg[in.message_len+1];
if (!spice_read(channel, msg, in.message_len+1))
return false;
DEBUG_INFO("notify message: %s", msg);
*handled = true;
return true;
}
}
*handled = false;
return true;
}
// ============================================================================
bool spice_on_main_channel_read()
{
struct SpiceChannel *channel = &spice.scMain;
SpiceDataHeader header;
bool handled;
if (!spice_on_common_read(channel, &header, &handled))
{
DEBUG_ERROR("read failure");
return false;
}
if (handled)
return true;
if (!channel->initDone)
{
if (header.type != SPICE_MSG_MAIN_INIT)
{
spice_disconnect();
DEBUG_ERROR("expected main init message but got type %u", header.type);
return false;
}
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
channel->initDone = true;
SpiceMsgMainInit msg;
if (!spice_read(channel, &msg, sizeof(msg)))
{
spice_disconnect();
return false;
}
spice.sessionID = msg.session_id;
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
{
DEBUG_ERROR("failed to set mouse mode");
return false;
}
if (!spice_connect_channel(&spice.scInputs))
{
DEBUG_ERROR("failed to connect inputs channel");
return false;
}
return true;
}
DEBUG_WARN("main channel unhandled message type %u", header.type);
spice_discard(channel, header.size);
return true;
}
// ============================================================================
bool spice_on_inputs_channel_read()
{
struct SpiceChannel *channel = &spice.scInputs;
SpiceDataHeader header;
bool handled;
if (!spice_on_common_read(channel, &header, &handled))
{
DEBUG_ERROR("read failure");
return false;
}
if (handled)
return true;
switch(header.type)
{
case SPICE_MSG_INPUTS_INIT:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
if (channel->initDone)
{
DEBUG_ERROR("input init message already done");
return false;
}
channel->initDone = true;
SpiceMsgInputsInit in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
return true;
}
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
SpiceMsgInputsInit in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
spice.kb.modifiers = in.modifiers;
return true;
}
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
return true;
}
}
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
spice_discard(channel, header.size);
return true;
}
// ============================================================================
bool spice_connect_channel(struct SpiceChannel * channel)
{
channel->initDone = false;
channel->ackFrequency = 0;
channel->ackCount = 0;
channel->serial = 0;
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
if (channel->socket == -1)
return false;
int flag = 1;
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
{
DEBUG_ERROR("socket connect failure");
close(channel->socket);
return false;
}
channel->connected = true;
SpiceLinkHeader header =
{
.magic = SPICE_MAGIC ,
.major_version = SPICE_VERSION_MAJOR,
.minor_version = SPICE_VERSION_MINOR,
.size = sizeof(SpiceLinkMess)
};
SpiceLinkMess message =
{
.connection_id = spice.sessionID,
.channel_type = channel->channelType,
.channel_id = spice.channelID,
.num_common_caps = 0,
.num_channel_caps = 0,
.caps_offset = sizeof(SpiceLinkMess)
};
spice_write(channel, &header , sizeof(header ));
spice_write(channel, &message, sizeof(message));
if (!spice_read(channel, &header, sizeof(header)))
{
DEBUG_ERROR("failed to read SpiceLinkHeader");
spice_disconnect_channel(channel);
return false;
}
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
{
DEBUG_ERROR("invalid or unsupported protocol version");
spice_disconnect_channel(channel);
return false;
}
if (header.size < sizeof(SpiceLinkReply))
{
DEBUG_ERROR("reported data size too small");
spice_disconnect_channel(channel);
return false;
}
SpiceLinkReply reply;
if (!spice_read(channel, &reply, sizeof(reply)))
{
DEBUG_ERROR("failed to read SpiceLinkReply");
spice_disconnect_channel(channel);
return false;
}
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
{
DEBUG_ERROR("server replied with error %u", reply.error);
spice_disconnect_channel(channel);
return false;
}
uint32_t capsCommon [reply.num_common_caps ];
uint32_t capsChannel[reply.num_channel_caps];
spice_read(channel, &capsCommon , sizeof(capsCommon ));
spice_read(channel, &capsChannel, sizeof(capsChannel));
BIO *bioKey = BIO_new(BIO_s_mem());
if (!bioKey)
{
DEBUG_ERROR("failed to allocate bioKey");
spice_disconnect_channel(channel);
return false;
}
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
char enc[RSA_size(rsa)];
if (RSA_public_encrypt(
strlen(spice.password) + 1,
(uint8_t*)spice.password,
(uint8_t*)enc,
rsa,
RSA_PKCS1_OAEP_PADDING
) <= 0)
{
DEBUG_ERROR("rsa public encrypt failed");
spice_disconnect_channel(channel);
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return false;
}
ssize_t rsaSize = RSA_size(rsa);
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
if (!spice_write(channel, enc, rsaSize))
{
DEBUG_ERROR("failed to write encrypted data");
spice_disconnect_channel(channel);
return false;
}
uint32_t linkResult;
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
{
DEBUG_ERROR("failed to read SpiceLinkResult");
spice_disconnect_channel(channel);
return false;
}
if (linkResult != SPICE_LINK_ERR_OK)
{
DEBUG_ERROR("connect code error %u", linkResult);
spice_disconnect_channel(channel);
return false;
}
return true;
}
// ============================================================================
void spice_disconnect_channel(struct SpiceChannel * channel)
{
if (channel->connected)
close(channel->socket);
channel->connected = false;
}
// ============================================================================
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
{
if (!channel->connected)
{
DEBUG_ERROR("not connected");
return -1;
}
if (!buffer)
{
DEBUG_ERROR("invalid buffer argument supplied");
return -1;
}
ssize_t len = send(channel->socket, buffer, size, 0);
if (len != size)
DEBUG_WARN("incomplete write");
return len;
}
// ============================================================================
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
{
SpiceDataHeader header;
header.serial = channel->serial++;
header.type = type;
header.size = size;
header.sub_list = 0;
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
{
DEBUG_ERROR("failed to write message header");
return false;
}
if (spice_write(channel, buffer, size) != size)
{
DEBUG_ERROR("failed to write message body");
return false;
}
return true;
}
// ============================================================================
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
{
if (!channel->connected)
{
DEBUG_ERROR("not connected");
return false;
}
if (!buffer)
{
DEBUG_ERROR("invalid buffer argument supplied");
return false;
}
ssize_t len = read(channel->socket, buffer, size);
if (len != size)
{
DEBUG_ERROR("incomplete write");
return false;
}
return true;
}
// ============================================================================
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
{
while(size)
{
char c[8192];
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
if (len <= 0)
return false;
size -= len;
}
return true;
}
// ============================================================================
bool spice_key_down(uint32_t code)
{
DEBUG_PROTO("%u", code);
if (code > 0x100)
code = 0xe0 | ((code - 0x100) << 8);
SpiceMsgcKeyDown msg;
msg.code = code;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
}
// ============================================================================
bool spice_key_up(uint32_t code)
{
DEBUG_PROTO("%u", code);
if (code < 0x100)
code |= 0x80;
else
code = 0x80e0 | ((code - 0x100) << 8);
SpiceMsgcKeyDown msg;
msg.code = code;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_mode(bool server)
{
DEBUG_PROTO("%s", server ? "server" : "client");
SpiceMsgcMainMouseModeRequest msg;
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_position(uint32_t x, uint32_t y)
{
DEBUG_PROTO("x=%u, y=%u", x, y);
SpiceMsgcMousePosition msg;
msg.x = x;
msg.y = y;
msg.button_state = spice.mouse.buttonState;
msg.display_id = 0;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_motion(int32_t x, int32_t y)
{
DEBUG_PROTO("x=%d, y=%d", x, y);
SpiceMsgcMouseMotion msg;
msg.x = x;
msg.y = y;
msg.button_state = spice.mouse.buttonState;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_press(uint32_t button)
{
DEBUG_PROTO("%u", button);
switch(button)
{
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
}
SpiceMsgcMousePress msg;
msg.button = button;
msg.button_state = spice.mouse.buttonState;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_release(uint32_t button)
{
DEBUG_PROTO("%u", button);
switch(button)
{
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
}
SpiceMsgcMouseRelease msg;
msg.button = button;
msg.button_state = spice.mouse.buttonState;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
}

16
client/spice.h Normal file
View File

@ -0,0 +1,16 @@
#include <sys/types.h>
#include <stdbool.h>
#include <stdint.h>
bool spice_connect(const char * host, const short port, const char * password);
void spice_disconnect();
bool spice_process();
bool spice_ready();
bool spice_key_down (uint32_t code);
bool spice_key_up (uint32_t code);
bool spice_mouse_mode (bool server);
bool spice_mouse_position(uint32_t x, uint32_t y);
bool spice_mouse_motion ( int32_t x, int32_t y);
bool spice_mouse_press (uint32_t button);
bool spice_mouse_release (uint32_t button);

102
client/spice/messages.h Normal file
View File

@ -0,0 +1,102 @@
#include <stdint.h>
#pragma pack(push,1)
typedef struct SpicePoint16
{
int16_t x, y;
}
SpicePoint16;
typedef struct SpiceMsgMainInit
{
uint32_t session_id;
uint32_t display_channels_hint;
uint32_t supported_mouse_modes;
uint32_t current_mouse_mode;
uint32_t agent_connected;
uint32_t agent_tokens;
uint32_t multi_media_time;
uint32_t ram_hint;
}
SpiceMsgMainInit;
typedef struct SpiceMsgcMainMouseModeRequest
{
uint16_t mouse_mode;
}
SpiceMsgcMainMouseModeRequest;
typedef struct SpiceMsgPing
{
uint32_t id;
uint64_t timestamp;
}
SpiceMsgPing,
SpiceMsgcPong;
typedef struct SpiceMsgSetAck
{
uint32_t generation;
uint32_t window;
}
SpiceMsgSetAck;
typedef struct SpiceMsgcAckSync
{
uint32_t generation;
}
SpiceMsgcAckSync;
typedef struct SpiceMsgNotify
{
uint64_t time_stamp;
uint32_t severity;
uint32_t visibility;
uint32_t what;
uint32_t message_len;
//char message[message_len+1]
}
SpiceMsgNotify;
typedef struct SpiceMsgInputsInit
{
uint16_t modifiers;
}
SpiceMsgInputsInit,
SpiceMsgInputsKeyModifiers,
SpiceMsgcInputsKeyModifiers;
typedef struct SpiceMsgcKeyDown
{
uint32_t code;
}
SpiceMsgcKeyDown,
SpiceMsgcKeyUp;
typedef struct SpiceMsgcMousePosition
{
uint32_t x;
uint32_t y;
uint16_t button_state;
uint8_t display_id;
}
SpiceMsgcMousePosition;
typedef struct SpiceMsgcMouseMotion
{
int32_t x;
int32_t y;
uint16_t button_state;
}
SpiceMsgcMouseMotion;
typedef struct SpiceMsgcMousePress
{
uint8_t button;
uint16_t button_state;
}
SpiceMsgcMousePress,
SpiceMsgcMouseRelease;
#pragma pack(pop)

0
server/TODO Normal file
View File