/*
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ipmi.hpp"

#include <cstring>
#include <span>
#include <string>
#include <unordered_map>

namespace blobs
{

bool validateRequestLength(BlobOEMCommands command, size_t requestLen)
{
    /* The smallest string is one letter and the nul-terminator. */
    static const int kMinStrLen = 2;

    static const std::unordered_map<BlobOEMCommands, size_t> minimumLengths = {
        {BlobOEMCommands::bmcBlobEnumerate, sizeof(struct BmcBlobEnumerateTx)},
        {BlobOEMCommands::bmcBlobOpen,
         sizeof(struct BmcBlobOpenTx) + kMinStrLen},
        {BlobOEMCommands::bmcBlobClose, sizeof(struct BmcBlobCloseTx)},
        {BlobOEMCommands::bmcBlobDelete,
         sizeof(struct BmcBlobDeleteTx) + kMinStrLen},
        {BlobOEMCommands::bmcBlobStat,
         sizeof(struct BmcBlobStatTx) + kMinStrLen},
        {BlobOEMCommands::bmcBlobSessionStat,
         sizeof(struct BmcBlobSessionStatTx)},
        {BlobOEMCommands::bmcBlobCommit, sizeof(struct BmcBlobCommitTx)},
        {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx)},
        {BlobOEMCommands::bmcBlobWrite,
         sizeof(struct BmcBlobWriteTx) + sizeof(uint8_t)},
        {BlobOEMCommands::bmcBlobWriteMeta,
         sizeof(struct BmcBlobWriteMetaTx) + sizeof(uint8_t)},
    };

    auto results = minimumLengths.find(command);
    if (results == minimumLengths.end())
    {
        /* Valid length by default if we don't care. */
        return true;
    }

    /* If the request is shorter than the minimum, it's invalid. */
    if (requestLen < results->second)
    {
        return false;
    }

    return true;
}

std::string stringFromBuffer(std::span<const uint8_t> data)
{
    if (data.empty() || data.back() != '\0')
    {
        return std::string();
    }

    // Last index is nul-terminator.
    return std::string(data.begin(), data.end() - 1);
}

Resp getBlobCount(ManagerInterface* mgr, std::span<const uint8_t>)
{
    struct BmcBlobCountRx resp;
    resp.crc = 0;
    resp.blobCount = mgr->buildBlobList();

    /* Copy the response into the reply buffer */
    std::vector<uint8_t> output(sizeof(BmcBlobCountRx), 0);
    std::memcpy(output.data(), &resp, sizeof(resp));

    return ipmi::responseSuccess(output);
}

Resp enumerateBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    /* Verify datalen is >= sizeof(request) */
    struct BmcBlobEnumerateTx request;

    std::memcpy(&request, data.data(), sizeof(request));

    std::string blobId = mgr->getBlobId(request.blobIdx);
    if (blobId.empty())
    {
        return ipmi::responseInvalidFieldRequest();
    }

    std::vector<uint8_t> output(sizeof(BmcBlobEnumerateRx), 0);
    output.insert(output.end(), blobId.c_str(),
                  blobId.c_str() + blobId.length() + 1);
    return ipmi::responseSuccess(output);
}

Resp openBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    auto request = reinterpret_cast<const struct BmcBlobOpenTx*>(data.data());
    uint16_t session;

    std::string path = stringFromBuffer(data.subspan(sizeof(BmcBlobOpenTx)));
    if (path.empty())
    {
        return ipmi::responseReqDataLenInvalid();
    }

    /* Attempt to open. */
    if (!mgr->open(request->flags, path, &session))
    {
        return ipmi::responseUnspecifiedError();
    }

    struct BmcBlobOpenRx reply;
    reply.crc = 0;
    reply.sessionId = session;

    std::vector<uint8_t> output(sizeof(BmcBlobOpenRx), 0);
    std::memcpy(output.data(), &reply, sizeof(reply));
    return ipmi::responseSuccess(output);
}

Resp closeBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    struct BmcBlobCloseTx request;
    if (data.size() < sizeof(request))
    {
        return ipmi::responseReqDataLenInvalid();
    }
    std::memcpy(&request, data.data(), sizeof(request));

    /* Attempt to close. */
    if (!mgr->close(request.sessionId))
    {
        return ipmi::responseUnspecifiedError();
    }

    return ipmi::responseSuccess(std::vector<uint8_t>{});
}

