# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2021 Nvidia, Inc. All rights reserved. See COPYING file

from libc.string cimport memcpy

from pyverbs.pyverbs_error import PyverbsRDMAError
cimport pyverbs.providers.mlx5.libmlx5 as dv
from pyverbs.base import PyverbsRDMAErrno
from pyverbs.pd cimport PD


cdef class Mlx5CryptoLoginAttr(PyverbsObject):
    def __init__(self, credential, credential_id=0, import_kek_id=0):
        """
        Initializes a Mlx5CryptoLoginAttr object representing
        mlx5dv_crypto_login_attr C struct.
        :param credential: The credential to login with. Must be provided
                           wrapped by the AES key wrap algorithm using the
                           import KEK indicated by *import_kek_id*.
        :param credential_id: The index of credential that stored on the device.
        :param import_kek_id: The index of import_kek that stored on the device.
        """
        cdef char *credential_c = credential
        self.mlx5dv_crypto_login_attr.credential_id = credential_id
        self.mlx5dv_crypto_login_attr.import_kek_id = import_kek_id
        memcpy(self.mlx5dv_crypto_login_attr.credential, credential_c, 48)

    @property
    def credential_id(self):
        return self.mlx5dv_crypto_login_attr.credential_id

    @property
    def import_kek_id(self):
        return self.mlx5dv_crypto_login_attr.import_kek_id

    @property
    def credential(self):
        return self.mlx5dv_crypto_login_attr.credential

    @property
    def comp_mask(self):
        return self.mlx5dv_crypto_login_attr.comp_mask

    def __str__(self):
        print_format = '{:20}: {:<20}\n'
        return 'Mlx5CryptoLoginAttr:\n' +\
               print_format.format('Credential id', self.credential_id) +\
               print_format.format('Import KEK id', self.import_kek_id) +\
               print_format.format('Credential', str(self.credential)) +\
               print_format.format('Comp mask', self.comp_mask)


cdef class Mlx5CryptoExtLoginAttr(PyverbsObject):
    def __init__(self, credential, credential_len, credential_id=0, import_kek_id=0):
        """
        Initializes a Mlx5CryptoExtLoginAttr object representing
        mlx5dv_crypto_login_attr_ex C struct.
        :param credential: The credential to login with.
        :param credential_len: The credential length. Must be provided.
        :param credential_id: The index of credential that stored on the device.
        :param import_kek_id: The index of import_kek that stored on the device.
        """
        cdef char *credential_c = credential
        self.mlx5dv_crypto_login_attr_ex.credential_id = credential_id
        self.mlx5dv_crypto_login_attr_ex.import_kek_id = import_kek_id
        self.mlx5dv_crypto_login_attr_ex.credential = <void*>credential_c
        self.mlx5dv_crypto_login_attr_ex.credential_len = credential_len
        self.credential = credential

    @property
    def credential_id(self):
        return self.mlx5dv_crypto_login_attr_ex.credential_id

    @property
    def import_kek_id(self):
        return self.mlx5dv_crypto_login_attr_ex.import_kek_id

    @property
    def credential(self):
        return self.credential

    @property
    def credential_len(self):
        return self.mlx5dv_crypto_login_attr_ex.credential_len

    @property
    def comp_mask(self):
        return self.mlx5dv_crypto_login_attr_ex.comp_mask

    def __str__(self):
        print_format = '{:20}: {:<20}\n'
        return 'Mlx5CryptoExtLoginAttr:\n' +\
               print_format.format('Credential id', self.credential_id) +\
               print_format.format('Import KEK id', self.import_kek_id) +\
               print_format.format('Credential', str(self.credential)) +\
               print_format.format('credential_len', self.credential_len) +\
               print_format.format('Comp mask', self.comp_mask)


