Skip to content

Commit

Permalink
Merge branch 'serialcom' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
b5i authored Nov 13, 2024
2 parents 7b179ed + ca59deb commit 0307bdd
Show file tree
Hide file tree
Showing 26 changed files with 2,191 additions and 9 deletions.
105 changes: 105 additions & 0 deletions lib/serialcom/ArrayedStreamBuffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <array>
#include <cstddef>
#include <iostream>
#include <memory>
#include <Arduino.h>

namespace serialcom {

/// Adapted from https://en.cppreference.com/w/cpp/io/basic_streambuf/overflow's example
template<std::size_t size, class CharT = char>
struct ArrayedStreamBuffer : std::basic_streambuf<CharT>
{
using Base = std::basic_streambuf<CharT>;
using char_type = typename Base::char_type;
using int_type = typename Base::int_type;

bool canFlushOnOverflow = true;

ArrayedStreamBuffer()
{
// put area pointers to work with 'buffer'
Base::setp(buffer.data(), buffer.data() + size);
}

~ArrayedStreamBuffer()
{
if (this->stream && this->originalBuffer)
this->stream->rdbuf(originalBuffer);
}

void installOnStream(std::ostream* stream)
{
this->stream = stream;
this->originalBuffer = this->stream->rdbuf();
this->stream->rdbuf(this);
}

void flushBuffer()
{
if (!this->stream || !this->originalBuffer)
return;
this->stream->rdbuf(originalBuffer);
this->stream->write(buffer.data(), this->pptr() - this->pbase());
this->stream->flush(); // Ensure it's flushed to output
this->stream->rdbuf(this);
buffer.fill(0);
Base::setp(buffer.data(), buffer.data() + size);
}

void log(const std::string& log)
{
this->buffer->write(log);
}

void directLog(const std::string& log, bool newLine)
{
if (!this->stream || !this->originalBuffer)
return;
this->stream->rdbuf(originalBuffer);
Serial.write(log.c_str(), log.size());
if (newLine)
{
Serial.write("\r\n");
Serial.flush();
}
this->stream->rdbuf(this);
}

std::streambuf* changeDefaultBuffer(std::streambuf* buffer)
{
std::streambuf* oldBuffer = this->originalBuffer;
this->originalBuffer = buffer;
return oldBuffer;
}

void emptyBuffer()
{
buffer.fill(0);
Base::setp(buffer.data(), buffer.data() + size);
}
private:
int_type overflow(int_type ch) override
{
if (!this->stream)
return Base::overflow(ch);

// TODO: find a way to force flush on overflow without compromising an eventual command log.
if (canFlushOnOverflow)
flushBuffer();
else {
this->buffer.fill(0);
Base::setp(buffer.data(), buffer.data() + size);
}

*(this->stream) << (char)ch;
return 1; // overflow management succeeded
}

std::array<char_type, size> buffer{}; // value-initialize buffer
std::streambuf* originalBuffer;
std::ostream* stream;
};
}
203 changes: 203 additions & 0 deletions lib/serialcom/Command.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include "Command.hpp"
#include "Commands/InfoCommand.hpp"
#include "Commands/FileCommands.hpp"
#include "Commands/ShellModeCommand.hpp"
#include "Commands/ConsoleCommand.hpp"
#include <iostream>

