//go:build !octopus && !nautilus
// +build !octopus,!nautilus

package rbd

// #cgo LDFLAGS: -lrbd
// /* force XSI-complaint strerror_r() */
// #define _POSIX_C_SOURCE 200112L
// #undef _GNU_SOURCE
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
// #include <rbd/librbd.h>
import "C"

import (
	"unsafe"
)

// cEncryptionData contains the data needed by the encryption functions
type cEncryptionData struct {
	format   C.rbd_encryption_format_t
	opts     C.rbd_encryption_options_t
	optsSize C.size_t
	free     func()
}

// EncryptionAlgorithm is the encryption algorithm
type EncryptionAlgorithm C.rbd_encryption_algorithm_t

// Possible values for EncryptionAlgorithm:
// EncryptionAlgorithmAES128: AES 128bits
// EncryptionAlgorithmAES256: AES 256bits
const (
	EncryptionAlgorithmAES128 = EncryptionAlgorithm(C.RBD_ENCRYPTION_ALGORITHM_AES128)
	EncryptionAlgorithmAES256 = EncryptionAlgorithm(C.RBD_ENCRYPTION_ALGORITHM_AES256)
)

// EncryptionOptionsLUKS1 and EncryptionOptionsLUKS2 are identical
// structures at the moment, just as they are in the librbd api.
// The purpose behind creating different identical structures, is to facilitate
// future modifications of one of the formats, while maintaining backwards
// compatibility with the other.

// EncryptionOptionsLUKS1 options required for LUKS v1
type EncryptionOptionsLUKS1 struct {
	Alg        EncryptionAlgorithm
	Passphrase []byte
}

// EncryptionOptionsLUKS2 options required for LUKS v2
type EncryptionOptionsLUKS2 struct {
	Alg        EncryptionAlgorithm
	Passphrase []byte
}

// EncryptionOptions interface is used to encapsulate the different encryption
// formats options and enable converting them from go to C structures.
type EncryptionOptions interface {
	allocateEncryptionOptions() cEncryptionData
}

func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cEncryptionData {
	var retData cEncryptionData

	// CBytes allocates memory. it will be freed when cEncryptionData.free is called
	cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
	cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks1_format_options_t)
	cOpts := (*C.rbd_encryption_luks1_format_options_t)(C.malloc(cOptsSize))
	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
	cOpts.passphrase = cPassphrase
	cOpts.passphrase_size = C.size_t(len(opts.Passphrase))

	retData.format = C.RBD_ENCRYPTION_FORMAT_LUKS1
	retData.opts = C.rbd_encryption_options_t(cOpts)
	retData.optsSize = cOptsSize
	retData.free = func() {
		C.free(unsafe.Pointer(cOpts.passphrase))
		C.free(unsafe.Pointer(cOpts))
	}
	return retData
}

func (opts EncryptionOptionsLUKS2) allocateEncryptionOptions() cEncryptionData {
	var retData cEncryptionData

	// CBytes allocates memory. it will be freed when cEncryptionData.free is called
	cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
	cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks2_format_options_t)
	cOpts := (*C.rbd_encryption_luks2_format_options_t)(C.malloc(cOptsSize))
	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
	cOpts.passphrase = cPassphrase
	cOpts.passphrase_size = C.size_t(len(opts.Passphrase))

	retData.format = C.RBD_ENCRYPTION_FORMAT_LUKS2
	retData.opts = C.rbd_encryption_options_t(cOpts)
	retData.optsSize = cOptsSize
	retData.free = func() {
		C.free(unsafe.Pointer(cOpts.passphrase))
		C.free(unsafe.Pointer(cOpts))
	}
	return retData
}

// EncryptionFormat creates an encryption format header
//
// Implements:
//
//	int rbd_encryption_format(rbd_image_t image,
//	                          rbd_encryption_format_t format,
//	                          rbd_encryption_options_t opts,
//	                          size_t opts_size);
//
// To issue an IO against the image, you need to mount the image
// with libvirt/qemu using the LUKS format, or make a call to
// rbd_encryption_load().
func (image *Image) EncryptionFormat(opts EncryptionOptions) error {
	if image.image == nil {
		return ErrImageNotOpen
	}

	encryptionOpts := opts.allocateEncryptionOptions()
	defer encryptionOpts.free()

	ret := C.rbd_encryption_format(
		image.image,
		encryptionOpts.format,
		encryptionOpts.opts,
		encryptionOpts.optsSize)

	return getError(ret)
}

// EncryptionLoad enables IO on an open encrypted image
//
// Implements:
//
//	int rbd_encryption_load(rbd_image_t image,
//	                        rbd_encryption_format_t format,
//	                        rbd_encryption_options_t opts,
//	                        size_t opts_size);
func (image *Image) EncryptionLoad(opts EncryptionOptions) error {
	if image.image == nil {
		return ErrImageNotOpen
	}

	encryptionOpts := opts.allocateEncryptionOptions()
	defer encryptionOpts.free()

	ret := C.rbd_encryption_load(
		image.image,
		encryptionOpts.format,
		encryptionOpts.opts,
		encryptionOpts.optsSize)
	return getError(ret)
}