cdef class Mlx5DEKInitAttr(PyverbsObject):
    def __init__(self, PD pd, key_size, has_keytag=False, key_purpose=0, opaque=bytes(),
                 key=bytes(), comp_mask=0, Mlx5CryptoLogin crypto_login=None):
        """
        Initializes a Mlx5DEKInitAttr object representing mlx5dv_dek_init_attr
        C struct.
        :param pd: The protection domain to be associated with the DEK.
        :param credential_id: The size of the key,
                              can be MLX5DV_CRYPTO_KEY_SIZE_128/256
        :param has_keytag: Whether the DEK has a keytag or not. If set, the key
                           should include a 8 Bytes keytag.
        :param key_purpose: The crypto purpose of the key.
        :param opaque: Plaintext metadata to describe the key.
        :param key: The key itself, wrapped by the crypto login session's
                    import KEK.
        :param comp_mask: Reserved for future extension.
        :param crypto_login: Crypto login object.
        """
        cdef char *opaque_c = opaque
        cdef char *key_c = key
        self.pd = pd
        self.mlx5dv_dek_init_attr.pd = pd.pd
        self.mlx5dv_dek_init_attr.key_size = key_size
        self.mlx5dv_dek_init_attr.has_keytag = has_keytag
        self.mlx5dv_dek_init_attr.key_purpose = key_purpose
        memcpy(self.mlx5dv_dek_init_attr.opaque, opaque_c, 8)
        memcpy(self.mlx5dv_dek_init_attr.key, key_c, 128)
        self.mlx5dv_dek_init_attr.comp_mask = comp_mask
        self.mlx5dv_dek_init_attr.crypto_login = crypto_login.crypto_login_obj if crypto_login else NULL

    @property
    def key_size(self):
        return self.mlx5dv_dek_init_attr.key_size

    @property
    def has_keytag(self):
        return self.mlx5dv_dek_init_attr.has_keytag

    @property
    def key_purpose(self):
        return self.mlx5dv_dek_init_attr.key_purpose

    @property
    def opaque(self):
        return self.mlx5dv_dek_init_attr.opaque.decode()

    @property
    def key(self):
        return self.mlx5dv_dek_init_attr.key.hex()

    @property
    def comp_mask(self):
        return self.mlx5dv_dek_init_attr.comp_mask

    def __str__(self):
        print_format = '{:20}: {:<20}\n'
        return 'Mlx5DEKInitAttr:\n' +\
            print_format.format('key_size', self.key_size) +\
            print_format.format('Has keytag', self.has_keytag) +\
            print_format.format('Key purpose', self.key_purpose) +\
            print_format.format('Opaque', self.opaque) +\
            print_format.format('Key (in hex format)', self.key) +\
            print_format.format('Comp mask', self.comp_mask)


cdef class Mlx5DEKAttr(PyverbsObject):
    """
    Initializes a Mlx5DEKAttr object representing mlx5dv_dek_attr
    C struct.
    """
    @property
    def state(self):
        return self.mlx5dv_dek_attr.state

    @property
    def opaque(self):
        return self.mlx5dv_dek_attr.opaque

    @property
    def comp_mask(self):
        return self.mlx5dv_dek_attr.comp_mask


cdef class Mlx5CryptoAttr(PyverbsObject):
    def __init__(self, crypto_standard=0, encrypt_on_tx=False, signature_crypto_order=0,
                 data_unit_size=0, initial_tweak=bytes(), Mlx5DEK dek=None,
                 keytag=bytes(), comp_mask=0):
        """
        Initializes a Mlx5CryptoAttr object representing mlx5dv_crypto_attr
        C struct.
        :param crypto_standard: The encryption standard that should be used.
        :param encrypt_on_tx: If set, memory data will be encrypted during TX
                              and wire data will be decrypted during RX.
        :param signature_crypto_order: Controls the order between crypto and
                                       signature operations. Relevant only if
                                       signature is configured.
        :param data_unit_size: The tweak is	incremented after each
                               *data_unit_size* during the encryption.
        :param initial_tweak: A value to be used during encryption of each data
                              unit. This value is incremented by the device for
                              every data unit in the message
        :param dek: The DEK to be used for the crypto operations.
        :param keytag: A tag that verifies that the correct DEK is being used.
        :param comp_mask: Reserved for future extension.
        """
        cdef char *initial_tweak_c = initial_tweak
        cdef char *keytag_c = keytag
        self.mlx5dv_crypto_attr.crypto_standard = crypto_standard
        self.mlx5dv_crypto_attr.encrypt_on_tx = encrypt_on_tx
        self.mlx5dv_crypto_attr.signature_crypto_order = signature_crypto_order
        self.mlx5dv_crypto_attr.data_unit_size = data_unit_size
        memcpy(self.mlx5dv_crypto_attr.initial_tweak, initial_tweak_c, 16)
        self.mlx5dv_crypto_attr.dek = dek.mlx5dv_dek
        memcpy(self.mlx5dv_crypto_attr.keytag, keytag_c, 8)
        self.mlx5dv_crypto_attr.comp_mask = comp_mask

    @property
    def crypto_standard(self):
        return self.mlx5dv_crypto_attr.crypto_standard

    @property
    def encrypt_on_tx(self):
        return self.mlx5dv_crypto_attr.encrypt_on_tx

    @property
    def signature_crypto_order(self):
        return self.mlx5dv_crypto_attr.signature_crypto_order

    @property
    def data_unit_size(self):
        return self.mlx5dv_crypto_attr.data_unit_size

    @property
    def initial_tweak(self):
        return self.mlx5dv_crypto_attr.initial_tweak.hex()

    @property
    def keytag(self):
        print('@keytag')
        return self.mlx5dv_crypto_attr.keytag.hex()

    @property
    def comp_mask(self):
        return self.mlx5dv_crypto_attr.comp_mask

    def __str__(self):
        print_format = '{:30}: {:<20}\n'
        return 'Mlx5CryptoAttr:\n' +\
            print_format.format('Crypto standard', self.crypto_standard) +\
            print_format.format('Encrypt on TX', self.encrypt_on_tx) +\
            print_format.format('Signature crypto order', self.signature_crypto_order) +\
            print_format.format('Data unit size', self.data_unit_size) +\
            print_format.format('Initial tweak (in hex format)', self.initial_tweak) +\
            print_format.format('keytag (in hex format)', self.keytag) +\
            print_format.format('Comp mask', self.comp_mask)


