//go:build linux

package vsock

import (
	"context"
	"net"
	"os"
	"time"

	"github.com/mdlayher/socket"
	"golang.org/x/sys/unix"
)

var _ net.Listener = &listener{}

// A listener is the net.Listener implementation for connection-oriented
// VM sockets.
type listener struct {
	c    *socket.Conn
	addr *Addr
}

// Addr and Close implement the net.Listener interface for listener.
func (l *listener) Addr() net.Addr                { return l.addr }
func (l *listener) Close() error                  { return l.c.Close() }
func (l *listener) SetDeadline(t time.Time) error { return l.c.SetDeadline(t) }

// Accept accepts a single connection from the listener, and sets up
// a net.Conn backed by conn.
func (l *listener) Accept() (net.Conn, error) {
	c, rsa, err := l.c.Accept(context.Background(), 0)
	if err != nil {
		return nil, err
	}

	savm := rsa.(*unix.SockaddrVM)
	remote := &Addr{
		ContextID: savm.CID,
		Port:      savm.Port,
	}

	return &Conn{
		c:      c,
		local:  l.addr,
		remote: remote,
	}, nil
}

// name is the socket name passed to package socket.
const name = "vsock"

// listen is the entry point for Listen on Linux.
func listen(cid, port uint32, _ *Config) (*Listener, error) {
	// TODO(mdlayher): Config default nil check and initialize. Pass options to
	// socket.Config where necessary.

	c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, name, nil)
	if err != nil {
		return nil, err
	}

	// Be sure to close the Conn if any of the system calls fail before we
	// return the Conn to the caller.

	if port == 0 {
		port = unix.VMADDR_PORT_ANY
	}

	if err := c.Bind(&unix.SockaddrVM{CID: cid, Port: port}); err != nil {
		_ = c.Close()
		return nil, err
	}

	if err := c.Listen(unix.SOMAXCONN); err != nil {
		_ = c.Close()
		return nil, err
	}

	l, err := newListener(c)
	if err != nil {
		_ = c.Close()
		return nil, err
	}

	return l, nil
}

// fileListener is the entry point for FileListener on Linux.
func fileListener(f *os.File) (*Listener, error) {
	c, err := socket.FileConn(f, name)
	if err != nil {
		return nil, err
	}

	l, err := newListener(c)
	if err != nil {
		_ = c.Close()
		return nil, err
	}

	return l, nil
}

// newListener creates a Listener from a raw socket.Conn.
func newListener(c *socket.Conn) (*Listener, error) {
	lsa, err := c.Getsockname()
	if err != nil {
		return nil, err
	}

	// Now that the library can also accept arbitrary os.Files, we have to
	// verify the address family so we don't accidentally create a
	// *vsock.Listener backed by TCP or some other socket type.
	lsavm, ok := lsa.(*unix.SockaddrVM)
	if !ok {
		// All errors should wrapped with os.SyscallError.
		return nil, os.NewSyscallError("listen", unix.EINVAL)
	}

	addr := &Addr{
		ContextID: lsavm.CID,
		Port:      lsavm.Port,
	}

	return &Listener{
		l: &listener{
			c:    c,
			addr: addr,
		},
	}, nil
}
