// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package linuxabi describes the /dev/sev-guest ioctl command ABI.
package linuxabi

import (
	"errors"
	"fmt"
	"reflect"
	"unsafe"
)

// EsResult is the status code type for Linux's GHCB communication results.
type EsResult int

// ioctl bits for x86-64
const (
	iocNrbits    = 8
	iocTypebits  = 8
	iocSizebits  = 14
	iocDirbits   = 2
	iocNrshift   = 0
	iocTypeshift = (iocNrshift + iocNrbits)
	iocSizeshift = (iocTypeshift + iocTypebits)
	iocDirshift  = (iocSizeshift + iocSizebits)
	iocWrite     = 1
	iocRead      = 2

	// Linux /dev/sev-guest ioctl interface
	iocTypeSnpGuestReq = 'S'
	iocSnpWithoutNr    = ((iocWrite | iocRead) << iocDirshift) |
		(iocTypeSnpGuestReq << iocTypeshift) |
		// unsafe.Sizeof(snpUserGuestRequest)
		(32 << iocSizeshift)

	// IocSnpGetReport is the ioctl command for getting an attestation report
	IocSnpGetReport = iocSnpWithoutNr | (0x0 << iocNrshift)

	// IocSnpGetDerivedKey is the ioctl command for getting a key derived from measured components and
	// either the VCEK or VMRK.
	IocSnpGetDerivedKey = iocSnpWithoutNr | (0x1 << iocNrshift)

	// IocSnpGetReport is the ioctl command for getting an extended attestation report that includes
	// certificate information.
	IocSnpGetExtendedReport = iocSnpWithoutNr | (0x2 << iocNrshift)

	// The message version for MSG_REPORT_REQ in the SNP API. Specified as 1.
	guestMsgVersion = 1

	// These numbers are from the uapi header sev_guest.h
	snpResportRespSize      = 4000
	msgReportReqHeaderSize  = 0x20
	SnpReportRespReportSize = snpResportRespSize - msgReportReqHeaderSize
)

const (
	// EsOk denotes success.
	EsOk EsResult = iota
	// EsUnsupported denotes that the requested operation is not supported.
	EsUnsupported
	// EsVmmError denotes that the virtual machine monitor was in an unexpected state.
	EsVmmError
	// EsDecodeFailed denotes that instruction decoding failed.
	EsDecodeFailed
	// EsException denotes that the GHCB communication caused an exception.
	EsException
	// EsRetry is the code for a retry instruction emulation
	EsRetry
)

// SevEsErr is an error that interprets SEV-ES guest-host communication results.
type SevEsErr struct {
	Result EsResult
}

func (err *SevEsErr) Error() string {
	if err.Result == EsUnsupported {
		return "requested operation not supported"
	}
	if err.Result == EsVmmError {
		return "unexpected state from the VMM"
	}
	if err.Result == EsDecodeFailed {
		return "instruction decoding failed"
	}
	if err.Result == EsException {
		return "instruction caused exception"
	}
	if err.Result == EsRetry {
		return "retry instruction emulation"
	}
	return "unknown error"
}

// SnpReportReqABI is Linux's sev-guest ioctl abi for sending a GET_REPORT request. See
// include/uapi/linux/sev-guest.h
type SnpReportReqABI struct {
	// ReportData to be included in the report
	ReportData [64]uint8

	// Vmpl is the SEV-SNP VMPL level to be included in the report.
	// The kernel must have access to the corresponding VMPCK.
	Vmpl uint32

	reserved [28]byte
}

// SnpReportRespABI is Linux's sev-guest ioctl abi for receiving a GET_REPORT response.
// The size is expected to be snpReportRespSize.
type SnpReportRespABI struct {
	Status     uint32
	ReportSize uint32
	reserved   [0x20 - 8]byte
	// Data is the response data, see SEV-SNP spec for the format
	Data [SnpReportRespReportSize]uint8
}

// ABI returns the same object since it doesn't need a separate representation across the interface.
func (r *SnpReportReqABI) ABI() BinaryConversion { return r }

// Pointer returns a pointer to the object itself.
func (r *SnpReportReqABI) Pointer() unsafe.Pointer {
	return unsafe.Pointer(r)
}

// Finish is a no-op.
func (r *SnpReportReqABI) Finish(_ BinaryConvertible) error { return nil }

// ABI returns the same object since it doesn't need a separate representation across the interface.
func (r *SnpReportRespABI) ABI() BinaryConversion { return r }

// Pointer returns a pointer to the object itself.
func (r *SnpReportRespABI) Pointer() unsafe.Pointer {
	return unsafe.Pointer(r)
}

// Finish checks the status of the message and translates it to a Golang error.
func (r *SnpReportRespABI) Finish(_ BinaryConvertible) error {
	if r.Status != 0 {
		switch r.Status {
		case 0x16: // Value from MSG_REPORT_RSP specification in SNP API.
			return errors.New("get_report had invalid parameters")
		default:
			return fmt.Errorf("unknown status: 0x%x", r.Status)
		}
	}
	return nil
}

// SnpDerivedKeyReqABI is the ABI representation of a request to the SEV guest device to derive a
// key from specified information.
type SnpDerivedKeyReqABI struct {
	// RootKeySelect is all reserved bits except bit 0 for UseVMRK (1) or UseVCEK (0).
	RootKeySelect    uint32
	reserved         uint32
	GuestFieldSelect uint64
	// Vmpl to mix into the key. Must be greater than or equal to current Vmpl.
	Vmpl uint32
	// GuestSVN to mix into the key. Must be less than or equal to GuestSVN at launch.
	GuestSVN uint32
	// TCBVersion to mix into the key. Must be less than or equal to the CommittedTcb.
	TCBVersion uint64
}

