// Copyright 2020 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// This file is shared between executor and csource package.

// NetBSD-specific implementation of syz_usb_* pseudo-syscalls.

#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <dev/usb/vhci.h>
#include <fcntl.h>
#include <sys/ioctl.h>

// Redefinitions to match the linux types used in common_usb.h.

struct usb_endpoint_descriptor {
	uint8 bLength;
	uint8 bDescriptorType;
	uint8 bEndpointAddress;
	uint8 bmAttributes;
	uint16 wMaxPacketSize;
	uint8 bInterval;
	uint8 bRefresh;
	uint8 bSynchAddress;
} __attribute__((packed));

struct usb_device_descriptor {
	uint8 bLength;
	uint8 bDescriptorType;
	uint16 bcdUSB;
	uint8 bDeviceClass;
	uint8 bDeviceSubClass;
	uint8 bDeviceProtocol;
	uint8 bMaxPacketSize0;
	uint16 idVendor;
	uint16 idProduct;
	uint16 bcdDevice;
	uint8 iManufacturer;
	uint8 iProduct;
	uint8 iSerialNumber;
	uint8 bNumConfigurations;
} __attribute__((packed));

struct usb_config_descriptor {
	uint8 bLength;
	uint8 bDescriptorType;

	uint16 wTotalLength;
	uint8 bNumInterfaces;
	uint8 bConfigurationValue;
	uint8 iConfiguration;
	uint8 bmAttributes;
	uint8 bMaxPower;
} __attribute__((packed));

struct usb_interface_descriptor {
	uint8 bLength;
	uint8 bDescriptorType;
	uint8 bInterfaceNumber;
	uint8 bAlternateSetting;
	uint8 bNumEndpoints;
	uint8 bInterfaceClass;
	uint8 bInterfaceSubClass;
	uint8 bInterfaceProtocol;
	uint8 iInterface;
} __attribute__((packed));

struct usb_ctrlrequest {
	uint8 bRequestType;
	uint8 bRequest;
	uint16 wValue;
	uint16 wIndex;
	uint16 wLength;
} __attribute__((packed));

struct usb_qualifier_descriptor {
	uint8 bLength;
	uint8 bDescriptorType;
	uint16 bcdUSB;
	uint8 bDeviceClass;
	uint8 bDeviceSubClass;
	uint8 bDeviceProtocol;
	uint8 bMaxPacketSize0;
	uint8 bNumConfigurations;
	uint8 bRESERVED;
} __attribute__((packed));

#define USB_TYPE_MASK (0x03 << 5)
#define USB_TYPE_STANDARD (0x00 << 5)
#define USB_TYPE_CLASS (0x01 << 5)
#define USB_TYPE_VENDOR (0x02 << 5)
#define USB_TYPE_RESERVED (0x03 << 5)

#define USB_DT_DEVICE 0x01
#define USB_DT_CONFIG 0x02
#define USB_DT_STRING 0x03
#define USB_DT_INTERFACE 0x04
#define USB_DT_ENDPOINT 0x05
#define USB_DT_DEVICE_QUALIFIER 0x06
#define USB_DT_OTHER_SPEED_CONFIG 0x07
#define USB_DT_INTERFACE_POWER 0x08
#define USB_DT_OTG 0x09
#define USB_DT_DEBUG 0x0a
#define USB_DT_INTERFACE_ASSOCIATION 0x0b
#define USB_DT_SECURITY 0x0c
#define USB_DT_KEY 0x0d
#define USB_DT_ENCRYPTION_TYPE 0x0e
#define USB_DT_BOS 0x0f
#define USB_DT_DEVICE_CAPABILITY 0x10
#define USB_DT_WIRELESS_ENDPOINT_COMP 0x11
#define USB_DT_WIRE_ADAPTER 0x21
#define USB_DT_RPIPE 0x22
#define USB_DT_CS_RADIO_CONTROL 0x23
#define USB_DT_PIPE_USAGE 0x24
#define USB_DT_SS_ENDPOINT_COMP 0x30
#define USB_DT_SSP_ISOC_ENDPOINT_COMP 0x31

