/*
Implementation by Ronny Van Keer, hereby denoted as "the implementer".

For more information, feedback or questions, please refer to our website:
https://keccak.team/

To the extent possible under law, the implementer has waived all copyright
and related or neighboring rights to the source code in this file.
http://creativecommons.org/publicdomain/zero/1.0/
*/

#define JOIN0(a, b)                     a ## b
#define JOIN(a, b)                      JOIN0(a, b)

#define SnP_StaticInitialize            JOIN(SnP, _StaticInitialize)
#define SnP_Initialize                  JOIN(SnP, _Initialize)
#define SnP_AddBytes                    JOIN(SnP, _AddBytes)
#define SnP_AddByte                     JOIN(SnP, _AddByte)
#define SnP_OverwriteBytes              JOIN(SnP, _OverwriteBytes)
#define SnP_ExtractBytes                JOIN(SnP, _ExtractBytes)
#define SnP_ExtractAndAddBytes          JOIN(SnP, _ExtractAndAddBytes)

#define Cyclist_Instance                JOIN(prefix, _Instance)
#define Cyclist_Initialize              JOIN(prefix, _Initialize)
#define Cyclist_Absorb                  JOIN(prefix, _Absorb)
#define Cyclist_Encrypt                 JOIN(prefix, _Encrypt)
#define Cyclist_Decrypt                 JOIN(prefix, _Decrypt)
#define Cyclist_Squeeze                 JOIN(prefix, _Squeeze)
#define Cyclist_SqueezeKey              JOIN(prefix, _SqueezeKey)
#define Cyclist_Ratchet                 JOIN(prefix, _Ratchet)

#define Cyclist_AbsorbAny               JOIN(prefix, _AbsorbAny)
#define Cyclist_AbsorbKey               JOIN(prefix, _AbsorbKey)
#define Cyclist_SqueezeAny              JOIN(prefix, _SqueezeAny)
#define Cyclist_Down                    JOIN(prefix, _Down)
#define Cyclist_Up                      JOIN(prefix, _Up)
#define Cyclist_Crypt                   JOIN(prefix, _Crypt)

#define Cyclist_f_bPrime                JOIN(prefix, _f_bPrime)
#define Cyclist_Rhash                   JOIN(prefix, _Rhash)
#define Cyclist_Rkin                    JOIN(prefix, _Rkin)
#define Cyclist_Rkout                   JOIN(prefix, _Rkout)
#define Cyclist_lRatchet                JOIN(prefix, _lRatchet)

/* ------- Cyclist internal interfaces ------- */

static void Cyclist_Down(Cyclist_Instance *instance, const uint8_t *Xi, unsigned int XiLen, uint8_t Cd)
{
    SnP_AddBytes(instance->state, Xi, 0, XiLen);
    SnP_AddByte(instance->state, 0x01, XiLen);
    SnP_AddByte(instance->state, (instance->mode == Cyclist_ModeHash) ? (Cd & 0x01) : Cd, Cyclist_f_bPrime - 1);
    instance->phase = Cyclist_PhaseDown;

}

static void Cyclist_Up(Cyclist_Instance *instance, uint8_t *Yi, unsigned int YiLen, uint8_t Cu)
{
    #if defined(OUTPUT)
    uint8_t   s[Cyclist_f_bPrime];
    #endif

    if (instance->mode != Cyclist_ModeHash) {
        SnP_AddByte(instance->state, Cu, Cyclist_f_bPrime - 1);
    }
    #if defined(OUTPUT)
    if (instance->file != NULL) {
        SnP_ExtractBytes( instance->stateShadow, s, 0, Cyclist_f_bPrime );
        SnP_ExtractAndAddBytes( instance->state, s, s, 0, Cyclist_f_bPrime );
    }
    #endif
    SnP_Permute( instance->state );
    #if defined(OUTPUT)
    if (instance->file != NULL) {
        memcpy( instance->stateShadow, instance->state, sizeof(instance->state) );
        fprintf( instance->file, "Data XORed" );
        displayByteString( instance->file, "", s, Cyclist_f_bPrime );
        SnP_ExtractBytes( instance->stateShadow, s, 0, Cyclist_f_bPrime );
        fprintf( instance->file, "After f() ");
        displayByteString( instance->file, "", s, Cyclist_f_bPrime );
    }
    #endif
    instance->phase = Cyclist_PhaseUp;
    SnP_ExtractBytes( instance->state, Yi, 0, YiLen );
}

static void Cyclist_AbsorbAny(Cyclist_Instance *instance, const uint8_t *X, size_t XLen, unsigned int r, uint8_t Cd)
{
    unsigned int splitLen;

    do {
        if (instance->phase != Cyclist_PhaseUp) {
            Cyclist_Up(instance, NULL, 0, 0);
        }
        splitLen = MyMin(XLen, r);
        Cyclist_Down(instance, X, splitLen, Cd);
        Cd = 0;
        X       += splitLen;
        XLen    -= splitLen;
    } while ( XLen != 0 );
}

static void Cyclist_AbsorbKey(Cyclist_Instance *instance, const uint8_t *K, size_t KLen, const uint8_t *ID, size_t IDLen, const uint8_t *counter, size_t counterLen)
{
    uint8_t KID[Cyclist_Rkin];

    assert(instance->mode == Cyclist_ModeHash);
    assert((KLen + IDLen) <= (Cyclist_Rkin - 1));

    instance->mode      = Cyclist_ModeKeyed;
    instance->Rabsorb   = Cyclist_Rkin;
    instance->Rsqueeze  = Cyclist_Rkout;
    if (KLen != 0) {
        memcpy(KID, K, KLen);
        memcpy(KID + KLen, ID, IDLen);
        KID[KLen + IDLen] = (uint8_t)IDLen;
        Cyclist_AbsorbAny(instance, KID, KLen + IDLen + 1, instance->Rabsorb, 0x02);
        if (counterLen != 0) {
            Cyclist_AbsorbAny(instance, counter, counterLen, 1, 0x00);
        }
    }
}