// Pointer returns a pointer to the object.
func (r *SnpDerivedKeyReqABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) }

// Finish is a no-op.
func (r *SnpDerivedKeyReqABI) Finish(BinaryConvertible) error { return nil }

// ABI returns the ABI representation of this object.
func (r *SnpDerivedKeyReqABI) ABI() BinaryConversion { return r }

// SnpDerivedKeyRespABI represents the response to an SnpDerivedKeyReq.
type SnpDerivedKeyRespABI struct {
	Status   uint32
	reserved [0x20 - 4]byte
	Data     [32]byte
}

// ABI returns the object itself.
func (r *SnpDerivedKeyRespABI) ABI() BinaryConversion { return r }

// Pointer returns a pointer to the object itself.
func (r *SnpDerivedKeyRespABI) Pointer() unsafe.Pointer { return unsafe.Pointer(r) }

// Finish is a no-op.
func (r *SnpDerivedKeyRespABI) Finish(BinaryConvertible) error {
	switch r.Status {
	case 0:
		return nil
	case 0x16:
		return errors.New("msg_key_req error: invalid parameters")
	default:
		return fmt.Errorf("msg_key_req unknown status code: 0x%x", r.Status)
	}
}

// SnpExtendedReportReqABI is Linux's sev-guest ioctl abi for sending a GET_EXTENDED_REPORT request.
type SnpExtendedReportReqABI struct {
	Data SnpReportReqABI

	// Where to copy the certificate blob.
	CertsAddress unsafe.Pointer

	// length of the certificate blob
	CertsLength uint32
}

// SnpExtendedReportReq is close to Linux's sev-guest ioctl abi for sending a GET_EXTENDED_REPORT request,
// but uses safer types for the Ioctl interface.
type SnpExtendedReportReq struct {
	Data SnpReportReqABI

	// Certs receives the certificate blob after the extended report request.
	Certs []byte

	// CertsLength is the length of the certificate blob.
	CertsLength uint32
}

// Pointer returns a pointer so the object itself.
func (r *SnpExtendedReportReqABI) Pointer() unsafe.Pointer {
	return unsafe.Pointer(r)
}

// Finish writes back the changed CertsLength value.
func (r *SnpExtendedReportReqABI) Finish(b BinaryConvertible) error {
	s, ok := b.(*SnpExtendedReportReq)
	if !ok {
		return fmt.Errorf("Finish argument is %v. Expects a *SnpExtendedReportReq", reflect.TypeOf(b))
	}
	s.CertsLength = r.CertsLength
	return nil
}

// ABI returns an object that can cross the ABI boundary and copy back changes to the original
// object.
func (r *SnpExtendedReportReq) ABI() BinaryConversion {
	var certsAddress unsafe.Pointer
	if len(r.Certs) != 0 {
		certsAddress = unsafe.Pointer(&r.Certs[0])
	}
	return &SnpExtendedReportReqABI{
		Data:         r.Data,
		CertsAddress: certsAddress,
		CertsLength:  r.CertsLength,
	}
}

// SnpUserGuestRequestABI is Linux's sev-guest ioctl abi for issuing a guest message.
type SnpUserGuestRequestABI struct {
	GuestMsgVersion uint32
	// Request and response structure address.
	ReqData  unsafe.Pointer
	RespData unsafe.Pointer
	// firmware error code on failure (see psp-sev.h in Linux kernel)
	FwErr uint64
}

type snpUserGuestRequestConversion struct {
	abi      SnpUserGuestRequestABI
	reqConv  BinaryConversion
	respConv BinaryConversion
}

// SnpUserGuestRequest is Linux's sev-guest ioctl interface for issuing a guest message. The
// types here enhance runtime safety when using Ioctl as an interface.
type SnpUserGuestRequest struct {
	// Request and response structure address.
	ReqData  BinaryConvertible
	RespData BinaryConvertible
	// firmware error code on failure (see psp-sev.h in Linux kernel)
	FwErr uint64
}

// ABI returns an object that can cross the ABI boundary and copy back changes to the original
// object.
func (r *SnpUserGuestRequest) ABI() BinaryConversion {
	result := &snpUserGuestRequestConversion{
		reqConv:  r.ReqData.ABI(),
		respConv: r.RespData.ABI(),
	}
	result.abi.GuestMsgVersion = guestMsgVersion
	result.abi.ReqData = result.reqConv.Pointer()
	result.abi.RespData = result.respConv.Pointer()
	return result
}

// Pointer returns a pointer to the object that crosses the ABI boundary.
func (r *snpUserGuestRequestConversion) Pointer() unsafe.Pointer {
	return unsafe.Pointer(&r.abi)
}

// Finish writes back the FwErr and any changes to the request or response objects.
func (r *snpUserGuestRequestConversion) Finish(b BinaryConvertible) error {
	s, ok := b.(*SnpUserGuestRequest)
	if !ok {
		return fmt.Errorf("Finish argument is %v. Expects a *SnpUserGuestRequestSafe", reflect.TypeOf(b))
	}
	if err := r.reqConv.Finish(s.ReqData); err != nil {
		return fmt.Errorf("could not finalize request data: %v", err)
	}
	if err := r.respConv.Finish(s.RespData); err != nil {
		return fmt.Errorf("could not finalize response data: %v", err)
	}
	s.FwErr = r.abi.FwErr
	return nil
}

// BinaryConversion is an interface that abstracts a "stand-in" object that passes through an ABI
// boundary and can finalize changes to the original object.
type BinaryConversion interface {
	Pointer() unsafe.Pointer
	Finish(BinaryConvertible) error
}

// BinaryConvertible is an interface for an object that can produce a partner BinaryConversion
// object to allow its representation to pass the ABI boundary.
type BinaryConvertible interface {
	ABI() BinaryConversion
}
