mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
194 lines
7.2 KiB
C++
194 lines
7.2 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include <development/requests.h>
|
|
|
|
#include <tactility/filesystem/fs.h>
|
|
#include <tactility/log.h>
|
|
#include <tactility/memory.h>
|
|
|
|
#include <cstring>
|
|
|
|
namespace {
|
|
|
|
constexpr auto* TAG = "DevelopmentRequests";
|
|
|
|
// Bounds the wait for socket input, which doesn't signal the event group
|
|
constexpr uint32_t POLL_INTERVAL_MS = 20;
|
|
constexpr size_t RECEIVE_BUFFER_SIZE = DEVELOPMENT_FRAME_HEADER_SIZE + DEVELOPMENT_FRAME_PAYLOAD_MAX;
|
|
|
|
constexpr MemoryPolicy MEMORY_POLICY = { .required = 0, .desired = MEMORY_CAPABILITY_EXTERNAL, .alignment = 0 };
|
|
|
|
struct Buffers {
|
|
uint8_t receive[RECEIVE_BUFFER_SIZE];
|
|
uint8_t scratch[DEVELOPMENT_FRAME_PAYLOAD_MAX];
|
|
};
|
|
|
|
bool createParentDirectory(const char* path) {
|
|
char parent[FILE_MAX_PATH_STRING_LENGTH];
|
|
strcpy(parent, path);
|
|
char* separator = strrchr(parent, '/');
|
|
if (separator == nullptr || separator == parent) {
|
|
return true;
|
|
}
|
|
*separator = '\0';
|
|
if (directory_exists(parent)) {
|
|
return true;
|
|
}
|
|
const error_t error = directory_make(parent, true);
|
|
return error == ERROR_NONE || error == ERROR_ALREADY_EXISTS;
|
|
}
|
|
|
|
void writePush(DevelopmentRequestContext& context, const DevelopmentFrame& frame) {
|
|
if (context.pushFile != nullptr && fwrite(frame.payload, 1, frame.length, context.pushFile) != frame.length) {
|
|
context.pushError = "failed to write file";
|
|
}
|
|
}
|
|
|
|
/** Closes the pushed file, and removes it when the push failed or the file is temporary */
|
|
void closePush(DevelopmentRequestContext& context) {
|
|
if (context.pushFile != nullptr) {
|
|
if (fclose(context.pushFile) != 0 && context.pushError == nullptr) {
|
|
context.pushError = "failed to write file";
|
|
}
|
|
context.pushFile = nullptr;
|
|
if (context.pushError == nullptr && context.pushComplete != nullptr) {
|
|
context.pushError = context.pushComplete(context, context.pushPath);
|
|
}
|
|
if (context.pushError != nullptr || context.pushIsTemporary) {
|
|
remove(context.pushPath);
|
|
}
|
|
}
|
|
context.pushing = false;
|
|
}
|
|
|
|
bool finishPush(DevelopmentRequestContext& context) {
|
|
closePush(context);
|
|
if (context.pushError != nullptr) {
|
|
LOG_W(TAG, "Push to %s failed: %s", context.pushPath, context.pushError);
|
|
return development_requests_send_fail(context, context.pushError);
|
|
}
|
|
LOG_I(TAG, "Pushed %s", context.pushPath);
|
|
return development_requests_send_ok(context);
|
|
}
|
|
|
|
bool handleFrame(DevelopmentRequestContext& context, const DevelopmentFrame& frame, DevelopmentRequestMode mode) {
|
|
if (context.pushing) {
|
|
switch (frame.type) {
|
|
case DEVELOPMENT_FRAME_DATA:
|
|
writePush(context, frame);
|
|
return true;
|
|
case DEVELOPMENT_FRAME_DONE:
|
|
return finishPush(context);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
switch (mode) {
|
|
case DevelopmentRequestMode::Sync:
|
|
return development_sync_handle(context, frame);
|
|
case DevelopmentRequestMode::App:
|
|
return development_app_handle(context, frame);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** @return false when the connection must close */
|
|
bool processFrames(DevelopmentRequestContext& context, Buffers* buffers, size_t& receiveLength, DevelopmentRequestMode mode) {
|
|
size_t offset = 0;
|
|
bool connected = true;
|
|
while (connected) {
|
|
DevelopmentFrame frame {};
|
|
size_t consumed = 0;
|
|
const auto result = development_frame_parse(buffers->receive + offset, receiveLength - offset, &frame, &consumed);
|
|
if (result == DevelopmentFrameParseResult::Incomplete) {
|
|
break;
|
|
}
|
|
connected = result == DevelopmentFrameParseResult::Ok && handleFrame(context, frame, mode);
|
|
offset += consumed;
|
|
}
|
|
memmove(buffers->receive, buffers->receive + offset, receiveLength - offset);
|
|
receiveLength -= offset;
|
|
return connected;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void development_requests_run(DevelopmentSession* session, TaskEventGroup* event_group, AppEventSubscription* subscription, bool* close_requested, DevelopmentRequestMode mode) {
|
|
auto* buffers = static_cast<Buffers*>(memory_alloc_with_policy(sizeof(Buffers), &MEMORY_POLICY));
|
|
if (buffers == nullptr) {
|
|
LOG_E(TAG, "Out of memory");
|
|
return;
|
|
}
|
|
DevelopmentRequestContext context { session, subscription, close_requested, buffers->scratch };
|
|
size_t receiveLength = 0;
|
|
|
|
bool connected = true;
|
|
while (connected && !*close_requested) {
|
|
const int result = mbedtls_ssl_read(&session->ssl, buffers->receive + receiveLength, RECEIVE_BUFFER_SIZE - receiveLength);
|
|
if (result > 0) {
|
|
receiveLength += static_cast<size_t>(result);
|
|
connected = processFrames(context, buffers, receiveLength, mode);
|
|
} else if (result == MBEDTLS_ERR_SSL_WANT_READ || result == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
|
task_event_group_wait_any(event_group, nullptr, pdMS_TO_TICKS(POLL_INTERVAL_MS));
|
|
} else {
|
|
connected = false;
|
|
}
|
|
development_requests_poll_events(context);
|
|
}
|
|
|
|
if (context.pushing) {
|
|
context.pushError = "interrupted";
|
|
closePush(context);
|
|
}
|
|
memory_free(buffers);
|
|
}
|
|
|
|
void development_requests_start_push(DevelopmentRequestContext& context, const char* path, const char* error, DevelopmentPushCompleteFn complete, bool temporary) {
|
|
context.pushing = true;
|
|
context.pushFile = nullptr;
|
|
context.pushError = error;
|
|
context.pushComplete = complete;
|
|
context.pushIsTemporary = temporary;
|
|
context.pushPath[0] = '\0';
|
|
if (error != nullptr) {
|
|
return;
|
|
}
|
|
strcpy(context.pushPath, path);
|
|
if (directory_exists(path)) {
|
|
context.pushError = "is a directory";
|
|
} else if (!createParentDirectory(path)) {
|
|
context.pushError = "failed to create parent directory";
|
|
} else if ((context.pushFile = fopen(path, "wb")) == nullptr) {
|
|
context.pushError = "failed to open file";
|
|
}
|
|
}
|
|
|
|
bool development_requests_read_string(const DevelopmentFrame& frame, char* out_string, size_t out_size) {
|
|
if (frame.length == 0 || frame.length >= out_size || memchr(frame.payload, '\0', frame.length) != nullptr) {
|
|
return false;
|
|
}
|
|
memcpy(out_string, frame.payload, frame.length);
|
|
out_string[frame.length] = '\0';
|
|
return true;
|
|
}
|
|
|
|
bool development_requests_read_path(const DevelopmentFrame& frame, char* out_path) {
|
|
return frame.length > 0 && frame.payload[0] == '/' && development_requests_read_string(frame, out_path, FILE_MAX_PATH_STRING_LENGTH);
|
|
}
|
|
|
|
bool development_requests_send_ok(DevelopmentRequestContext& context) {
|
|
return development_session_send_frame(context.session, DEVELOPMENT_FRAME_OK, nullptr, 0);
|
|
}
|
|
|
|
bool development_requests_send_fail(DevelopmentRequestContext& context, const char* message) {
|
|
return development_session_send_frame(context.session, DEVELOPMENT_FRAME_FAIL, message, static_cast<uint16_t>(strlen(message)));
|
|
}
|
|
|
|
void development_requests_poll_events(DevelopmentRequestContext& context) {
|
|
AppEvent event {};
|
|
while (app_event_poll(context.subscription, &event) == ERROR_NONE) {
|
|
if (event.type == APP_EVENT_CLOSE) {
|
|
*context.closeRequested = true;
|
|
}
|
|
}
|
|
}
|