#include "HSExt.hpp"

namespace SRT {

bool HSExtMessage::loadFromData(uint8_t *buf, size_t len) {
    if (buf == NULL || len != HSEXT_MSG_SIZE) {
        return false;
    }

    _data = BufferRaw::create();
    _data->assign((char *)buf, len);
    extension_length = 3;
    HSExt::loadHeader();

    assert(extension_type == SRT_CMD_HSREQ || extension_type == SRT_CMD_HSRSP);

    uint8_t *ptr = (uint8_t *)_data->data() + 4;
    srt_version = loadUint32(ptr);
    ptr += 4;

    srt_flag = loadUint32(ptr);
    ptr += 4;

    recv_tsbpd_delay = loadUint16(ptr);
    ptr += 2;

    send_tsbpd_delay = loadUint16(ptr);
    ptr += 2;

    return true;
}

std::string HSExtMessage::dump() {
    _StrPrinter printer;
    printer << "srt version : " << std::hex << srt_version << " srt flag : " << std::hex << srt_flag
            << " recv_tsbpd_delay=" << recv_tsbpd_delay << " send_tsbpd_delay = " << send_tsbpd_delay;
    return printer;
}

bool HSExtMessage::storeToData() {
    _data = BufferRaw::create();
    _data->setCapacity(HSEXT_MSG_SIZE);
    _data->setSize(HSEXT_MSG_SIZE);
    extension_length = 3;
    HSExt::storeHeader();
    uint8_t *ptr = (uint8_t *)_data->data() + 4;

    storeUint32(ptr, srt_version);
    ptr += 4;

    storeUint32(ptr, srt_flag);
    ptr += 4;

    storeUint16(ptr, recv_tsbpd_delay);
    ptr += 2;

    storeUint16(ptr, send_tsbpd_delay);
    ptr += 2;
    return true;
}

bool HSExtStreamID::loadFromData(uint8_t *buf, size_t len) {
    if (buf == NULL || len < 4) {
        return false;
    }
    _data = BufferRaw::create();
    _data->assign((char *)buf, len);

    HSExt::loadHeader();

    size_t content_size = extension_length * 4;
    if (len < content_size + 4) {
        return false;
    }
    streamid.clear();
    char *ptr = _data->data() + 4;

    for (size_t i = 0; i < extension_length; ++i) {
        streamid.push_back(*(ptr + 3));
        streamid.push_back(*(ptr + 2));
        streamid.push_back(*(ptr + 1));
        streamid.push_back(*(ptr));
        ptr += 4;
    }
    char zero = 0x00;
    if (!streamid.empty()) {
        if (streamid.back() == zero) {
            streamid.erase(streamid.find_first_of(zero), streamid.size());
        }
    }
    return true;
}

bool HSExtStreamID::storeToData() {
    size_t content_size = ((streamid.length() + 4) + 3) / 4 * 4;

    _data = BufferRaw::create();
    _data->setCapacity(content_size);
    _data->setSize(content_size);
    extension_length = (content_size - 4) / 4;
    extension_type = SRT_CMD_SID;
    HSExt::storeHeader();
    auto ptr = _data->data() + 4;
    memset(ptr, 0, extension_length * 4);
    const char *src = streamid.c_str();
    for (size_t i = 0; i < streamid.length() / 4; ++i) {
        *ptr = *(src + 3 + i * 4);
        ptr++;

        *ptr = *(src + 2 + i * 4);
        ptr++;

        *ptr = *(src + 1 + i * 4);
        ptr++;

        *ptr = *(src + 0 + i * 4);
        ptr++;
    }

    ptr += 3;
    size_t offset = streamid.length() / 4 * 4;
    for (size_t i = 0; i < streamid.length() % 4; ++i) {
        *ptr = *(src + offset + i);
        ptr -= 1;
    }

    return true;
}

std::string HSExtStreamID::dump() {
    _StrPrinter printer;
    printer << " streamid : " << streamid;
    return printer;
}

size_t KeyMaterial::getContentSize() {
    size_t variable_width = _slen + _warpped_key.size();
    size_t content_size = variable_width + 16;
    return content_size;
}

bool KeyMaterial::loadFromData(uint8_t *buf, size_t len) {
    if (buf == NULL || len < 16) {
        return false;
    }
    uint8_t *ptr = (uint8_t *)buf;

    _km_version = (*ptr & 0x70) >> 4;
    _pt = *ptr & 0x0f;
    ptr += 1;

    _sign = loadUint16(ptr);
    ptr += 2;

    _kk = *ptr & 0x03;
    auto sek_num = 1;
    if (_kk == KEY_BASED_ENCRYPTION_BOTH_SEK) {
        sek_num = 2;
    }
    ptr += 1;

    _keki = loadUint32(ptr);
    ptr += 4;

    _cipher = *ptr;
    ptr += 1;

    _auth = *ptr;
    ptr += 1;

    _se = *ptr;
    ptr += 1;

    //Resv2
    ptr += 1;
    //Resv3
    ptr += 2;

    _slen = *ptr *4;
    ptr += 1;

    _klen = *ptr *4;
    ptr += 1;

    size_t wrapped_key_len = 8 + sek_num * _klen;
    size_t variable_width = _slen + wrapped_key_len;
    if (len < variable_width + 16) {
        return false;
    }

    _salt.assign((const char*)ptr, (size_t)_slen);
    ptr += _slen;

    _warpped_key.assign((const char*)ptr, (size_t)wrapped_key_len);

    return true;
}

bool KeyMaterial::storeToData(uint8_t *buf, size_t len) {
    auto content_size = getContentSize();
    if (len < content_size) {
        return false;
    }

    uint8_t *ptr = (uint8_t *)buf;
    memset(ptr, 0, len);

    *ptr = ((_km_version << 4)& 0x70) | (_pt & 0x0f);
    ptr += 1;

    storeUint16(ptr, _sign);
    ptr += 2;

    *ptr = _kk & 0x03;
    ptr += 1;

    storeUint32(ptr, _keki);
    ptr += 4;

    *ptr = _cipher;
    ptr += 1;

    *ptr = _auth;
    ptr += 1;

    *ptr = _se;
    ptr += 1;

    *ptr = 0; //Resv2
    ptr += 1;

    storeUint16(ptr, 0);//Resv3
    ptr += 2;

    *ptr = (uint8_t)(_slen/4);
    ptr += 1;

    *ptr = (uint8_t)(_klen/4);
    ptr += 1;

    const char *src = _salt.data();
    for (size_t i = 0; i < _salt.size(); ptr++, src++, i++) {
        *ptr = *src;
    }

    src = _warpped_key.data();
    for (size_t i = 0; i < _warpped_key.size(); ptr++, src++, i++) {
        *ptr = *src;
    }
    return true;
}

std::string KeyMaterial::dump() {
    _StrPrinter printer;
    printer << "kmVersion: " << _km_version 
        << " pt : " << _pt 
        << " sign : " << std::hex << _sign
        << " kk : " << _kk 
        << " keki : " << _keki 
        << " cipher : " << _cipher 
        << " auth : " << _auth 
        << " se : " << _se 
        << " sLen : " << _slen 
        << " salt : " << std::hex << _salt.data()
        << " kLen : " << _klen;
    return printer;
}

bool HSExtKeyMaterial::loadFromData(uint8_t *buf, size_t len) {
    if (buf == NULL || len < 4) {
        return false;
    }
    HSExt::_data = BufferRaw::create();
    HSExt::_data->assign((char *)buf, len);
    HSExt::loadHeader();
    assert(extension_type == SRT_CMD_KMREQ || extension_type == SRT_CMD_KMRSP);
    return KeyMaterial::loadFromData(buf +4, len -4);
}

bool HSExtKeyMaterial::storeToData() {
    size_t content_size = ((KeyMaterial::getContentSize() + 4) + 3) / 4 * 4;
    HSExt::_data = BufferRaw::create();
    HSExt::_data->setCapacity(content_size);
    HSExt::_data->setSize(content_size);
    extension_length = (content_size - 4) / 4;
    HSExt::storeHeader();
    return KeyMaterial::storeToData((uint8_t*)_data->data() + 4, content_size - 4);
}

std::string HSExtKeyMaterial::dump() {
    return KeyMaterial::dump();
}

} // namespace SRT
