/*
 * VBMouse - VirtualBox Shared Folders service client functions
 * Copyright (C) 2022 Javier S. Pedro
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#ifndef VBOXSHFL_H
#define VBOXSHFL_H

#include "vboxhgcm.h"

#define SHFLSTRING_WITH_BUF(varname, bufsize) \
	struct { \
	    SHFLSTRING shflstr; \
	    char buf[bufsize]; \
	} varname = { .shflstr = { .u16Size = bufsize} }

#define SHFLDIRINFO_WITH_NAME_BUF(varname, bufsize) \
	struct { \
	    SHFLDIRINFO dirinfo; \
	    char buf[bufsize]; \
	} varname = { \
	    .dirinfo = { .name = { .u16Size = bufsize} } \
	}

static inline unsigned shflstring_size_with_buf(const SHFLSTRING *str)
{
	return sizeof(SHFLSTRING) + str->u16Size;
}

static inline unsigned shflstring_size_optional_in(const SHFLSTRING *str)
{
	if (str->u16Length > 0) {
		return sizeof(SHFLSTRING) + str->u16Size;
	} else {
		return 0;
	}
}

static inline void shflstring_clear(SHFLSTRING *str)
{
	str->u16Length = 0;
}

static void shflstring_strcpy(SHFLSTRING *str, const char __far *src)
{
	str->u16Length = MIN(_fstrlen(src), str->u16Size - 1);
	_fmemcpy(str->ach, src, str->u16Length);
	str->ach[str->u16Length] = '\0';
}

static void shflstring_strncpy(SHFLSTRING *str, const char __far *src, unsigned len)
{
	str->u16Length = MIN(len, str->u16Size - 1);
	_fmemcpy(str->ach, src, str->u16Length);
	str->ach[str->u16Length] = '\0';
}

static int32_t vbox_shfl_query_mappings(LPVBOXCOMM vb, hgcm_client_id_t client_id, uint32_t flags, unsigned __far *num_maps, SHFLMAPPING __far *maps)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAPPINGS, 3);

	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = flags;

	req->aParms[1].type = VMMDevHGCMParmType_32bit;
	req->aParms[1].u.value32 = *num_maps;

	req->aParms[2].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[2].u.Pointer.size = sizeof(SHFLMAPPING) * *num_maps;
	req->aParms[2].u.Pointer.u.linearAddr = linear_addr(maps);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*num_maps = req->aParms[1].u.value32;

	return req->header.result;
}

static int32_t vbox_shfl_query_map_name(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLSTRING *name)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAP_NAME, 2);

	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	req->aParms[1].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[1].u.Pointer.size = shflstring_size_with_buf(name);
	req->aParms[1].u.Pointer.u.linearAddr = linear_addr(name);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_map_folder(LPVBOXCOMM vb, hgcm_client_id_t client_id, const SHFLSTRING *name, SHFLROOT *root)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_MAP_FOLDER, 4);

	// arg 0 in shflstring "name"
	req->aParms[0].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[0].u.Pointer.size = shflstring_size_with_buf(name);
	req->aParms[0].u.Pointer.u.linearAddr = linear_addr(name);

	// arg 1 out uint32 "root"
	req->aParms[1].type = VMMDevHGCMParmType_32bit;
	req->aParms[1].u.value32 = *root;

	// arg 2 in uint32 "delimiter"
	req->aParms[2].type = VMMDevHGCMParmType_32bit;
	req->aParms[2].u.value32 = '\\';

	// arg 3 in uint32 "caseSensitive"
	req->aParms[3].type = VMMDevHGCMParmType_32bit;
	req->aParms[3].u.value32 = 0;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*root = req->aParms[1].u.value32;

	return req->header.result;
}

static int32_t vbox_shfl_unmap_folder(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_UNMAP_FOLDER, 1);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_open(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, const SHFLSTRING *name, SHFLCREATEPARMS *parms)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_CREATE, 3);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in shflstring "name"
	req->aParms[1].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[1].u.Pointer.size = shflstring_size_with_buf(name);
	req->aParms[1].u.Pointer.u.linearAddr = linear_addr(name);

	// arg 2 in shflcreateparms "parms"
	req->aParms[2].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[2].u.Pointer.size = sizeof(SHFLCREATEPARMS);
	req->aParms[2].u.Pointer.u.linearAddr = linear_addr(parms);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_close(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_CLOSE, 2);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in uint64 "handle"
	req->aParms[1].type = VMMDevHGCMParmType_64bit;
	req->aParms[1].u.value64 = handle;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_read(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                               unsigned long offset, unsigned __far *size, void __far *buffer)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_READ, 5);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in uint64 "handle"
	req->aParms[1].type = VMMDevHGCMParmType_64bit;
	req->aParms[1].u.value64 = handle;

	// arg 2 in uint64 "offset"
	req->aParms[2].type = VMMDevHGCMParmType_64bit;
	req->aParms[2].u.value64 = offset;

	// arg 3 inout uint32 "size"
	req->aParms[3].type = VMMDevHGCMParmType_32bit;
	req->aParms[3].u.value32 = *size;

	// arg 4 out void "buffer"
	req->aParms[4].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[4].u.Pointer.size = *size;
	req->aParms[4].u.Pointer.u.linearAddr = linear_addr(buffer);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*size = req->aParms[3].u.value32;

	return req->header.result;
}

static int32_t vbox_shfl_write(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                               unsigned long offset, unsigned __far *size, void __far *buffer)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_WRITE, 5);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in uint64 "handle"
	req->aParms[1].type = VMMDevHGCMParmType_64bit;
	req->aParms[1].u.value64 = handle;

	// arg 2 in uint64 "offset"
	req->aParms[2].type = VMMDevHGCMParmType_64bit;
	req->aParms[2].u.value64 = offset;

	// arg 3 inout uint32 "size"
	req->aParms[3].type = VMMDevHGCMParmType_32bit;
	req->aParms[3].u.value32 = *size;

	// arg 4 in void "buffer"
	req->aParms[4].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[4].u.Pointer.size = *size;
	req->aParms[4].u.Pointer.u.linearAddr = linear_addr(buffer);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*size = req->aParms[3].u.value32;

	return req->header.result;
}

static int32_t vbox_shfl_lock(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                              unsigned long offset, unsigned long length, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_LOCK, 5);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in uint64 "handle"
	req->aParms[1].type = VMMDevHGCMParmType_64bit;
	req->aParms[1].u.value64 = handle;

	// arg 2 in uint64 "offset"
	req->aParms[2].type = VMMDevHGCMParmType_64bit;
	req->aParms[2].u.value64 = offset;

	// arg 3 inout uint64 "length"
	req->aParms[3].type = VMMDevHGCMParmType_64bit;
	req->aParms[3].u.value64 = length;

	// arg 4 in uint32 "flags"
	req->aParms[4].type = VMMDevHGCMParmType_32bit;
	req->aParms[4].u.value32 = flags;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_list(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                              unsigned flags, unsigned __far *size, const SHFLSTRING *path, SHFLDIRINFO *dirinfo, unsigned __far *resume, unsigned __far *count)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_LIST, 8);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in uint64 "handle"
	req->aParms[1].type = VMMDevHGCMParmType_64bit;
	req->aParms[1].u.value64 = handle;

	// arg 2 in uint32 "flags"
	req->aParms[2].type = VMMDevHGCMParmType_32bit;
	req->aParms[2].u.value32 = flags;

	// arg 3 inout uint32 "size"
	req->aParms[3].type = VMMDevHGCMParmType_32bit;
	req->aParms[3].u.value32 = *size;

	// arg 4 in shflstring "path"
	req->aParms[4].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[4].u.Pointer.size = shflstring_size_optional_in(path);
	req->aParms[4].u.Pointer.u.linearAddr = linear_addr(path);

	// arg 5 out void "dirinfo"
	req->aParms[5].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[5].u.Pointer.size = *size;
	req->aParms[5].u.Pointer.u.linearAddr = linear_addr(dirinfo);

	// arg 6 inout uint32 "resume_point"
	req->aParms[6].type = VMMDevHGCMParmType_32bit;
	req->aParms[6].u.value32 = *resume;

	// arg 7 out uint32 "count"
	req->aParms[7].type = VMMDevHGCMParmType_32bit;
	req->aParms[7].u.value32 = 0;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*size = req->aParms[3].u.value32;
	*resume = req->aParms[6].u.value32;
	*count = req->aParms[7].u.value32;

	return req->header.result;
}

static int32_t vbox_shfl_remove(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                const SHFLSTRING *path, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_REMOVE, 3);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in shflstring "path"
	req->aParms[1].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[1].u.Pointer.size = shflstring_size_with_buf(path);
	req->aParms[1].u.Pointer.u.linearAddr = linear_addr(path);

	// arg 2 in uint32 "flags"
	req->aParms[2].type = VMMDevHGCMParmType_32bit;
	req->aParms[2].u.value32 = flags;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_rename(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                const SHFLSTRING *src, const SHFLSTRING *dst, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_RENAME, 4);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 in shflstring "src"
	req->aParms[1].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[1].u.Pointer.size = shflstring_size_with_buf(src);
	req->aParms[1].u.Pointer.u.linearAddr = linear_addr(src);

	// arg 2 in shflstring "dst"
	req->aParms[2].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[2].u.Pointer.size = shflstring_size_with_buf(dst);
	req->aParms[2].u.Pointer.u.linearAddr = linear_addr(dst);

	// arg 3 in uint32 "flags"
	req->aParms[3].type = VMMDevHGCMParmType_32bit;
	req->aParms[3].u.value32 = flags;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_set_utf8(LPVBOXCOMM vb, hgcm_client_id_t client_id)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_SET_UTF8, 0);

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	return req->header.result;
}

static int32_t vbox_shfl_query_map_info(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                        SHFLSTRING *name, SHFLSTRING *mountPoint, unsigned *flags, unsigned *version)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAP_INFO, 5);

	// arg 0 in uint32 "root"
	req->aParms[0].type = VMMDevHGCMParmType_32bit;
	req->aParms[0].u.value32 = root;

	// arg 1 inout shflstring "name"
	req->aParms[1].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[1].u.Pointer.size = shflstring_size_with_buf(name);
	req->aParms[1].u.Pointer.u.linearAddr = linear_addr(name);

	// arg 2 inout shflstring "mountPoint"
	req->aParms[2].type = VMMDevHGCMParmType_LinAddr;
	req->aParms[2].u.Pointer.size = shflstring_size_with_buf(mountPoint);
	req->aParms[2].u.Pointer.u.linearAddr = linear_addr(mountPoint);

	// arg 3 inout uint64 "flags"
	req->aParms[3].type = VMMDevHGCMParmType_64bit;
	req->aParms[3].u.value64 = *flags;

	// arg 4 out uint32 "version"
	req->aParms[4].type = VMMDevHGCMParmType_32bit;
	req->aParms[4].u.value32 = *version;

	vbox_send_request(vb->iobase, vb->dds.physicalAddress);

	if (req->header.header.rc < 0) {
		return req->header.header.rc;
	} else if (req->header.header.rc == VINF_HGCM_ASYNC_EXECUTE) {
		vbox_hgcm_wait(&req->header);
	}

	*flags = req->aParms[3].u.value64;
	*version = req->aParms[4].u.value32;

	return req->header.result;
}

#endif // VBOXSHFL_H