namespace serialcom {
Command::Command(char (&input)[INPUT_MAX_SIZE]) {
bool shellMode = CommandsManager::defaultInstance->shellMode;

// detect the command type knowing that the maximum size that the command type can have is MAX_COMMMAND_TYPE_SIZE
char command_type[MAX_COMMMAND_TYPE_SIZE];

size_t command_size = MAX_COMMMAND_TYPE_SIZE;

for (size_t i = 0; i < MAX_COMMMAND_TYPE_SIZE; i++) {
if (input[i] == ' ' || input[i] == '\0') {
command_type[i] = '\0';
command_size = i;
break;
}
command_type[i] = input[i];
}

// match the command_type with a loop with the CommandType enum but don't forget that the input is a 16 char array

this->type = CommandType::unknown;
for (auto const& [key, val] : command_types_raw_strings) {
if (val.size() == 0) {
continue;
}
if (MAX_COMMMAND_TYPE_SIZE < val.size()) {
if (shellMode) {
SerialManager::sharedInstance->commandLog("WARNING: COMMAND TYPE " + val + " HAS A GREATER SIZE THAN THE MAXIMUM COMMAND TYPE SIZE");
} else {
SerialManager::sharedInstance->commandLog(NON_SHELL_MODE_ERROR_CODE);
}
continue;
}

bool broken = false;

for(size_t i = 0; i < val.size(); i++) {
if (command_type[i] != val[i]) {
broken = true;
break;
}
}
if (!broken)
{
this->type = key;
break;
}
}

if (type == CommandType::unknown) {
if (shellMode) {
SerialManager::sharedInstance->commandLog("ERROR: UNKONWN COMMAND TYPE");
} else {
SerialManager::sharedInstance->commandLog(NON_SHELL_MODE_ERROR_CODE);
}
return;
}

size_t current_index = command_size;

for (size_t argument_index = 0; argument_index < MAX_COMMAND_ARGUMENTS_COUNT; argument_index++) {
// skip spaces
while (input[current_index] == ' ' && current_index < INPUT_MAX_SIZE - 1) {
current_index++;
}

// check if the input has ended
if (input[current_index] == '\0') {
break;
}

bool isUsingQuotes = false;

if (input[current_index] == '"') {
isUsingQuotes = true;
current_index++;
}

bool isEscaping = false;
size_t argument_char_index = 0;
for (size_t i = current_index; i < current_index + MAX_ARGUMENT_SIZE; i++)
{
if (input[i] == '\0' || input[i] == '\n') {
if (isEscaping) {
// escaping end of input
isEscaping = false;
this->arguments[argument_index][argument_char_index] = input[i];
current_index = i + 1;
break;
} else {
// end of input
this->arguments[argument_index][argument_char_index] = '\0';
current_index = i + 1;
break;
}
} else if (input[i] == '\\') {
if (isEscaping) {
// escaping backslash
isEscaping = false;
this->arguments[argument_index][argument_char_index] = '\\';
argument_char_index++;
} else {
// start escaping
isEscaping = true;
}
} else if (input[i] == ' ') {
if (isUsingQuotes) {
// space and is using quotes
this->arguments[argument_index][argument_char_index] = input[i];
argument_char_index++;
continue;
}

// not using quotes => need to escape space characters
if (isEscaping) {
// escaping space
isEscaping = false;
this->arguments[argument_index][argument_char_index] = ' ';
argument_char_index++;
continue;
} else {
// end of argument because of space
this->arguments[argument_index][argument_char_index] = '\0';
current_index = i + 1;
break;
}
} else if (input[i] == '"') {
if (isUsingQuotes) {
if (isEscaping) {
// escaping quote
isEscaping = false;
this->arguments[argument_index][argument_char_index] = '"';
argument_char_index++;
} else {
// end of argument because of quote
this->arguments[argument_index][argument_char_index] = '\0';
current_index = i + 1;
break;
}
} else {
if (isEscaping) {
// escaping quote
isEscaping = false;
this->arguments[argument_index][argument_char_index] = '"';
argument_char_index++;
} else {
if (shellMode) {
SerialManager::sharedInstance->commandLog("ERROR: UNEXPECTED QUOTE CHARACTER AT POSITION " + std::to_string(i));
} else {
SerialManager::sharedInstance->commandLog(NON_SHELL_MODE_ERROR_CODE);
}
this->type = CommandType::unknown;
for (size_t j = 0; j < MAX_COMMAND_ARGUMENTS_COUNT; j++)
{
this->arguments[j][0] = '\0';
}
return;
}
}
} else {
if (isEscaping) {
// escaping character
isEscaping = false;
this->arguments[argument_index][argument_char_index] = '\\';
argument_char_index++;
}
this->arguments[argument_index][argument_char_index] = input[i];
argument_char_index++;
}
}
}
}

std::unordered_map<Command::CommandType, std::string> Command::command_types_raw_strings {
{Command::CommandType::sm, "sm"},
{Command::CommandType::console, "console"},

{Command::CommandType::info, "info"},
{Command::CommandType::echo, "echo"},
{Command::CommandType::apps, "apps"},
{Command::CommandType::files, "files"},
{Command::CommandType::elevate, "elevate"},
{Command::CommandType::lte, "lte"},

{Command::CommandType::ls, "ls"},
{Command::CommandType::touch, "touch"},
{Command::CommandType::mkdir, "mkdir"},
{Command::CommandType::rm, "rm"},
{Command::CommandType::cp, "cp"},
{Command::CommandType::mv, "mv"},
{Command::CommandType::cat, "cat"},
{Command::CommandType::download, "download"},
{Command::CommandType::upload, "upload"}
};
}

49 changes: 49 additions & 0 deletions lib/serialcom/Command.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include <cstddef>
#include <unordered_map>
#include <optional>
#include <string>

namespace serialcom {
const size_t MAX_COMMMAND_TYPE_SIZE = 16;
const size_t MAX_ARGUMENT_SIZE = 512;
const size_t MAX_COMMAND_ARGUMENTS_COUNT = 3;
constexpr size_t INPUT_MAX_SIZE = MAX_COMMMAND_TYPE_SIZE + MAX_COMMAND_ARGUMENTS_COUNT * MAX_ARGUMENT_SIZE;

struct Command {
enum class CommandType {
sm,
console,

info,
echo,
apps,
files,
elevate,
lte,

ls,
touch,
mkdir,
rm,
cp,
mv,
cat,
download,
upload,

unknown
};

// a static unordered map between the raw strings of the command types and the CommandType enum

static std::unordered_map<CommandType, std::string> command_types_raw_strings;

CommandType type;

Command(char (&input)[INPUT_MAX_SIZE]);

char arguments[MAX_COMMAND_ARGUMENTS_COUNT][MAX_ARGUMENT_SIZE] = {'\0'};
};
}
Loading

0 comments on commit 0307bdd

Please sign in to comment.