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
+