Resp deleteBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    std::string path = stringFromBuffer(data.subspan(sizeof(BmcBlobDeleteTx)));
    if (path.empty())
    {
        return ipmi::responseReqDataLenInvalid();
    }

    /* Attempt to delete. */
    if (!mgr->deleteBlob(path))
    {
        return ipmi::responseUnspecifiedError();
    }

    return ipmi::responseSuccess(std::vector<uint8_t>{});
}

static Resp returnStatBlob(BlobMeta* meta)
{
    struct BmcBlobStatRx reply;
    reply.crc = 0;
    reply.blobState = meta->blobState;
    reply.size = meta->size;
    reply.metadataLen = meta->metadata.size();

    std::vector<uint8_t> output(sizeof(BmcBlobStatRx), 0);
    std::memcpy(output.data(), &reply, sizeof(reply));

    /* If there is metadata, insert it to output. */
    if (!meta->metadata.empty())
    {
        output.insert(output.end(), meta->metadata.begin(),
                      meta->metadata.end());
    }
    return ipmi::responseSuccess(output);
}

Resp statBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    std::string path = stringFromBuffer(data.subspan(sizeof(BmcBlobStatTx)));
    if (path.empty())
    {
        return ipmi::responseReqDataLenInvalid();
    }

    /* Attempt to stat. */
    BlobMeta meta;
    if (!mgr->stat(path, &meta))
    {
        return ipmi::responseUnspecifiedError();
    }

    return returnStatBlob(&meta);
}

Resp sessionStatBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    struct BmcBlobSessionStatTx request;
    if (data.size() < sizeof(request))
    {
        return ipmi::responseReqDataLenInvalid();
    }
    std::memcpy(&request, data.data(), sizeof(request));

    /* Attempt to stat. */
    BlobMeta meta;

    if (!mgr->stat(request.sessionId, &meta))
    {
        return ipmi::responseUnspecifiedError();
    }

    return returnStatBlob(&meta);
}

Resp commitBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    auto request = reinterpret_cast<const struct BmcBlobCommitTx*>(data.data());

    /* Sanity check the commitDataLen */
    if (request->commitDataLen > (data.size() - sizeof(struct BmcBlobCommitTx)))
    {
        return ipmi::responseReqDataLenInvalid();
    }

    data = data.subspan(sizeof(struct BmcBlobCommitTx), request->commitDataLen);

    if (!mgr->commit(request->sessionId,
                     std::vector<uint8_t>(data.begin(), data.end())))
    {
        return ipmi::responseUnspecifiedError();
    }

    return ipmi::responseSuccess(std::vector<uint8_t>{});
}

Resp readBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    struct BmcBlobReadTx request;
    if (data.size() < sizeof(request))
    {
        return ipmi::responseReqDataLenInvalid();
    }
    std::memcpy(&request, data.data(), sizeof(request));

    std::vector<uint8_t> result =
        mgr->read(request.sessionId, request.offset, request.requestedSize);

    /* If the Read fails, it returns success but with only the crc and 0 bytes
     * of data.
     * If there was data returned, copy into the reply buffer.
     */
    std::vector<uint8_t> output(sizeof(BmcBlobReadRx), 0);

    if (!result.empty())
    {
        output.insert(output.end(), result.begin(), result.end());
    }

    return ipmi::responseSuccess(output);
}

Resp writeBlob(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    auto request = reinterpret_cast<const struct BmcBlobWriteTx*>(data.data());
    data = data.subspan(sizeof(struct BmcBlobWriteTx));

    /* Attempt to write the bytes. */
    if (!mgr->write(request->sessionId, request->offset,
                    std::vector<uint8_t>(data.begin(), data.end())))
    {
        return ipmi::responseUnspecifiedError();
    }

    return ipmi::responseSuccess(std::vector<uint8_t>{});
}

Resp writeMeta(ManagerInterface* mgr, std::span<const uint8_t> data)
{
    struct BmcBlobWriteMetaTx request;
    if (data.size() < sizeof(request))
    {
        return ipmi::responseReqDataLenInvalid();
    }

    /* Copy over the request. */
    std::memcpy(&request, data.data(), sizeof(request));

    /* Nothing really else to validate, we just copy those bytes. */
    data = data.subspan(sizeof(struct BmcBlobWriteMetaTx));

    /* Attempt to write the bytes. */
    if (!mgr->writeMeta(request.sessionId, request.offset,
                        std::vector<uint8_t>(data.begin(), data.end())))
    {
        return ipmi::responseUnspecifiedError();
    }

    return ipmi::responseSuccess(std::vector<uint8_t>{});
}

} // namespace blobs