GSSAPI="BASE"  # This ensures that a full module is generated by Cython

from gssapi.raw.cython_types cimport *
from gssapi.raw.sec_contexts cimport SecurityContext

from gssapi.raw.misc import GSSError
from gssapi.raw.named_tuples import WrapResult, UnwrapResult


cdef extern from "python_gssapi.h":
    OM_uint32 gss_get_mic(OM_uint32 *min_stat,
                          const gss_ctx_id_t context,
                          gss_qop_t qop,
                          const gss_buffer_t message,
                          gss_buffer_t output_token) nogil

    OM_uint32 gss_verify_mic(OM_uint32 *min_stat,
                             const gss_ctx_id_t context,
                             const gss_buffer_t message,
                             const gss_buffer_t token,
                             gss_qop_t *qop) nogil

    OM_uint32 gss_wrap_size_limit(OM_uint32 *min_stat,
                                  const gss_ctx_id_t context,
                                  int conf_req,
                                  gss_qop_t qop,
                                  OM_uint32 max_output_size,
                                  OM_uint32 *max_input_size) nogil

    OM_uint32 gss_wrap(OM_uint32 *min_stat,
                       const gss_ctx_id_t context,
                       int conf_req,
                       gss_qop_t qop,
                       const gss_buffer_t input_message,
                       int *conf_used,
                       gss_buffer_t output_message) nogil

    OM_uint32 gss_unwrap(OM_uint32 *min_stat,
                         const gss_ctx_id_t context,
                         const gss_buffer_t input_message,
                         gss_buffer_t output_message,
                         int *conf_used,
                         gss_qop_t *qop) nogil


def get_mic(SecurityContext context not None, message, qop=None):
    """
    get_mic(context, message, qop=None)
    Generate a MIC for a message.

    This method generates a Message Integrity Check token for the
    given message.  This can be separately trasmitted to the other
    entity, unlike wrap, which bundles the MIC and the message
    together.

    Args:
        context (SecurityContext): the current security context
        message (bytes): the message for which to generate the MIC
        qop (int): the requested Quality of Protection
            (or None to use the default)

    Returns:
        bytes: the generated MIC token

    Raises:
        ExpiredContextError
        MissingContextError
        BadQoPError
    """

    cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
                                                          message)
    cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT

    # GSS_C_EMPYT_BUFFER
    cdef gss_buffer_desc token_buffer = gss_buffer_desc(0, NULL)

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_get_mic(&min_stat, context.raw_ctx, qop_req,
                               &message_buffer, &token_buffer)

    if maj_stat == GSS_S_COMPLETE:
        res = (<char*>token_buffer.value)[:token_buffer.length]
        gss_release_buffer(&min_stat, &token_buffer)
        return res
    else:
        raise GSSError(maj_stat, min_stat)


def verify_mic(SecurityContext context not None, message, token):
    """
    verify_mic(context, message, token)
    Verify that a MIC matches a message.

    This method verifies that the given MIC matches the given message.
    If the MIC does not match the given message, an exception will
    be raised.

    Args:
        context (SecurityContext): the current security context
        message (bytes): the message in question
        token (bytes): the MIC token in question

    Returns:
        int: the QoP used.

    Raises:
        InvalidTokenError
        BadMICError
        DuplicateTokenError
        ExpiredTokenError
        TokenTooLateError
        TokenTooEarlyError
        ExpiredContextError
        MissingContextError
    """

    cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
                                                          message)
    cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)

    cdef gss_qop_t qop_state

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_verify_mic(&min_stat, context.raw_ctx, &message_buffer,
                                  &token_buffer, &qop_state)

    if maj_stat == GSS_S_COMPLETE:
        return qop_state
    else:
        raise GSSError(maj_stat, min_stat)


def wrap_size_limit(SecurityContext context not None, OM_uint32 output_size,
                    confidential=True, qop=None):
    """
    wrap_size_limit(context, output_size, confidential=True, qop=None)
    Calculate the max message size.

    This method calculates the unwrapped/unencrypted message size for
    the given maximum wrapped/encrypted message size.

    Args:
        context (SecurityContext): the current security context
        output_size (int): the maximum desired wrapped/encrypted message size
        confidential (bool): whether or not confidentiality is being used
        qop (int): the QoP that will be when you actually call wrap
            (or None for the default QoP)

    Returns:
        int: the maximum unencrypted/unwrapped message size

    Raises:
        MissingContextError
        ExpiredContextError
        BadQoPError
    """

    cdef int conf_req = confidential
    cdef OM_uint32 qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT

    cdef OM_uint32 max_input_size

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_wrap_size_limit(&min_stat, context.raw_ctx, conf_req,
                                       qop_req, output_size, &max_input_size)

    if maj_stat == GSS_S_COMPLETE:
        return max_input_size
    else:
        raise GSSError(maj_stat, min_stat)


def wrap(SecurityContext context not None, message, confidential=True,
         qop=None):
    """
    wrap(context, message, confidential=True, qop=None)
    Wrap/Encrypt a message.

    This method wraps or encrypts a message (depending on the value
    of confidential) with the given Quality of Protection.

    Args:
        context (SecurityContext): the current security context
        message (bytes): the message to wrap or encrypt
        confidential (bool): whether or not to encrypt the message (True),
            or just wrap it with a MIC (False)
        qop (int): the desired Quality of Protection
            (or None for the default QoP)

    Returns:
        WrapResult: the wrapped/encrypted message, and whether or not
            encryption was actually used

    Raises:
        ExpiredContextError
        MissingContextError
        BadQoPError
    """

    cdef int conf_req = confidential
    cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
    cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
                                                          message)

    cdef int conf_used
    # GSS_C_EMPTY_BUFFER
    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_wrap(&min_stat, context.raw_ctx, conf_req, qop_req,
                            &message_buffer, &conf_used, &output_buffer)

    if maj_stat == GSS_S_COMPLETE:
        output_message = (<char*>output_buffer.value)[:output_buffer.length]
        gss_release_buffer(&min_stat, &output_buffer)
        return WrapResult(output_message, <bint>conf_used)
    else:
        raise GSSError(maj_stat, min_stat)


def unwrap(SecurityContext context not None, message):
    """
    unwrap(context, message)
    Unwrap/Decrypt a message.

    This method unwraps or decrypts a message, depending
    on whether the sender used confidentiality.

    Args:
        context (SecurityContext): the current security context
        message (bytes): the message to unwrap/decrypt

    Returns:
        UnwrapResult: the unwrapped/decrypted message, whether or on
            encryption was used, and the QoP used

    Raises:
        InvalidTokenError
        BadMICError
        DuplicateTokenError
        ExpiredTokenError
        TokenTooLateError
        TokenTooEarlyError
        ExpiredContextError
        MissingContextError
    """

    cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)

    # GSS_C_EMPTY_BUFFER
    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
    cdef int conf_state
    cdef gss_qop_t qop_state

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_unwrap(&min_stat, context.raw_ctx, &input_buffer,
                              &output_buffer, &conf_state, &qop_state)

    if maj_stat == GSS_S_COMPLETE:
        output_message = (<char*>output_buffer.value)[:output_buffer.length]
        gss_release_buffer(&min_stat, &output_buffer)
        return UnwrapResult(output_message, <bint>conf_state, qop_state)
    else:
        raise GSSError(maj_stat, min_stat)