static void Cyclist_SqueezeAny(Cyclist_Instance *instance, uint8_t *Y, size_t YLen, uint8_t Cu)
{
    unsigned int len;

    len = MyMin(YLen, instance->Rsqueeze );
    Cyclist_Up(instance, Y, len, Cu);
    Y += len;
    YLen -= len;
    while (YLen != 0) {
        Cyclist_Down(instance, NULL, 0, 0);
        len = MyMin(YLen, instance->Rsqueeze );
        Cyclist_Up(instance, Y, len, 0);
        Y += len;
        YLen -= len;
    }
}

static void Cyclist_Crypt(Cyclist_Instance *instance, const uint8_t *I, uint8_t *O, size_t IOLen, int decrypt)
{
    unsigned int splitLen;
    uint8_t      P[Cyclist_Rkout];
    uint8_t      Cu = 0x80;

    do {
        splitLen = MyMin(IOLen, Cyclist_Rkout); /* use Rkout instead of Rsqueeze, this function is only called in keyed mode */
        if (decrypt != 0) {
            Cyclist_Up(instance, NULL, 0, Cu); /* Up without extract */
            Xoodoo_ExtractAndAddBytes(instance->state, I, O, 0, splitLen); /* Extract from Up and Add */
            Cyclist_Down(instance, O, splitLen, 0x00);
        }
        else {
            memcpy(P, I, splitLen);
            Cyclist_Up(instance, NULL, 0, Cu); /* Up without extract */
            Xoodoo_ExtractAndAddBytes(instance->state, I, O, 0, splitLen); /* Extract from Up and Add */
            Cyclist_Down(instance, P, splitLen, 0x00);
        }
        Cu = 0x00;
        I       += splitLen;
        O       += splitLen;
        IOLen   -= splitLen;
    } while ( IOLen != 0 );
}

/* ------- Cyclist interfaces ------- */

void Cyclist_Initialize(Cyclist_Instance *instance, const uint8_t *K, size_t KLen, const uint8_t *ID, size_t IDLen, const uint8_t *counter, size_t counterLen)
{
    SnP_StaticInitialize();
    SnP_Initialize(instance->state);
    instance->phase     = Cyclist_PhaseUp;
    instance->mode      = Cyclist_ModeHash;
    instance->Rabsorb   = Cyclist_Rhash;
    instance->Rsqueeze  = Cyclist_Rhash;
    #ifdef OUTPUT
    instance->file = 0;
    SnP_Initialize( instance->stateShadow );
    #endif
    if (KLen != 0) {
        Cyclist_AbsorbKey(instance, K, KLen, ID, IDLen, counter, counterLen);
    }
}

void Cyclist_Absorb(Cyclist_Instance *instance, const uint8_t *X, size_t XLen)
{
    Cyclist_AbsorbAny(instance, X, XLen, instance->Rabsorb, 0x03);
}

void Cyclist_Encrypt(Cyclist_Instance *instance, const uint8_t *P, uint8_t *C, size_t PLen)
{
    assert(instance->mode == Cyclist_ModeKeyed);
    Cyclist_Crypt(instance, P, C, PLen, 0);
}

void Cyclist_Decrypt(Cyclist_Instance *instance, const uint8_t *C, uint8_t *P, size_t CLen)
{
    assert(instance->mode == Cyclist_ModeKeyed);
    Cyclist_Crypt(instance, C, P, CLen, 1);
}

void Cyclist_Squeeze(Cyclist_Instance *instance, uint8_t *Y, size_t YLen)
{
    Cyclist_SqueezeAny(instance, Y, YLen, 0x40);
}

void Cyclist_SqueezeKey(Cyclist_Instance *instance, uint8_t *K, size_t KLen)
{
    assert(instance->mode == Cyclist_ModeKeyed);
    Cyclist_SqueezeAny(instance, K, KLen, 0x20);
}

void Cyclist_Ratchet(Cyclist_Instance *instance)
{
    uint8_t buffer[Cyclist_lRatchet];

    assert(instance->mode == Cyclist_ModeKeyed);
    /* Squeeze then absorb is the same as overwriting with zeros */
    Cyclist_SqueezeAny(instance, buffer, sizeof(buffer), 0x10);
    Cyclist_AbsorbAny(instance, buffer, sizeof(buffer), instance->Rabsorb, 0x00);
}

#undef SnP_StaticInitialize
#undef SnP_Initialize
#undef SnP_AddBytes
#undef SnP_AddByte
#undef SnP_OverwriteBytes
#undef SnP_ExtractBytes
#undef SnP_ExtractAndAddBytes

#undef Cyclist_Instance
#undef Cyclist_Initialize
#undef Cyclist_Absorb
#undef Cyclist_Encrypt
#undef Cyclist_Decrypt
#undef Cyclist_Squeeze
#undef Cyclist_SqueezeKey
#undef Cyclist_Ratchet

#undef Cyclist_AbsorbAny
#undef Cyclist_AbsorbKey
#undef Cyclist_SqueezeAny
#undef Cyclist_Down
#undef Cyclist_Up
#undef Cyclist_Crypt

#undef Cyclist_f_bPrime
#undef Cyclist_Rhash
#undef Cyclist_Rkin
#undef Cyclist_Rkout
#undef Cyclist_lRatchet