cdef class Mlx5DEK(PyverbsCM):
    def __init__(self, Context ctx, Mlx5DEKInitAttr dek_init_attr):
        """
        Create a Mlx5DEK object.
        :param context: Context to create the schedule resources on.
        :param dek_init_attr: Mlx5DEKInitAttr, containing the DEK attributes.
        """
        self.mlx5dv_dek = dv.mlx5dv_dek_create(ctx.context,
                                               &dek_init_attr.mlx5dv_dek_init_attr)
        if self.mlx5dv_dek == NULL:
            raise PyverbsRDMAErrno('Failed to create DEK')
        self.pd = dek_init_attr.pd
        self.pd.deks.add(self)

    def query(self):
        """
        Query the dek state.
        :return: Mlx5DEKAttr which contains the dek state and opaque.
        """
        dek_attr = Mlx5DEKAttr()
        rc = dv.mlx5dv_dek_query(self.mlx5dv_dek, &dek_attr.mlx5dv_dek_attr)
        if rc:
            raise PyverbsRDMAError('Failed to query the dek', rc)
        return dek_attr

    def __dealloc__(self):
        self.close()

    cpdef close(self):
        if self.mlx5dv_dek != NULL:
            rc = dv.mlx5dv_dek_destroy(self.mlx5dv_dek)
            if rc:
                raise PyverbsRDMAError('Failed to destroy a DEK', rc)
            self.mlx5dv_dek = NULL
            self.pd = None


cdef class Mlx5CryptoLogin(PyverbsCM):
    def __init__(self, Context context, Mlx5CryptoExtLoginAttr crypto_attr):
        """
        Create a Mlx5CryptoLogin object.
        :param context: Context to create the schedule resources on.
        :param crypto_ext_login_attr: Mlx5CryptoExtLoginAttr.
        """
        self.crypto_login_obj = dv.mlx5dv_crypto_login_create(context.context,
                                                              &crypto_attr.mlx5dv_crypto_login_attr_ex)
        if self.crypto_login_obj == NULL:
            raise PyverbsRDMAErrno('Failed to create CryptoLoginObj')

        self.context = context
        context.crypto_logins.add(self)

    def query(self):
        """
        Queries the state of the current crypto login session.
        :return: The login state.
        """
        cdef dv.mlx5dv_crypto_login_query_attr query_attr
        return dv.mlx5dv_crypto_login_query(self.crypto_login_obj, &query_attr)

    def __dealloc__(self):
        self.close()

    cpdef close(self):
        if self.crypto_login_obj != NULL:
            rc = dv.mlx5dv_crypto_login_destroy(self.crypto_login_obj)
            if rc:
                raise PyverbsRDMAError('Failed to destroy a CryptoLoginObj', rc)
            self.crypto_login_obj = NULL
