/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a slight modifications) from:
 *     MD5Builder.cpp - exposed md5 ROM functions for esp8266
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2015 Hristo Gochkov. All rights reserved.
 *
 * This file is part of the esp8266 core for Arduino environment.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include "CoreSys.h"
#include "MD5Builder.h"


namespace eCxx {

namespace NodeMCU {


//
// Helper function
//
static inline uint8_t hexCharToByte(uint8_t c) __ECXX_ATTR_ALWAYS_INLINE;
static inline uint8_t hexCharToByte(uint8_t c)
{
    return ( c >= 'a' && c <= 'f' ) ? ( c - ( static_cast<uint8_t>('a') - 0xA ) ) :
           ( c >= 'A' && c <= 'F' ) ? ( c - ( static_cast<uint8_t>('A') - 0xA ) ) :
           ( c >= '0' && c <= '9' ) ? ( c - ( static_cast<uint8_t>('0')       ) ) : 0;
}


//
// MD5 builder class
//
void MD5Builder::begin()
{
    memset(_buf, 0x00, 16);
    MD5Init(&_ctx);
}


void MD5Builder::add(const uint8_t* data, const uint16_t len)
{ MD5Update(&_ctx, data, len); }


void MD5Builder::addHexString(const char* data)
{
    const uint32_t len = strlen(data);

    uint8_t* tmp = new(nothrow) uint8_t[len / 2];
    if(!tmp) return;

    for(uint32_t i = 0; i < len; i += 2) {
        const uint8_t high = hexCharToByte( data[i    ] );
        const uint8_t low  = hexCharToByte( data[i + 1] );
        tmp[i / 2] = (high & 0x0F) << 4 | (low & 0x0F);
    }

    add(tmp, len / 2);

    delete [] tmp;
}


bool MD5Builder::addStream(Stream &stream, const size_t maxLen)
{
    constexpr int32_t BufSize = 512;

    uint8_t* buf = new(eCxx::nothrow) uint8_t[BufSize];
    if(!buf) return false;

    int32_t maxLengthLeft  = maxLen;
    int32_t bytesAvailable = stream.available();

    while( (bytesAvailable > 0) && (maxLengthLeft > 0) ) {
        // Determine number of bytes to read
        int32_t readBytes = bytesAvailable;
        // Read only until maxLengthLeft
        if(readBytes > maxLengthLeft)readBytes = maxLengthLeft;
        // Do not read more than the buffer can handle
        if(readBytes > BufSize) readBytes = BufSize;
        // Read data and check for error
        const int32_t numBytesRead = stream.readBytes(buf, readBytes);
        if(numBytesRead < 1) {
            delete [] buf;
            return false;
        }
        // Update MD5 with the buffer payload
        MD5Update(&_ctx, buf, numBytesRead);
        yield();
        // Update available number of bytes
        maxLengthLeft  -= numBytesRead;
        bytesAvailable  = stream.available();
    }

    delete [] buf;
    return true;
}


void MD5Builder::calculate()
{ MD5Final(_buf, &_ctx); }


void MD5Builder::getBytes(uint8_t* output) const
{ memcpy(output, _buf, 16); }


void MD5Builder::getChars(char* output) const
{ for(uint32_t i = 0; i < 16; ++i) sprintf( output + (i* 2), "%02x", _buf[i] ); }


const WString MD5Builder::toString() const
{
    char out[33];
    getChars(out);

    return WString(out);
}


} // namespace NodeMCU

} // namespace eCxx
