spicetools/api/serial.cpp

241 lines
9.3 KiB
C++

#include "controller.h"
#include "serial.h"
#include <string>
#include <utility>
#include "util/logging.h"
#include "util/utils.h"
namespace api {
SerialController::SerialController(Controller *controller, std::string port, DWORD baud)
: controller(controller), port(std::move(port)), baud(baud) {
this->state = new ClientState();
controller->init_state(this->state);
this->thread = new std::thread([this] () {
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
// read buffer
uint8_t read_buffer[16*1024];
size_t read_buffer_len = 0;
// serial retry loop
while (this->running) {
// try to open port
if (this->handle == INVALID_HANDLE_VALUE) {
this->open_port();
}
// reset in-buffer
read_buffer_len = 0;
// connection loop
DWORD retry_time = 1000;
while (this->handle != INVALID_HANDLE_VALUE) {
retry_time = 1000;
DWORD bytes_read = 0;
// check if we need to wait for incoming data first
bool messages_in_buffer = false;
for (size_t i = 0; i < read_buffer_len; i++) {
if (read_buffer[i] == 0x00) {
messages_in_buffer = true;
break;
}
}
// read data
if (!messages_in_buffer && (!ReadFile(
this->handle,
&read_buffer[read_buffer_len],
sizeof(read_buffer) - read_buffer_len, &bytes_read,
nullptr) || bytes_read == 0)) {
// open new connection
log_warning("api::serial", "read error on {}", this->port);
this->free_port();
break;
} else {
//log_info("api::serial::in", "{}", bin2hex(read_buffer + read_buffer_len, bytes_read));
// check for reset
if (read_buffer_len + bytes_read > 7) {
size_t zero_counter = 0;
for (size_t i = 0; i < read_buffer_len + bytes_read; i++) {
if (read_buffer[i] == 0x00) {
if (++zero_counter == 8) {
// reset password
state->password = this->controller->get_password();
state->password_change = true;
Controller::process_password_change(state);
// drop input
size_t new_length = 0;
while (++i < read_buffer_len + bytes_read) {
read_buffer[new_length++] = read_buffer[i];
}
bytes_read = new_length;
read_buffer_len = 0;
log_info("api::serial", "session reset, remaining bytes: {} {}", bytes_read, bin2hex(read_buffer, bytes_read));
break;
}
} else {
zero_counter = 0;
}
}
}
// crypt in-data
if (state->cipher) {
state->cipher->crypt(&read_buffer[read_buffer_len], bytes_read);
}
// adjust size
read_buffer_len += bytes_read;
// check if message complete
for (size_t i = 0; i < read_buffer_len; ++i) {
if (read_buffer[i] != 0x00) {
continue;
} else {
// process request
std::vector<char> out;
if (!this->controller->process_request(
state,
(const char*) &read_buffer[0], read_buffer_len, &out)) {
// open new connection
log_warning("api::serial", "process error on {} (length {})",
this->port, i - 1);
this->free_port();
retry_time = 5;
break;
}
// adjust in-buffer
if (i == read_buffer_len - 1) {
read_buffer_len = 0;
} else {
size_t new_length = 0;
while (i < read_buffer_len) {
read_buffer[new_length++] = read_buffer[i++];
}
read_buffer_len = new_length;
}
// crypt out-data
if (state->cipher) {
state->cipher->crypt((uint8_t*) out.data(), out.size());
}
// send answer
DWORD bytes_written = 0;
if (!WriteFile(
this->handle,
out.data(),
out.size(),
&bytes_written,
nullptr) || bytes_written != out.size()) {
// open new connection
log_warning("api::serial", "write error on {}", this->port);
this->free_port();
break;
}
// check for password change
Controller::process_password_change(state);
//log_info("api::serial::out", "{}", bin2hex(out));
read_buffer_len = 0;
}
}
}
}
// slow down on reconnect
if (this->running) {
Sleep(retry_time);
}
}
});
}
SerialController::~SerialController() {
this->free_port();
this->running = false;
this->thread->join();
delete this->thread;
delete this->state;
}
void SerialController::open_port() {
// free resources
this->free_port();
// open port
this->handle = CreateFile(
this->port.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr);
// check if open failed
if (this->handle == INVALID_HANDLE_VALUE) {
log_warning("api::serial", "failed to open port {}", port);
return;
}
// settings
DCB serial_params{};
serial_params.DCBlength = sizeof(serial_params);
if (!GetCommState(this->handle, &serial_params)) {
log_warning("api::serial", "{}: unable to get COM port state: 0x{:x}", port, GetLastError());
return;
}
// set params
serial_params.BaudRate = this->baud;
serial_params.ByteSize = 8;
serial_params.StopBits = ONESTOPBIT;
serial_params.Parity = NOPARITY;
if (!SetCommState(this->handle, &serial_params)) {
log_warning("api::serial", "{}: unable to set COM port state: 0x{:x}", port, GetLastError());
return;
}
// timeouts
COMMTIMEOUTS timeouts{};
timeouts.ReadIntervalTimeout = 5;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 30;
timeouts.WriteTotalTimeoutMultiplier = 5;
if (!SetCommTimeouts(this->handle, &timeouts)) {
log_warning("api::serial", "{}: unable to set COM port timeouts: 0x{:x}", port, GetLastError());
return;
}
// reset password
state->password_change = true;
Controller::process_password_change(state);
log_info("api::serial", "listening on {}/{}", port, baud);
}
void SerialController::free_port() {
if (this->handle != INVALID_HANDLE_VALUE) {
CloseHandle(this->handle);
this->handle = INVALID_HANDLE_VALUE;
}
}
}