#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
#define USB_REQ_SET_ADDRESS 0x05
#define USB_REQ_GET_DESCRIPTOR 0x06
#define USB_REQ_SET_DESCRIPTOR 0x07
#define USB_REQ_GET_CONFIGURATION 0x08
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_REQ_GET_INTERFACE 0x0A
#define USB_REQ_SET_INTERFACE 0x0B
#define USB_REQ_SYNCH_FRAME 0x0C
#define USB_REQ_SET_SEL 0x30
#define USB_REQ_SET_ISOCH_DELAY 0x31

#define USB_REQ_SET_ENCRYPTION 0x0D
#define USB_REQ_GET_ENCRYPTION 0x0E
#define USB_REQ_RPIPE_ABORT 0x0E
#define USB_REQ_SET_HANDSHAKE 0x0F
#define USB_REQ_RPIPE_RESET 0x0F
#define USB_REQ_GET_HANDSHAKE 0x10
#define USB_REQ_SET_CONNECTION 0x11
#define USB_REQ_SET_SECURITY_DATA 0x12
#define USB_REQ_GET_SECURITY_DATA 0x13
#define USB_REQ_SET_WUSB_DATA 0x14
#define USB_REQ_LOOPBACK_DATA_WRITE 0x15
#define USB_REQ_LOOPBACK_DATA_READ 0x16
#define USB_REQ_SET_INTERFACE_DS 0x17

#define USB_REQ_GET_PARTNER_PDO 20
#define USB_REQ_GET_BATTERY_STATUS 21
#define USB_REQ_SET_PDO 22
#define USB_REQ_GET_VDM 23
#define USB_REQ_SEND_VDM 24

#if SYZ_EXECUTOR || __NR_syz_usb_connect
#include "common_usb.h"

static int vhci_open(void)
{
	char path[1024];

	snprintf(path, sizeof(path), "/dev/vhci%llu", procid);

	return open(path, O_RDWR);
}

static int vhci_setport(int fd, u_int port)
{
	struct vhci_ioc_set_port args;

	args.port = port;
	return ioctl(fd, VHCI_IOC_SET_PORT, &args);
}

static int vhci_usb_attach(int fd)
{
	return ioctl(fd, VHCI_IOC_USB_ATTACH, NULL);
}

static int vhci_usb_recv(int fd, void* buf, size_t size)
{
	uint8* ptr = (uint8*)buf;

	while (1) {
		ssize_t done = read(fd, ptr, size);
		if (done < 0)
			return -1;
		if ((size_t)done == size)
			return 0;
		size -= done;
		ptr += done;
	}
}

static int vhci_usb_send(int fd, void* buf, size_t size)
{
	uint8* ptr = (uint8*)buf;

	while (1) {
		ssize_t done = write(fd, ptr, size);
		if (done <= 0)
			return -1;
		if ((size_t)done == size)
			return 0;
		size -= done;
		ptr += done;
	}
}

