~aleteoryx/muditaos

0596951027284740bc5ca560284b062ec132c4d0 — Marek Niepieklo 4 years ago 399acf8
[CP-280] Implement send file procedure

Implemented Send File operation on FS endpoint
M module-services/service-desktop/endpoints/filesystem/FileContext.cpp => module-services/service-desktop/endpoints/filesystem/FileContext.cpp +113 -16
@@ 4,13 4,45 @@
#include "FileContext.hpp"
#include <log.hpp>

FileContext::FileContext(std::filesystem::path path, std::size_t size, std::size_t offset, std::size_t chunkSize)
FileContext::FileContext(
    std::filesystem::path path, std::size_t size, std::size_t chunkSize, std::string openMode, std::size_t offset)
    : path(path), size(size), offset(offset), chunkSize(chunkSize)
{
    if (!size || !chunkSize) {
        throw std::invalid_argument("Invalid FileContext arguments");
    }
};

    file = std::fopen(path.c_str(), openMode.c_str());

    if (!file) {
        throw std::runtime_error("File open error");
    }

    runningCrc32Digest.reset();
}

FileContext::~FileContext()
{
    std::fclose(file);
}

FileReadContext::FileReadContext(std::filesystem::path path,
                                 std::size_t size,
                                 std::size_t chunkSize,
                                 std::size_t offset)
    : FileContext(path, size, chunkSize, "rb", offset)
{}

FileReadContext::~FileReadContext()
{}

FileWriteContext::FileWriteContext(
    std::filesystem::path path, std::size_t size, std::size_t chunkSize, std::string crc32Digest, std::size_t offset)
    : FileContext(path, size, chunkSize, "wb", offset), crc32Digest(crc32Digest)
{}

FileWriteContext::~FileWriteContext()
{}

auto FileContext::advanceFileOffset(std::size_t bySize) -> void
{


@@ 42,35 74,56 @@ auto FileContext::validateChunkRequest(std::uint32_t chunkNo) const -> bool
    return !(chunkNo < 1 || chunkNo > totalChunksInFile() || chunkNo != expectedChunkInFile());
}

