package hvsock

// On Linux we have to deal with two different implementations. The
// "legacy" implementation never made it into the kernel, but several
// kernels, including the LinuxKit one carried patches for it for
// quite a while. The legacy version defined a new address family
// while the new version sits on top of the existing VMware/virtio
// socket implementation.
//
// We try to determine at init if we are on a kernel with the legacy
// implementation or the new version and set "legacyMode" accordingly.
//
// We can't just reuse the vsock implementation as we still need to
// emulated CloseRead()/CloseWrite() as not all Windows builds support
// it.

/*
#include <sys/socket.h>

struct sockaddr_hv {
	unsigned short shv_family;
	unsigned short reserved;
	unsigned char  shv_vm_id[16];
	unsigned char  shv_service_id[16];
};
int bind_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
    return bind(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int connect_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
    return connect(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int accept_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) {
    return accept4(fd, (struct sockaddr *)sa_hv, sa_hv_len, 0);
}
*/
import "C"

import (
	"fmt"
	"net"
	"os"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

const (
	hvsockAF  = 43 //SHV_PROTO_RAW
	hvsockRaw = 1  // SHV_PROTO_RAW
)

// Supported returns if hvsocks are supported on your platform
func Supported() bool {
	// Try opening  a hvsockAF socket. If it works we are on older, i.e. 4.9.x kernels.
	// 4.11 defines AF_SMC as 43 but it doesn't support protocol 1 so the
	// socket() call should fail.
	fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
	if err != nil {
		return false
	}
	syscall.Close(fd)
	return true
}

// Dial a Hyper-V socket address
func Dial(raddr Addr) (Conn, error) {
	fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
	if err != nil {
		return nil, err
	}

	sa := C.struct_sockaddr_hv{}
	sa.shv_family = hvsockAF
	sa.reserved = 0

	for i := 0; i < 16; i++ {
		sa.shv_vm_id[i] = C.uchar(raddr.VMID[i])
	}
	for i := 0; i < 16; i++ {
		sa.shv_service_id[i] = C.uchar(raddr.ServiceID[i])
	}

	// Retry connect in a loop if EINTR is encountered.
	for {
		if ret, errno := C.connect_sockaddr_hv(C.int(fd), &sa); ret != 0 {
			if errno == syscall.EINTR {
				continue
			}
			return nil, fmt.Errorf("connect(%s) failed with %d, errno=%d", raddr, ret, errno)
		}
		break
	}

	return newHVsockConn(uintptr(fd), &Addr{VMID: GUIDZero, ServiceID: GUIDZero}, &raddr), nil
}

// Listen returns a net.Listener which can accept connections on the given port
func Listen(addr Addr) (net.Listener, error) {
	fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
	if err != nil {
		return nil, err
	}

	sa := C.struct_sockaddr_hv{}
	sa.shv_family = hvsockAF
	sa.reserved = 0

	for i := 0; i < 16; i++ {
		sa.shv_vm_id[i] = C.uchar(addr.VMID[i])
	}
	for i := 0; i < 16; i++ {
		sa.shv_service_id[i] = C.uchar(addr.ServiceID[i])
	}

	if ret, errno := C.bind_sockaddr_hv(C.int(fd), &sa); ret != 0 {
		return nil, fmt.Errorf("listen(%s) failed with %d, errno=%d", addr, ret, errno)
	}

	err = syscall.Listen(fd, syscall.SOMAXCONN)
	if err != nil {
		return nil, errors.Wrapf(err, "listen(%s) failed", addr)
	}
	return &hvsockListener{fd, addr}, nil
}

//
// Hyper-v sockets Listener implementation
//

type hvsockListener struct {
	fd    int
	local Addr
}

// Accept accepts an incoming call and returns the new connection.
func (v *hvsockListener) Accept() (net.Conn, error) {
	var acceptSA C.struct_sockaddr_hv
	var acceptSALen C.socklen_t

	acceptSALen = C.sizeof_struct_sockaddr_hv
	fd, err := C.accept_hv(C.int(v.fd), &acceptSA, &acceptSALen)
	if err != nil {
		return nil, errors.Wrapf(err, "accept(%s) failed", v.local)
	}

	remote := &Addr{VMID: guidFromC(acceptSA.shv_vm_id), ServiceID: guidFromC(acceptSA.shv_service_id)}
	return newHVsockConn(uintptr(fd), &v.local, remote), nil
}

// Close closes the listening connection
func (v *hvsockListener) Close() error {
	// Note this won't cause the Accept to unblock.
	return unix.Close(v.fd)
}

// Addr returns the address the Listener is listening on
func (v *hvsockListener) Addr() net.Addr {
	return v.local
}

//
// Hyper-V socket connection implementation
//

// hvsockConn represents a connection over a Hyper-V socket
type hvsockConn struct {
	hvsock *os.File
	fd     uintptr
	local  *Addr
	remote *Addr
}

func newHVsockConn(fd uintptr, local, remote *Addr) *hvsockConn {
	hvsock := os.NewFile(fd, fmt.Sprintf("hvsock:%d", fd))
	return &hvsockConn{hvsock: hvsock, fd: fd, local: local, remote: remote}
}

// LocalAddr returns the local address of a connection
func (v *hvsockConn) LocalAddr() net.Addr {
	return v.local
}

// RemoteAddr returns the remote address of a connection
func (v *hvsockConn) RemoteAddr() net.Addr {
	return v.remote
}

// Close closes the connection
func (v *hvsockConn) Close() error {
	return v.hvsock.Close()
}

// CloseRead shuts down the reading side of a hvsock connection
func (v *hvsockConn) CloseRead() error {
	return syscall.Shutdown(int(v.fd), syscall.SHUT_RD)
}

// CloseWrite shuts down the writing side of a hvsock connection
func (v *hvsockConn) CloseWrite() error {
	return syscall.Shutdown(int(v.fd), syscall.SHUT_WR)
}

// Read reads data from the connection
func (v *hvsockConn) Read(buf []byte) (int, error) {
	return v.hvsock.Read(buf)
}

// Write writes data over the connection
// TODO(rn): replace with a straight call to v.hvsock.Write() once 4.9.x support is deprecated
func (v *hvsockConn) Write(buf []byte) (int, error) {
	written := 0
	toWrite := len(buf)
	for toWrite > 0 {
		thisBatch := min(toWrite, maxMsgSize)
		n, err := v.hvsock.Write(buf[written : written+thisBatch])
		if err != nil {
			return written, err
		}
		if n != thisBatch {
			return written, fmt.Errorf("short write %d != %d", n, thisBatch)
		}
		toWrite -= n
		written += n
	}

	return written, nil
}

// SetDeadline sets the read and write deadlines associated with the connection
func (v *hvsockConn) SetDeadline(t time.Time) error {
	return nil // FIXME
}

// SetReadDeadline sets the deadline for future Read calls.
func (v *hvsockConn) SetReadDeadline(t time.Time) error {
	return nil // FIXME
}

// SetWriteDeadline sets the deadline for future Write calls
func (v *hvsockConn) SetWriteDeadline(t time.Time) error {
	return nil // FIXME
}

// File duplicates the underlying socket descriptor and returns it.
func (v *hvsockConn) File() (*os.File, error) {
	// This is equivalent to dup(2) but creates the new fd with CLOEXEC already set.
	r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.hvsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0)
	if e1 != 0 {
		return nil, os.NewSyscallError("fcntl", e1)
	}
	return os.NewFile(r0, v.hvsock.Name()), nil
}

func guidFromC(cg [16]C.uchar) GUID {
	var g GUID
	for i := 0; i < 16; i++ {
		g[i] = byte(cg[i])
	}
	return g
}