static volatile long syz_usb_connect_impl(int fd, uint64 speed, uint64 dev_len,
					  const char* dev, const struct vusb_connect_descriptors* descs,
					  lookup_connect_out_response_t lookup_connect_response_out)
{
	struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
	if (!index) {
		debug("syz_usb_connect: add_usb_index failed\n");
		return -1;
	}
	debug("syz_usb_connect: add_usb_index success\n");

#if USB_DEBUG
	analyze_usb_device(index);
#endif

	if (vhci_setport(fd, 1))
		fail("syz_usb_connect: vhci_setport failed with");

	if (vhci_usb_attach(fd)) {
		debug("syz_usb_connect: vhci_usb_attach failed with %d\n", errno);
		return -1;
	}
	debug("syz_usb_connect: vhci_usb_attach success\n");

	bool done = false;
	while (!done) {
		vhci_request_t req;

		if (vhci_usb_recv(fd, &req, sizeof(req))) {
			debug("syz_usb_connect: vhci_usb_recv failed with %d\n", errno);
			return -1;
		}
		if (req.type != VHCI_REQ_CTRL) {
			debug("syz_usb_connect: received non-control transfer\n");
			return -1;
		}

		debug("syz_usb_connect: bReqType: 0x%x (%s), bReq: 0x%x, wVal: 0x%x, wIdx: 0x%x, wLen: %d\n",
		      req.u.ctrl.bmRequestType, (req.u.ctrl.bmRequestType & UE_DIR_IN) ? "IN" : "OUT",
		      req.u.ctrl.bRequest, UGETW(req.u.ctrl.wValue), UGETW(req.u.ctrl.wIndex), UGETW(req.u.ctrl.wLength));

#if USB_DEBUG
		analyze_control_request(fd, &req.u.ctrl);
#endif

		char* response_data = NULL;
		uint32 response_length = 0;
		struct usb_qualifier_descriptor qual;
		char data[4096];

		if (req.u.ctrl.bmRequestType & UE_DIR_IN) {
			if (!lookup_connect_response_in(fd, descs, (const struct usb_ctrlrequest*)&req.u.ctrl, &qual, &response_data, &response_length)) {
				debug("syz_usb_connect: unknown control IN request\n");
				return -1;
			}
		} else {
			if (!lookup_connect_response_out(fd, descs, (const struct usb_ctrlrequest*)&req.u.ctrl, &done)) {
				debug("syz_usb_connect: unknown control OUT request\n");
				return -1;
			}
			response_data = NULL;
			response_length = UGETW(req.u.ctrl.wLength);
		}

		if ((req.u.ctrl.bmRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
		    req.u.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
		} // TODO: possibly revisit.

		if (response_length > sizeof(data))
			response_length = 0;
		if ((uint32)UGETW(req.u.ctrl.wLength) < response_length)
			response_length = UGETW(req.u.ctrl.wLength);

		if (response_data)
			memcpy(data, response_data, response_length);
		else
			memset(data, 0, response_length);

		int rv = 0;
		if (req.u.ctrl.bmRequestType & UE_DIR_IN) {
			debug("syz_usb_connect: writing %d bytes\n", response_length);
			if (response_length > 0) {
				vhci_response_t res;
				res.size = response_length;
				rv = vhci_usb_send(fd, &res, sizeof(res));
				if (rv == 0)
					rv = vhci_usb_send(fd, data, response_length);
			}
		} else {
			rv = vhci_usb_recv(fd, data, response_length);
			debug("syz_usb_connect: read %d bytes\n", response_length);
			debug_dump_data(&data[0], response_length);
		}
		if (rv < 0) {
			debug("syz_usb_connect: usb_raw_ep0_read/write failed with %d\n", rv);
			return -1;
		}
	}

	sleep_ms(200);
	debug("syz_usb_connect: configured\n");
	return fd;
}

static volatile long syz_usb_connect(volatile long a0, volatile long a1,
				     volatile long a2, volatile long a3)
{
	uint64 speed = a0;
	uint64 dev_len = a1;
	const char* dev = (const char*)a2;
	const struct vusb_connect_descriptors* descs = (const struct vusb_connect_descriptors*)a3;

	debug("syz_usb_connect: dev: %p\n", dev);
	if (!dev) {
		debug("syz_usb_connect: dev is null\n");
		return -1;
	}

	debug("syz_usb_connect: device data:\n");
	debug_dump_data(dev, dev_len);

	int fd = vhci_open();
	if (fd < 0)
		fail("syz_usb_connect: vhci_open failed");
	long res = syz_usb_connect_impl(fd, speed, dev_len, dev, descs, &lookup_connect_response_out_generic);
	close(fd);
	return res;
}
#endif // SYZ_EXECUTOR || __NR_syz_usb_connect

#if SYZ_EXECUTOR || __NR_syz_usb_disconnect
static volatile long syz_usb_disconnect(volatile long a0)
{
	int fd = a0;

	int rv = close(fd);

	sleep_ms(200);

	return rv;
}
#endif // SYZ_EXECUTOR || __NR_syz_usb_disconnect