auto FileContext::getDataForFile() -> std::vector<std::uint8_t>
auto FileReadContext::getFileHash() -> std::string
{
    LOG_DEBUG("Getting file data");
    std::vector<std::uint8_t> buffer(chunkSize);

    auto fileCloser = [](std::FILE *stream) { std::fclose(stream); };
    auto dataSize = size;

    std::unique_ptr<std::FILE, decltype(fileCloser)> file(std::fopen(path.c_str(), "rb"), fileCloser);
    while (dataSize) {

    if (!file) {
        LOG_ERROR("File open error");
        return {};
        auto dataLeft = std::min(static_cast<std::size_t>(chunkSize), dataSize);
        auto dataRead = std::fread(buffer.data(), sizeof(int8_t), dataLeft, file);

        if (dataRead != dataLeft) {
            LOG_ERROR("File %s read error", path.c_str());
            throw std::runtime_error("File read error");
        }

        runningCrc32Digest.add(buffer.data(), dataRead);

        dataSize -= dataRead;
    }

    std::fseek(file.get(), offset, SEEK_SET);
    std::fseek(file, 0, SEEK_SET);

    auto fileHash = runningCrc32Digest.getHash();

    runningCrc32Digest.reset();

    return fileHash;
}

auto FileReadContext::read() -> std::vector<std::uint8_t>
{
    LOG_DEBUG("Getting file data");

    std::fseek(file, offset, SEEK_SET);

    auto dataLeft = std::min(static_cast<std::size_t>(chunkSize), (size - offset));

    std::vector<std::uint8_t> buffer(dataLeft);

    auto dataRead = std::fread(buffer.data(), sizeof(uint8_t), dataLeft, file.get());

    LOG_DEBUG("Read %u bytes", static_cast<unsigned int>(dataRead));
    auto dataRead = std::fread(buffer.data(), sizeof(int8_t), dataLeft, file);

    if (dataRead != dataLeft) {
        LOG_ERROR("File read error");
        return {};
        LOG_ERROR("File %s read error", path.c_str());
        throw std::runtime_error("File read error");
    }

    advanceFileOffset(buffer.size());
    LOG_DEBUG("Read %u bytes", static_cast<unsigned int>(dataRead));

    advanceFileOffset(dataRead);

    if (reachedEOF()) {
        LOG_INFO("Reached EOF");


@@ 78,3 131,47 @@ auto FileContext::getDataForFile() -> std::vector<std::uint8_t>

    return buffer;
}

auto FileWriteContext::write(const std::vector<std::uint8_t> &data) -> void
{
    LOG_DEBUG("Sending file data");

    std::fseek(file, offset, SEEK_SET);

    auto dataLeft = std::min(static_cast<std::size_t>(chunkSize), (size - offset));

    auto dataWritten = std::fwrite(reinterpret_cast<const char *>(data.data()), sizeof(int8_t), dataLeft, file);

    if (dataWritten != dataLeft) {
        LOG_ERROR("File %s write error", path.c_str());
        throw std::runtime_error("File write error");
    }

    runningCrc32Digest.add(data.data(), dataWritten);

    LOG_DEBUG("Written %u bytes", static_cast<unsigned int>(dataWritten));

    advanceFileOffset(dataWritten);

    if (reachedEOF()) {
        LOG_INFO("Reached EOF of %s", path.c_str());
    }

    return;
}

auto FileWriteContext::fileHash() const -> std::string
{
    return runningCrc32Digest.getHash();
}

auto FileWriteContext::crc32Matches() const -> bool
{
    LOG_DEBUG("Hash: %s", fileHash().c_str());
    return crc32Digest.compare(fileHash()) == 0;
}

auto FileWriteContext::removeFile() -> void
{
    std::filesystem::remove(path);
}

M module-services/service-desktop/endpoints/filesystem/FileContext.hpp => module-services/service-desktop/endpoints/filesystem/FileContext.hpp +55 -5
@@ 3,14 3,26 @@

#pragma once

#include "Service/SystemReturnCodes.hpp"
#include <crc32.h>

#include <filesystem>
#include <vector>
#include <map>
#include <atomic>
#include <fstream>
#include <iostream>

struct FileContext
class FileContext
{
    explicit FileContext(std::filesystem::path path, std::size_t size, std::size_t offset, std::size_t chunkSize);
  public:
    explicit FileContext(std::filesystem::path path,
                         std::size_t size,
                         std::size_t chunkSize,
                         std::string openMode,
                         std::size_t offset = 0);

    virtual ~FileContext();

    auto validateChunkRequest(std::uint32_t chunkNo) const -> bool;



@@ 24,11 36,49 @@ struct FileContext

    auto expectedChunkInFile() const -> std::size_t;

    auto getDataForFile() -> std::vector<std::uint8_t>;

  private:
  protected:
    std::filesystem::path path{};
    std::FILE *file{};
    std::size_t size{};
    std::size_t offset{};
    std::size_t chunkSize{};
    CRC32 runningCrc32Digest;
};

class FileReadContext : public FileContext
{
  public:
    explicit FileReadContext(std::filesystem::path path,
                             std::size_t size,
                             std::size_t chunkSize,
                             std::size_t offset = 0);

    ~FileReadContext();

    auto getFileHash() -> std::string;

    auto read() -> std::vector<std::uint8_t>;
};

class FileWriteContext : public FileContext
{
  public:
    explicit FileWriteContext(std::filesystem::path path,
                              std::size_t size,
                              std::size_t chunkSize,
                              std::string crc32Digest,
                              std::size_t offset = 0);

    ~FileWriteContext();

    auto fileHash() const -> std::string;

    auto crc32Matches() const -> bool;

    auto removeFile() -> void;

    auto write(const std::vector<std::uint8_t> &data) -> void;

  private:
    std::string crc32Digest{};
};

M module-services/service-desktop/endpoints/filesystem/FileOperations.cpp => module-services/service-desktop/endpoints/filesystem/FileOperations.cpp +149 -24
@@ 14,50 14,73 @@ FileOperations &FileOperations::instance()

auto FileOperations::createReceiveIDForFile(const std::filesystem::path &file) -> std::pair<transfer_id, std::size_t>
{
    cancelTimedOutTransfer();
    const auto rxID = ++runningRxId;
    cancelTimedOutReadTransfer();
    const auto rxID = ++runningXfrId;
    const auto size = std::filesystem::file_size(file);

    if (!size) {
        LOG_ERROR("File is empty");
        return std::make_pair(0, 0);
    if (size == 0) {
        LOG_ERROR("File %s is empty", file.c_str());
        throw std::runtime_error("File size error");
    }

    LOG_DEBUG("Creating rxID %u", static_cast<unsigned>(rxID));

    try {
        createFileContextFor(file, size, rxID);
    createFileReadContextFor(file, size, rxID);

    return std::make_pair(rxID, size);
}

void FileOperations::cancelTimedOutReadTransfer()
{
    if (!runningXfrId) {
        return;
    }
    catch (std::exception &e) {
        LOG_ERROR("FileOperations::createFileContextFor() exception");
        return std::make_pair(0, 0);

    auto timedOutXfer       = runningXfrId - 1;
    const auto fileCtxEntry = readTransfers.find(timedOutXfer);

    if (fileCtxEntry == readTransfers.end()) {
        LOG_DEBUG("No timed out transfers");
        return;
    }

    return std::make_pair(rxID, size);
    LOG_DEBUG("Canceling timed out rxID %u", static_cast<unsigned>(timedOutXfer));
    readTransfers.erase(timedOutXfer);
}

void FileOperations::cancelTimedOutTransfer()
void FileOperations::cancelTimedOutWriteTransfer()
{
    if (!runningRxId) {
    if (!runningXfrId) {
        return;
    }

    auto timedOutXfer       = runningRxId - 1;
    const auto fileCtxEntry = transfers.find(timedOutXfer);
    auto timedOutXfer       = runningXfrId - 1;
    const auto fileCtxEntry = writeTransfers.find(timedOutXfer);

    if (fileCtxEntry == transfers.end()) {
    if (fileCtxEntry == writeTransfers.end()) {
        LOG_DEBUG("No timed out transfers");
        return;
    }

    LOG_DEBUG("Canceling timed out rxID %u", static_cast<unsigned>(timedOutXfer));
    transfers.erase(timedOutXfer);
    writeTransfers.erase(timedOutXfer);
}

auto FileOperations::createFileContextFor(const std::filesystem::path &file, std::size_t fileSize, transfer_id rxId)
    -> void
auto FileOperations::createFileReadContextFor(const std::filesystem::path &file,
                                              std::size_t fileSize,
                                              transfer_id xfrId) -> void
{
    transfers.insert(std::make_pair(rxId, std::make_unique<FileContext>(file, fileSize, 0, FileOperations::ChunkSize)));
    readTransfers.insert(
        std::make_pair(xfrId, std::make_unique<FileReadContext>(file, fileSize, FileOperations::ChunkSize)));
}

auto FileOperations::createFileWriteContextFor(const std::filesystem::path &file,
                                               std::size_t fileSize,
                                               const std::string Crc32,
                                               transfer_id xfrId) -> void
{
    writeTransfers.insert(
        std::make_pair(xfrId, std::make_unique<FileWriteContext>(file, fileSize, FileOperations::ChunkSize, Crc32)));
}

auto FileOperations::encodedSize(std::size_t binarySize) const -> std::size_t


@@ 68,6 91,14 @@ auto FileOperations::encodedSize(std::size_t binarySize) const -> std::size_t
    return ((binarySize + Mod3MaxReminder) / Base64ToBinFactor * BinToBase64Factor) + 1;
}

auto FileOperations::decodedSize(std::size_t encodedSize) const -> std::size_t
{
    /* 4 bytes of encoded data is converted back into 3 bytes of binary
        ((bytes + 3) / 4) * 3;
    */
    return ((((encodedSize + Base64ToBinFactor) / BinToBase64Factor) * Base64ToBinFactor) + 1);
}

auto FileOperations::encodeDataAsBase64(const std::vector<std::uint8_t> &binaryData) const -> std::string
{
    const auto encodedDataSize = encodedSize(binaryData.size());


@@ 79,13 110,45 @@ auto FileOperations::encodeDataAsBase64(const std::vector<std::uint8_t> &binaryD
    return encodedData;
}

auto FileOperations::decodeDataFromBase64(const std::string &encodedData) const -> std::vector<std::uint8_t>
{
    const auto decodedDataSize = decodedSize(encodedData.length());

    std::vector<std::uint8_t> decodedData(decodedDataSize, 0);

    b64tobin(decodedData.data(), encodedData.data());

    return decodedData;
}

auto FileOperations::getFileHashForReceiveID(transfer_id rxID) -> std::string
{
    LOG_DEBUG("Getting hash for rxID %u", static_cast<unsigned>(rxID));

    const auto fileCtxEntry = readTransfers.find(rxID);

    if (fileCtxEntry == readTransfers.end()) {
        LOG_ERROR("Invalid rxID %u", static_cast<unsigned>(rxID));
        return {};
    }

    auto fileCtx = fileCtxEntry->second.get();

    if (!fileCtx) {
        LOG_ERROR("Invalid fileCtx for rxID %u", static_cast<unsigned>(rxID));
        return {};
    }

    return fileCtx->getFileHash();
}

auto FileOperations::getDataForReceiveID(transfer_id rxID, std::uint32_t chunkNo) -> std::string
{
    LOG_DEBUG("Getting data for rxID %u", static_cast<unsigned>(rxID));

    const auto fileCtxEntry = transfers.find(rxID);
    const auto fileCtxEntry = readTransfers.find(rxID);

    if (fileCtxEntry == transfers.end()) {
    if (fileCtxEntry == readTransfers.end()) {
        LOG_ERROR("Invalid rxID %u", static_cast<unsigned>(rxID));
        return {};
    }


@@ 102,7 165,7 @@ auto FileOperations::getDataForReceiveID(transfer_id rxID, std::uint32_t chunkNo
        return {};
    }

    const auto data = fileCtx->getDataForFile();
    const auto data = fileCtx->read();

    if (data.empty()) {
        LOG_ERROR("File read error");


@@ 111,8 174,70 @@ auto FileOperations::getDataForReceiveID(transfer_id rxID, std::uint32_t chunkNo

    if (fileCtx->reachedEOF()) {
        LOG_INFO("Reached EOF for rxID %u", static_cast<unsigned>(rxID));
        transfers.erase(rxID);

        writeTransfers.erase(rxID);
    }

    return encodeDataAsBase64(data);
}

auto FileOperations::createTransmitIDForFile(const std::filesystem::path &file,
                                             std::size_t size,
                                             const std::string Crc32) -> transfer_id
{
    cancelTimedOutWriteTransfer();
    const auto txID = ++runningXfrId;

    LOG_DEBUG("Creating rxID %u", static_cast<unsigned>(txID));

    createFileWriteContextFor(file, size, Crc32, txID);

    return txID;
}

auto FileOperations::sendDataForTransmitID(transfer_id txID, std::uint32_t chunkNo, const std::string &data)
    -> sys::ReturnCodes
{
    LOG_DEBUG("Transmitting data for txID %u", static_cast<unsigned>(txID));
    auto returnCode = sys::ReturnCodes::Success;

    const auto fileCtxEntry = writeTransfers.find(txID);

    if (fileCtxEntry == writeTransfers.end()) {
        LOG_ERROR("Invalid txID %u", static_cast<unsigned>(txID));
        return sys::ReturnCodes::Failure;
    }

    auto fileCtx = fileCtxEntry->second.get();

    if (!fileCtx) {
        LOG_ERROR("Invalid fileCtx for txID %u", static_cast<unsigned>(txID));
        return sys::ReturnCodes::Failure;
    }

    if (!fileCtx->validateChunkRequest(chunkNo)) {
        LOG_ERROR("Invalid chunkNo %u", static_cast<unsigned>(chunkNo));
        return sys::ReturnCodes::Failure;
    }

    auto binaryData = decodeDataFromBase64(data);

    fileCtx->write(binaryData);

    if (fileCtx->reachedEOF()) {
        LOG_INFO("Reached EOF for txID %u", static_cast<unsigned>(txID));

        auto fileOK = fileCtx->crc32Matches();

        if (!fileOK) {
            LOG_ERROR("File CRC32 mismatch");
            fileCtx->removeFile();
            writeTransfers.erase(txID);

            throw std::runtime_error("File CRC32 mismatch");
        }
        writeTransfers.erase(txID);
    }

    return returnCode;
}

M module-services/service-desktop/endpoints/filesystem/FileOperations.hpp => module-services/service-desktop/endpoints/filesystem/FileOperations.hpp +25 -5
@@ 20,27 20,47 @@ class FileOperations

    using transfer_id = std::uint32_t;

    std::map<transfer_id, std::unique_ptr<FileContext>> transfers;
    std::map<transfer_id, std::unique_ptr<FileReadContext>> readTransfers;
    std::map<transfer_id, std::unique_ptr<FileWriteContext>> writeTransfers;

    std::atomic<transfer_id> runningRxId{0};
    std::atomic<transfer_id> runningXfrId{0};

    auto createFileContextFor(const std::filesystem::path &file, std::size_t fileSize, transfer_id rxId) -> void;
    auto createFileReadContextFor(const std::filesystem::path &file, std::size_t fileSize, transfer_id xfrId) -> void;

    auto createFileWriteContextFor(const std::filesystem::path &file,
                                   std::size_t fileSize,
                                   const std::string Crc32,
                                   transfer_id xfrId) -> void;

    auto encodeDataAsBase64(const std::vector<std::uint8_t> &binaryData) const -> std::string;

    auto decodeDataFromBase64(const std::string &encodedData) const -> std::vector<std::uint8_t>;

    auto encodedSize(std::size_t binarySize) const -> std::size_t;

    auto cancelTimedOutTransfer() -> void;
    auto decodedSize(std::size_t encodedSize) const -> std::size_t;

    auto cancelTimedOutReadTransfer() -> void;

    auto cancelTimedOutWriteTransfer() -> void;

  public:
    static constexpr auto ChunkSize         = 3 * 1024u;
    static constexpr auto BinToBase64Factor = 4u;
    static constexpr auto Base64ToBinFactor = 3u;
    static constexpr auto Mod3MaxReminder   = 2u;
    // ChunkSize must be a multiple of 3 and 4 so that ChunkSize % 12 == 0
    static constexpr auto ChunkSize = Base64ToBinFactor * BinToBase64Factor * 1024u;

    static FileOperations &instance();

    auto createReceiveIDForFile(const std::filesystem::path &file) -> std::pair<transfer_id, std::size_t>;

    auto getDataForReceiveID(transfer_id, std::uint32_t chunkNo) -> std::string;

    auto getFileHashForReceiveID(transfer_id rxID) -> std::string;

    auto createTransmitIDForFile(const std::filesystem::path &file, std::size_t size, const std::string Crc32)
        -> transfer_id;

    auto sendDataForTransmitID(transfer_id, std::uint32_t chunkNo, const std::string &data) -> sys::ReturnCodes;
};

M module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.cpp => module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.cpp +168 -11
@@ 5,7 5,6 @@
#include "FileOperations.hpp"
#include "service-desktop/DesktopMessages.hpp"
#include "service-desktop/ServiceDesktop.hpp"
#include <purefs/filesystem_paths.hpp>
#include <filesystem>

using namespace parserFSM;


@@ 20,6 19,9 @@ auto FilesystemEndpoint::handle(Context &context) -> void
    case http::Method::post:
        returnCode = runPost(context);
        break;
    case http::Method::put:
        returnCode = runPut(context);
        break;
    default:
        LOG_ERROR("Unhandled method request: %u", static_cast<unsigned>(context.getMethod()));
        returnCode = sys::ReturnCodes::Failure;


@@ 50,21 52,32 @@ auto FilesystemEndpoint::startGetFile(Context &context) const -> sys::ReturnCode

    LOG_DEBUG("Checking file");

    auto [rxID, fileSize] = fileOps.createReceiveIDForFile(filePath);
    try {
        auto [rxID, fileSize] = fileOps.createReceiveIDForFile(filePath);

        auto fileHash = fileOps.getFileHashForReceiveID(rxID);

    if (!fileSize) {
        LOG_ERROR("File is corrupted");
        context.setResponseStatus(parserFSM::http::Code::OK);
        context.setResponseBody(
            json11::Json::object({{json::filesystem::rxID, static_cast<int>(rxID)},
                                  {json::fileSize, static_cast<int>(fileSize)},
                                  {json::fileCrc32, fileHash},
                                  {json::filesystem::chunkSize, static_cast<int>(FileOperations::ChunkSize)}}));
    }
    catch (std::runtime_error &e) {
        LOG_ERROR("FileOperations exception: %s", e.what());

        context.setResponseStatus(parserFSM::http::Code::InternalServerError);
        context.setResponseBody(json11::Json::object({{json::reason, json::filesystem::reasons::fileDoesNotExist}}));
        context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
        return sys::ReturnCodes::Failure;
    }
    catch (std::exception &e) {
        LOG_ERROR("FileOperations exception: %s", e.what());

    context.setResponseStatus(parserFSM::http::Code::OK);
    context.setResponseBody(
        json11::Json::object({{json::filesystem::rxID, static_cast<int>(rxID)},
                              {json::fileSize, static_cast<int>(fileSize)},
                              {json::filesystem::chunkSize, static_cast<int>(FileOperations::ChunkSize)}}));
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
        return sys::ReturnCodes::Failure;
    }

    return sys::ReturnCodes::Success;
}


@@ 74,8 87,18 @@ auto FilesystemEndpoint::getFileChunk(Context &context) const -> sys::ReturnCode
    auto returnCode    = sys::ReturnCodes::Failure;
    const auto rxID    = context.getBody()[parserFSM::json::filesystem::rxID].int_value();
    const auto chunkNo = context.getBody()[parserFSM::json::filesystem::chunkNo].int_value();
    std::string data{};

    try {
        data = fileOps.getDataForReceiveID(rxID, chunkNo);
    }
    catch (std::exception &e) {
        LOG_ERROR("%s", e.what());
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        context.setResponseBody(json11::Json::object({{json::reason, e.what()}}));

    const auto data = fileOps.getDataForReceiveID(rxID, chunkNo);
        return sys::ReturnCodes::Failure;
    }

    if (data.length()) {
        context.setResponseStatus(parserFSM::http::Code::OK);


@@ 108,6 131,11 @@ auto FilesystemEndpoint::runGet(Context &context) -> sys::ReturnCodes
    else if (context.getBody()[parserFSM::json::filesystem::rxID].is_number()) {
        returnCode = getFileChunk(context);
    }
    else {
        LOG_ERROR("unknown request");
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        returnCode = sys::ReturnCodes::Failure;
    }

    MessageHandler::putToSendQueue(context.createSimpleResponse());



@@ 155,8 183,137 @@ auto FilesystemEndpoint::runPost(Context &context) -> sys::ReturnCodes
    }
    else {
        LOG_ERROR("unknown command");
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        returnCode = sys::ReturnCodes::Failure;
    }

    MessageHandler::putToSendQueue(context.createSimpleResponse());
    return returnCode;
}

auto FilesystemEndpoint::runPut(Context &context) -> sys::ReturnCodes
{
    LOG_DEBUG("Handling PUT");
    auto returnCode         = sys::ReturnCodes::Failure;
    const auto &requestBody = context.getBody();

    if (requestBody[parserFSM::json::fileName].is_string() && requestBody[parserFSM::json::fileSize].is_number() &&
        requestBody[parserFSM::json::fileCrc32].is_string()) {
        returnCode = startSendFile(context);
    }
    else if (requestBody[parserFSM::json::filesystem::txID].is_number() &&
             requestBody[parserFSM::json::filesystem::chunkNo].is_number() &&
             requestBody[parserFSM::json::filesystem::data].is_string()) {
        returnCode = sendFileChunk(context);
    }
    else {
        LOG_ERROR("unknown request");
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        returnCode = sys::ReturnCodes::Failure;
    }

    MessageHandler::putToSendQueue(context.createSimpleResponse());
    return returnCode;
}

auto FilesystemEndpoint::startSendFile(parserFSM::Context &context) const -> sys::ReturnCodes
{
    auto returnCode         = sys::ReturnCodes::Failure;
    const auto &requestBody = context.getBody();

    std::filesystem::path filePath = requestBody[parserFSM::json::fileName].string_value();
    const uint32_t fileSize        = requestBody[parserFSM::json::fileSize].int_value();
    const auto fileCrc32           = requestBody[parserFSM::json::fileCrc32].string_value();

    LOG_DEBUG("Start sending of file: %s", filePath.c_str());

    if (fileSize == 0 || fileCrc32.empty()) {
        LOG_ERROR("File %s corrupted", filePath.c_str());

        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        return returnCode;
    }

    if (!std::filesystem::exists(filePath)) {
        LOG_DEBUG("Creating file %s", filePath.c_str());

        context.setResponseStatus(parserFSM::http::Code::Created);
    }
    else {
        LOG_DEBUG("Overwriting file %s", filePath.c_str());
    }

    try {
        auto txID = fileOps.createTransmitIDForFile(filePath, fileSize, fileCrc32);

        context.setResponseStatus(parserFSM::http::Code::OK);
        context.setResponseBody(
            json11::Json::object({{json::filesystem::txID, static_cast<int>(txID)},
                                  {json::filesystem::chunkSize, static_cast<int>(FileOperations::ChunkSize)}}));
    }
    catch (std::runtime_error &e) {
        LOG_ERROR("FileOperations exception: %s", e.what());

        context.setResponseStatus(parserFSM::http::Code::InternalServerError);
        context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
        return sys::ReturnCodes::Failure;
    }
    catch (std::exception &e) {
        LOG_ERROR("FileOperations exception: %s", e.what());

        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
        return sys::ReturnCodes::Failure;
    }

    return sys::ReturnCodes::Success;
}

auto FilesystemEndpoint::sendFileChunk(parserFSM::Context &context) const -> sys::ReturnCodes
{
    auto returnCode         = sys::ReturnCodes::Failure;
    const auto &requestBody = context.getBody();
    const auto txID         = requestBody[parserFSM::json::filesystem::txID].int_value();
    const auto chunkNo      = requestBody[parserFSM::json::filesystem::chunkNo].int_value();
    const auto data         = requestBody[parserFSM::json::filesystem::data].string_value();

    if (data.empty()) {
        std::ostringstream errorReason;
        errorReason << "Invalid request txID: " << std::to_string(txID) << ", chunkNo: " << std::to_string(chunkNo);
        LOG_ERROR("%s", errorReason.str().c_str());

        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        context.setResponseBody(json11::Json::object({{json::reason, errorReason.str()}}));
        return returnCode;
    }

    try {
        returnCode = fileOps.sendDataForTransmitID(txID, chunkNo, data);
    }
    catch (std::exception &e) {
        LOG_ERROR("%s", e.what());
        context.setResponseStatus(parserFSM::http::Code::NotAcceptable);
        context.setResponseBody(json11::Json::object({{json::reason, e.what()}}));

        return sys::ReturnCodes::Failure;
    }

    if (returnCode == sys::ReturnCodes::Success) {
        LOG_DEBUG("FileOperations::sendDataForTransmitID success");
        context.setResponseStatus(parserFSM::http::Code::OK);
        context.setResponseBody(json11::Json::object({
            {json::filesystem::txID, static_cast<int>(txID)},
            {json::filesystem::chunkNo, static_cast<int>(chunkNo)},
        }));
    }
    else {
        LOG_ERROR("FileOperations::sendDataForTransmitID failed");
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
        context.setResponseBody(json11::Json::object({
            {json::filesystem::txID, static_cast<int>(txID)},
            {json::filesystem::chunkNo, static_cast<int>(chunkNo)},
        }));
    }

    return returnCode;
}

M module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.hpp => module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.hpp +4 -0
@@ 21,11 21,15 @@ class FilesystemEndpoint : public parserFSM::Endpoint
    auto handle(parserFSM::Context &context) -> void override;
    auto runGet(parserFSM::Context &context) -> sys::ReturnCodes;
    auto runPost(parserFSM::Context &context) -> sys::ReturnCodes;
    auto runPut(parserFSM::Context &context) -> sys::ReturnCodes;
    auto getUpdates(parserFSM::Context &context) -> sys::ReturnCodes;

  private:
    auto startGetFile(parserFSM::Context &context) const -> sys::ReturnCodes;
    auto getFileChunk(parserFSM::Context &context) const -> sys::ReturnCodes;

    auto startSendFile(parserFSM::Context &context) const -> sys::ReturnCodes;
    auto sendFileChunk(parserFSM::Context &context) const -> sys::ReturnCodes;

    FileOperations &fileOps;
};

M module-services/service-desktop/parser/HttpEnums.hpp => module-services/service-desktop/parser/HttpEnums.hpp +1 -0
@@ 12,6 12,7 @@ namespace parserFSM::http
    enum class Code
    {
        OK                  = 200,
        Created             = 201,
        Accepted            = 202,
        NoContent           = 204,
        SeeOther            = 303,

M module-services/service-desktop/tests/test-fs-ep-operations.cpp => module-services/service-desktop/tests/test-fs-ep-operations.cpp +95 -5
@@ 38,19 38,106 @@ TEST_CASE("Endpoint Filesystem Test")
{
    messageStrings.clear();

    SECTION("Request send file")
    {
        auto endpoint    = 3;
        auto uuid        = 1103;
        auto fileToSend  = "\"/sys/user/data/applications/settings/quotes.json\"";
        auto fileSize    = 676u;
        auto fileCrc32   = "\"37ef9a52\"";
        auto chunkSize   = 12288u;
        auto txID        = 1u;
        auto testMessage = "{\"endpoint\":" + std::to_string(endpoint) +
                           ", \"method\": 3, \"uuid\":" + std::to_string(uuid) +
                           ", \"body\":{\"fileName\":" + fileToSend + " , \"fileSize\":" + std::to_string(fileSize) +
                           " , \"fileCrc32\":" + fileCrc32 + "}}";
        std::string err;
        auto msgJson = json11::Json::parse(testMessage, err);
        UNSCOPED_INFO(err.c_str());

        REQUIRE(err.empty());

        parserFSM::Context context(msgJson);
        auto factory = std::make_unique<SecuredEndpointFactory>(EndpointSecurity::Allow);
        auto handler = factory->create(context, nullptr);
        handler->handle(context);
        REQUIRE(1 == messageStrings.size());
        auto msg = messageStrings[0];
        REQUIRE(msg.size() > 10);
        auto retJson = json11::Json::parse(msg.substr(10), err);

        UNSCOPED_INFO(err.c_str());

        REQUIRE(err.empty());
        REQUIRE(uuid == retJson[parserFSM::json::uuid].int_value());
        REQUIRE(endpoint == retJson[parserFSM::json::endpoint].int_value());

        auto body = retJson[parserFSM::json::body];
        REQUIRE(chunkSize == static_cast<uint32_t>(body[parserFSM::json::filesystem::chunkSize].int_value()));
        REQUIRE(txID == static_cast<uint32_t>(body[parserFSM::json::filesystem::txID].int_value()));
    }

    SECTION("Request send file chunk")
    {
        auto endpoint = 3;
        auto uuid     = 2103;
        auto chunkNo  = 1u;
        auto txID     = 1u;
        auto data =
            "WwogIHsKICAgICJsYW5nIjoiZW5nbGlzaCIsCiAgICAiYXV0aG9yIjogIkJ1ZGRoYSIsCiAgICAicXVvdGUiOiAiRG8gbm90IGR3ZWxsIG"
            "luIHRoZSBwYXN0LCBkbyBub3QgZHJlYW0gb2YgdGhlIGZ1dHVyZSwgY29uY2VudHJhdGUgdGhlIG1pbmQgb24gdGhlIHByZXNlbnQgbW9t"
            "ZW50LiIKICB9LAogIHsKICAgICJsYW5nIjoiZW5nbGlzaCIsCiAgICAiYXV0aG9yIjogIlRhcmEgQnJhY2giLAogICAgInF1b3RlIjogIl"
            "RoZSBvbmx5IHdheSB0byBsaXZlIGlzIGJ5IGFjY2VwdGluZyBlYWNoIG1pbnV0ZSBhcyBhbiB1bnJlcGVhdGFibGUgbWlyYWNsZS4iCiAg"
            "fSwKICB7CiAgICAibGFuZyI6ImVuZ2xpc2giLAogICAgImF1dGhvciI6ICJSaWNoYXIgQmFjaCIsCiAgICAicXVvdGUiOiAiVGhlIHNpbX"
            "BsZXN0IHRoaW5ncyBhcmUgb2Z0ZW4gdGhlIHRydWVzdCIKICB9LAogIHsKICAgICJsYW5nIjoiZW5nbGlzaCIsCiAgICAiYXV0aG9yIjog"
            "IkVja2hhcnQgVG9sbGUiLAogICAgInF1b3RlIjogIkFsd2F5cyBzYXkgeWVzIHRvIHRoZSBwcmVzZW50IG1vbWVudC4gU2F5IHllcyB0by"
            "BsaWZlLiIKICB9LAogIHsKICAgICJsYW5nIjoiZW5nbGlzaCIsCiAgICAiYXV0aG9yIjogIk5hb21pIEp1ZGQiLAogICAgInF1b3RlIjog"
            "IlNsb3cgZG93biwgc2ltcGxpZnkgYW5kIGJlIGtpbmQiCiAgfQpdCg==";

        auto testMessage = "{\"endpoint\":" + std::to_string(endpoint) +
                           ", \"method\": 3, \"uuid\":" + std::to_string(uuid) +
                           ", \"body\":{\"txID\":" + std::to_string(txID) + ", \"chunkNo\":" + std::to_string(chunkNo) +
                           ", \"data\":" + "\"" + data + "\"" + "}}";

        std::string err;
        auto msgJson = json11::Json::parse(testMessage, err);
        UNSCOPED_INFO(err.c_str());

        REQUIRE(err.empty());

        parserFSM::Context context(msgJson);
        auto factory = std::make_unique<SecuredEndpointFactory>(EndpointSecurity::Allow);
        auto handler = factory->create(context, nullptr);
        handler->handle(context);
        REQUIRE(1 == messageStrings.size());
        auto msg = messageStrings[0];
        REQUIRE(msg.size() > 10);
        auto retJson = json11::Json::parse(msg.substr(10), err); // string length and go to real data
        UNSCOPED_INFO(err.c_str());

        REQUIRE(err.empty());
        REQUIRE(uuid == retJson[parserFSM::json::uuid].int_value());
        REQUIRE(endpoint == retJson[parserFSM::json::endpoint].int_value());

        auto body = retJson[parserFSM::json::body];
        REQUIRE(chunkNo == static_cast<uint32_t>(body[parserFSM::json::filesystem::chunkNo].int_value()));
        REQUIRE(txID == static_cast<uint32_t>(body[parserFSM::json::filesystem::txID].int_value()));
    }

    SECTION("Request get file")
    {
        auto endpoint    = 3;
        auto uuid        = 1103;
        auto fileToGet   = "\"/sys/user/data/applications/settings/quotes.json\"";
        auto fileSize    = 676u;
        auto chunkSize   = 3072u;
        auto rxID        = 1u;
        auto chunkSize   = 12288u;
        auto rxID        = 2u;
        auto testMessage = "{\"endpoint\":" + std::to_string(endpoint) +
                           ", \"method\":1, \"uuid\":" + std::to_string(uuid) +
                           ", \"body\":{\"fileName\":" + fileToGet + "}}";
        std::string err;
        auto msgJson = json11::Json::parse(testMessage, err);
        UNSCOPED_INFO(err.c_str());
        REQUIRE(err.empty());

        parserFSM::Context context(msgJson);


@@ 61,7 148,7 @@ TEST_CASE("Endpoint Filesystem Test")
        auto msg = messageStrings[0];
        REQUIRE(msg.size() > 10);
        auto retJson = json11::Json::parse(msg.substr(10), err); // string length and go to real data

        UNSCOPED_INFO(err.c_str());
        REQUIRE(err.empty());
        REQUIRE(uuid == retJson[parserFSM::json::uuid].int_value());
        REQUIRE(endpoint == retJson[parserFSM::json::endpoint].int_value());


@@ 77,7 164,7 @@ TEST_CASE("Endpoint Filesystem Test")
        auto endpoint = 3;
        auto uuid     = 2013;
        auto chunkNo  = 1u;
        auto rxID     = 1u;
        auto rxID     = 2u;
        auto testMessage =
            "{\"endpoint\":" + std::to_string(endpoint) + ", \"method\":1, \"uuid\":" + std::to_string(uuid) +
            ", \"body\":{\"rxID\":" + std::to_string(rxID) + ", \"chunkNo\":" + std::to_string(chunkNo) + "}}";


@@ 95,6 182,7 @@ TEST_CASE("Endpoint Filesystem Test")

        std::string err;
        auto msgJson = json11::Json::parse(testMessage, err);
        UNSCOPED_INFO(err.c_str());
        REQUIRE(err.empty());

        parserFSM::Context context(msgJson);


@@ 104,13 192,15 @@ TEST_CASE("Endpoint Filesystem Test")
        REQUIRE(1 == messageStrings.size());
        auto msg = messageStrings[0];
        REQUIRE(msg.size() > 10);
        auto retJson = json11::Json::parse(msg.substr(10), err); // string length and go to real data
        auto retJson = json11::Json::parse(msg.substr(10), err);
        UNSCOPED_INFO(err.c_str());

        REQUIRE(err.empty());
        REQUIRE(uuid == retJson[parserFSM::json::uuid].int_value());
        REQUIRE(endpoint == retJson[parserFSM::json::endpoint].int_value());

        auto body = retJson[parserFSM::json::body];
        UNSCOPED_INFO(body.string_value());
        REQUIRE(chunkNo == static_cast<uint32_t>(body[parserFSM::json::filesystem::chunkNo].int_value()));
        REQUIRE(rxID == static_cast<uint32_t>(body[parserFSM::json::filesystem::rxID].int_value()));


M module-services/service-desktop/tests/unittest.cpp => module-services/service-desktop/tests/unittest.cpp +64 -5
@@ 307,7 307,7 @@ TEST_CASE("Secured Endpoint Factory test")
    }
}

TEST_CASE("FileOperations UT Test")
TEST_CASE("FileOperations UT Test Get File")
{
    auto &fileOps = FileOperations::instance();



@@ 331,7 331,7 @@ TEST_CASE("FileContext UT Test Valid Input")

    SECTION("Create file context for file")
    {
        auto fileCtx = FileContext(filePath, fileSize, fileOffset, chunkSize);
        auto fileCtx = FileReadContext(filePath, fileSize, chunkSize, fileOffset);

        REQUIRE(3 == fileCtx.expectedChunkInFile());



@@ 348,14 348,13 @@ TEST_CASE("FileContext UT Test Valid Input")
TEST_CASE("FileContext UT Test Invalid Input")
{
    auto filePath{"/sys/user/music/SMS-drum2-stereo.mp3"};
    auto fileOffset{0u};

    SECTION("Create file context for file with invalid file size")
    {
        auto fileSize{0u};
        auto chunkSize{1024 * 3u};

        REQUIRE_THROWS_WITH(FileContext(filePath, fileSize, fileOffset, chunkSize), "Invalid FileContext arguments");
        REQUIRE_THROWS_WITH(FileReadContext(filePath, fileSize, chunkSize), "Invalid FileContext arguments");
    }

    SECTION("Create file context for file with invalid chunk size")


@@ 363,6 362,66 @@ TEST_CASE("FileContext UT Test Invalid Input")
        auto fileSize{49146u};
        auto chunkSize{0u};

        REQUIRE_THROWS_WITH(FileContext(filePath, fileSize, fileOffset, chunkSize), "Invalid FileContext arguments");
        REQUIRE_THROWS_WITH(FileReadContext(filePath, fileSize, chunkSize), "Invalid FileContext arguments");
    }
}

TEST_CASE("FileOperations UT Test Send File")
{
    auto &fileOps = FileOperations::instance();

    SECTION("Create receive id for file")
    {
        auto filePath{"/sys/user/music/SMS-drum2-stereo.mp3"};
        auto fileSize{49146};
        auto fileCrc32{"efd73581"};

        auto txID = fileOps.createTransmitIDForFile(filePath, fileSize, fileCrc32);

        REQUIRE(txID == 1);
    }
}

TEST_CASE("FileContext UT Test Valid Input")
{
    auto filePath{"/sys/user/MuditaOS.log"};
    auto fileSize{1536u};
    auto fileOffset{128 * 6u};
    auto chunkSize{128 * 3u};

    SECTION("Create file context for file")
    {
        auto fileCtx = FileReadContext(filePath, fileSize, chunkSize, fileOffset);

        REQUIRE(3 == fileCtx.expectedChunkInFile());

        REQUIRE(true == fileCtx.validateChunkRequest(3));
        REQUIRE(false == fileCtx.validateChunkRequest(4));

        REQUIRE(4 == fileCtx.totalChunksInFile());

        fileCtx.advanceFileOffset(fileSize - fileOffset);
        REQUIRE(true == fileCtx.reachedEOF());
    }
}

TEST_CASE("FileContext UT Test Invalid Input")
{
    auto filePath{"/sys/user/music/SMS-drum2-stereo.mp3"};

    SECTION("Create file context for file with invalid file size")
    {
        auto fileSize{0u};
        auto chunkSize{1024 * 3u};

        REQUIRE_THROWS_WITH(FileReadContext(filePath, fileSize, chunkSize), "Invalid FileContext arguments");
    }

    SECTION("Create file context for file with invalid chunk size")
    {
        auto fileSize{49146u};
        auto chunkSize{0u};

        REQUIRE_THROWS_WITH(FileReadContext(filePath, fileSize, chunkSize), "Invalid FileContext arguments");
    }
}

A test/lorem-ipsum.txt => test/lorem-ipsum.txt +34 -0
@@ 0,0 1,34 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu mattis tellus. Duis consectetur velit aliquet elit
feugiat pretium. Pellentesque  nunc diam, iaculis a euismod at,  dictum non velit. In pretium sapien  id ipsum vulputate
lobortis. Maecenas finibus auctor nisl et imperdiet. Nam ultrices nunc id neque interdum, sed dictum dui auctor. Aliquam
tristique nunc  quis justo imperdiet  aliquam. In vel  volutpat enim. Nunc aliquam,  libero et bibendum  lacinia, ligula
neque volutpat  nisl, id elementum risus  urna eu lectus.  Donec tempus risus  id magna dapibus venenatis.  Fusce lectus
ipsum,  vestibulum in  magna a,  blandit luctus  turpis. Fusce  neque orci,  euismod et  dui ut,  accumsan dapibus  est.
Curabitur quis nunc  ut risus pretium gravida. Phasellus  eget auctor nulla. Nunc a convallis  ex. Phasellus ullamcorper
scelerisque mi sed rutrum.

Donec mollis  tortor euismod  posuere porttitor. Praesent  pretium bibendum  risus, in pellentesque  sem aliquam  eu. Ut
volutpat velit  vel lacus scelerisque,  sed luctus ligula  aliquam. Aliquam sem  ex, sodales sed  condimentum fermentum,
rhoncus condimentum  purus. Mauris non  cursus enim, at  pulvinar massa. Sed porta  turpis at elit  condimentum laoreet.
Maecenas leo purus, pellentesque ac volutpat quis, sodales sit  amet est. Nam justo velit, laoreet in odio in, imperdiet
fringilla leo.  Nunc at  porttitor eros,  ac pellentesque  ante. Etiam dignissim  nibh nisl.  In volutpat  eleifend arcu
tristique ultricies.  Vestibulum eget  feugiat metus. Proin  in odio velit.  Donec varius  purus et metus  egestas, quis
pretium elit tempus.

Vivamus vel malesuada  diam. Ut vel leo  faucibus, laoreet lectus vel,  varius lectus. Etiam luctus  consequat mauris ut
vulputate. Nullam ut  dolor nulla. Sed bibendum velit nec  odio egestas ultricies. Cras eu tempus  lorem. Sed non lectus
elit. Vivamus dapibus felis vel accumsan posuere. Suspendisse sodales cursus viverra.

Mauris erat quam, lacinia a  porttitor vel, egestas eget tortor. In egestas ac augue  posuere congue. Donec ac tincidunt
nibh, et suscipit enim.  Aliquam erat volutpat. Suspendisse nec eros a urna  molestie auctor. Phasellus interdum lacinia
enim sit  amet efficitur. Sed quis  sem eget mi tristique  ornare quis sit amet  dui. Nunc neque dolor,  consequat vitae
auctor faucibus, rhoncus et nibh. Aenean mi purus, tincidunt  eu pharetra sit amet, hendrerit ut ipsum. Cras iaculis non
diam sed  finibus. Vivamus  eleifend facilisis lacus,  sed aliquet  est. Sed pellentesque  malesuada nisl  nec volutpat.
Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas ut turpis sit amet lorem pretium faucibus.

Donec nunc justo, dignissim molestie faucibus imperdiet, mattis id urna. Ut pulvinar, sapien non molestie viverra, velit
arcu porta elit, eu  commodo nisi mi a metus. Curabitur  leo justo, congue ac eros a,  condimentum consectetur dolor. In
vel sapien iaculis, consectetur lacus at, faucibus leo. Sed ut semper dui, eu eleifend magna. Nulla vel lorem vitae orci
ultrices molestie  vitae a nisi. Sed  sed enim id  ligula aliquet vehicula.  Duis tincidunt tristique dui  ac consequat.
Proin ac  vulputate lacus.  Proin imperdiet  luctus tellus dapibus  laoreet. Suspendisse  potenti. Curabitur  ut finibus
mauris. Duis dignissim pellentesque sagittis. Vestibulum ut leo non metus porttitor vehicula placerat posuere ante.

M test/pytest/service-desktop/test_get_file.py => test/pytest/service-desktop/test_get_file.py +21 -3
@@ 4,6 4,11 @@ import pytest
import base64
from harness.interface.defs import status

def setPasscode(harness, flag):
    body = {"phoneLockCodeEnabled": flag}
    ret = harness.endpoint_request("developerMode", "put", body)
    assert ret["status"] == status["NoContent"]

@pytest.mark.service_desktop_test
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.rt1051


@@ 11,6 16,13 @@ def test_get_not_existing_file(harness):
    """
    Attempt requesting not exiting file
    """

    """
    Getting a large file may hit screen auto lock.
    We need to disable pass code for duration of test
    """
    setPasscode(harness, False)

    fileName = "Unknown.file"
    body = {"fileName" : "/sys/user/" + fileName}
    ret = harness.endpoint_request("filesystem", "get", body)


@@ 36,7 48,7 @@ def test_get_invalid_chunks(harness):
    fileSize  = ret["body"]["fileSize"]
    chunkSize = ret["body"]["chunkSize"]

    totalChunks = int((fileSize/chunkSize) + 1)
    totalChunks = int(((fileSize + chunkSize - 1) / chunkSize))
    print("totalChunks #: " + str(totalChunks))

    body = {"rxID" : rxID, "chunkNo": 0}


@@ 68,7 80,7 @@ def test_get_file(harness):
    fileSize  = ret["body"]["fileSize"]
    chunkSize = ret["body"]["chunkSize"]

    totalChunks = int((fileSize/chunkSize) + 1)
    totalChunks = int(((fileSize + chunkSize - 1) / chunkSize))
    print("totalChunks #: " + str(totalChunks))

    data = ""


@@ 107,7 119,7 @@ def test_get_invalid_rxID(harness):
    fileSize  = ret["body"]["fileSize"]
    chunkSize = ret["body"]["chunkSize"]

    totalChunks = int((fileSize/chunkSize) + 1)
    totalChunks = int(((fileSize + chunkSize - 1) / chunkSize))
    print("totalChunks #: " + str(totalChunks))

    body = {"rxID" : int(rxID - 1), "chunkNo": 1}


@@ 119,3 131,9 @@ def test_get_invalid_rxID(harness):
    ret = harness.endpoint_request("filesystem", "get", body)

    assert ret["status"] == status["BadRequest"]

    """
    Getting a large file may hit screen auto lock.
    We need to disable pass code for duration of test
    """
    setPasscode(harness, True)

A test/pytest/service-desktop/test_send_file.py => test/pytest/service-desktop/test_send_file.py +87 -0
@@ 0,0 1,87 @@
# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
import binascii
import os.path

from harness.interface.defs import status
from functools import partial

@pytest.mark.service_desktop_test
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.rt1051
def test_send_file(harness):
    """
    Attempt requesting and sending file to Pure
    """
    fileName = "lorem-ipsum.txt"
    filePath = os.getcwd() + "/" + fileName

    with open(filePath, 'rb') as file:
        file_data = file.read()
        fileCrc32 = format((binascii.crc32(file_data) & 0xFFFFFFFF), '08x')
        print("fileCrc32: " + fileCrc32)

    fileSize = os.path.getsize(fileName)

    body = {"fileName" : "/sys/user/" + fileName, "fileSize" : fileSize, "fileCrc32" : fileCrc32}
    ret = harness.endpoint_request("filesystem", "put", body)

    assert ret["status"] == status["OK"]
    assert ret["body"]["txID"] != 0

    txID      = ret["body"]["txID"]
    chunkSize = ret["body"]["chunkSize"]
    print("chunkSize: " + str(chunkSize))

    totalChunks = int(((fileSize + chunkSize - 1) / chunkSize))
    print("totalChunks #: " + str(totalChunks))
    chunkNo = 1

    with open(filePath, 'rb') as file:
        for chunk in iter(partial(file.read, chunkSize), b''):
            data = binascii.b2a_base64(chunk).decode()

            body = {"txID" : txID, "chunkNo": chunkNo, "data" : data}
            ret = harness.endpoint_request("filesystem", "put", body)
            assert ret["status"] == status["OK"]
            chunkNo += 1


@pytest.mark.service_desktop_test
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.rt1051
def test_get_file_back(harness):
    """
    Attempt requesting and transfering file data
    """
    fileName = "lorem-ipsum.txt"
    body = {"fileName" : "/sys/user/" + fileName}
    ret = harness.endpoint_request("filesystem", "get", body)

    assert ret["status"] == status["OK"]
    assert ret["body"]["fileSize"] != 0

    rxID      = ret["body"]["rxID"]
    fileSize  = ret["body"]["fileSize"]
    chunkSize = ret["body"]["chunkSize"]
    expectedCrc32 = ret["body"]["fileCrc32"]

    totalChunks = int(((fileSize + chunkSize - 1) / chunkSize))
    print("totalChunks #: " + str(totalChunks))
    print("Expected file CRC32: " + expectedCrc32)

    data = ""

    for n in range(1, totalChunks + 1):
        body = {"rxID" : rxID, "chunkNo": n}
        ret = harness.endpoint_request("filesystem", "get", body)

        assert ret["status"] == status["OK"]
        data += ret["body"]["data"][0:-1] # Skiping null char at end of chunk

    file_64_decode = binascii.a2b_base64(data)
    actualCrc32 = format((binascii.crc32(file_64_decode) & 0xFFFFFFFF), '08x')
    print("Actual file CRC32: " + actualCrc32)
    assert expectedCrc32 == actualCrc32