isv/libvoltronic/voltronic_dev.c
2020-10-24 02:38:24 +03:00

431 lines
11 KiB
C

/**
* Copyright (C) 2019 Johan van der Vyver
* Copyright (C) 2020 Evgeny Zinoviev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "voltronic_dev.h"
#include "voltronic_dev_impl.h"
#include "voltronic_crc.h"
#include "../util.h"
#if defined(_WIN32) || defined(WIN32)
#include "windows.h"
typedef DWORD millisecond_timestamp_t;
#elif defined(__APPLE__)
#include <mach/mach_time.h>
typedef uint64_t millisecond_timestamp_t;
#elif defined(ARDUINO)
#include <Arduino.h>
typedef unsigned long millisecond_timestamp_t;
#else
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
#include <unistd.h>
typedef uint64_t millisecond_timestamp_t;
#endif
#define END_OF_INPUT '\r'
#define END_OF_INPUT_SIZE sizeof(char)
#define NON_DATA_SIZE (sizeof(voltronic_crc_t) + END_OF_INPUT_SIZE)
#define GET_IMPL_DEV(_voltronic_dev_t_) \
((void*) (_voltronic_dev_t_))
#if defined(_WIN32) || defined(WIN32)
#define SET_TIMEOUT_REACHED() SET_LAST_ERROR(WAIT_TIMEOUT)
#define SET_BUFFER_OVERFLOW() SET_LAST_ERROR(ERROR_INSUFFICIENT_BUFFER)
#define SET_CRC_ERROR() SET_LAST_ERROR(ERROR_CRC)
#define SYSTEM_NOT_SUPPORTED() SET_LAST_ERROR(ERROR_CALL_NOT_IMPLEMENTED)
#else
#define SET_TIMEOUT_REACHED() SET_LAST_ERROR(ETIMEDOUT)
#define SET_BUFFER_OVERFLOW() SET_LAST_ERROR(ENOBUFS)
#define SET_CRC_ERROR() SET_LAST_ERROR(EBADMSG)
#define SYSTEM_NOT_SUPPORTED() SET_LAST_ERROR(ENOSYS)
#endif
static millisecond_timestamp_t get_millisecond_timestamp(void);
static int is_platform_supported_by_libvoltronic(void);
int voltronic_dev_read(
const voltronic_dev_t dev,
char* buffer,
const size_t buffer_size,
const unsigned int timeout_milliseconds) {
if (buffer_size > 0) {
const int result = voltronic_dev_impl_read(
GET_IMPL_DEV(dev),
buffer,
buffer_size,
timeout_milliseconds);
return result >= 0 ? result : -1;
} else {
return 0;
}
}
int voltronic_dev_write(
const voltronic_dev_t dev,
const char* buffer,
const size_t buffer_size,
const unsigned int timeout_milliseconds) {
if (buffer_size > 0) {
const int result = voltronic_dev_impl_write(
GET_IMPL_DEV(dev),
buffer,
buffer_size,
timeout_milliseconds);
return result >= 0 ? result : -1;
} else {
return 0;
}
}
int voltronic_dev_close(voltronic_dev_t dev) {
if (dev != 0) {
const int result = voltronic_dev_impl_close(GET_IMPL_DEV(dev));
return result > 0 ? 1 : 0;
} else {
SET_INVALID_INPUT();
return 0;
}
}
static int voltronic_read_data_loop(
const voltronic_dev_t dev,
char* buffer,
size_t buffer_length,
const unsigned int timeout_milliseconds) {
unsigned int size = 0;
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
millisecond_timestamp_t elapsed = 0;
while(1) {
int bytes_read = voltronic_dev_read(
dev,
buffer,
buffer_length,
timeout_milliseconds - elapsed);
if (bytes_read >= 0) {
while(bytes_read) {
--bytes_read;
++size;
if (*buffer == END_OF_INPUT) {
return size;
}
buffer += sizeof(char);
--buffer_length;
}
elapsed = get_millisecond_timestamp() - start_time;
if (elapsed >= timeout_milliseconds) {
SET_TIMEOUT_REACHED();
return -1;
}
if (buffer_length <= 0) {
SET_BUFFER_OVERFLOW();
return -1;
}
} else {
return bytes_read;
}
}
}
static int voltronic_receive_data(
const voltronic_dev_t dev,
const unsigned int options,
char *buffer,
const size_t buffer_length,
size_t *received,
const unsigned int timeout_milliseconds) {
const int result = voltronic_read_data_loop(
dev,
buffer,
buffer_length,
timeout_milliseconds);
if (result >= 0) {
if (received)
*received = result;
LOG("%s: got %d %s:\n",
__func__, result, (result > 1 ? "bytes" : "byte"));
HEXDUMP(buffer, result);
if ((options & DISABLE_PARSE_VOLTRONIC_CRC) == 0) {
if (((size_t) result) >= NON_DATA_SIZE) {
const size_t data_size = result - NON_DATA_SIZE;
const voltronic_crc_t read_crc = read_voltronic_crc(&buffer[data_size]);
const voltronic_crc_t calculated_crc = calculate_voltronic_crc(buffer, data_size);
buffer[data_size] = 0;
if (((options & DISABLE_VERIFY_VOLTRONIC_CRC) == 0) ||
(read_crc == calculated_crc)) {
return data_size;
}
}
SET_CRC_ERROR();
} else {
if (((size_t) result) >= END_OF_INPUT_SIZE) {
const size_t data_size = result - END_OF_INPUT_SIZE;
buffer[data_size] = 0;
return data_size;
}
}
} else {
if (received)
*received = 0;
return result;
}
return -1;
}
static int voltronic_write_data_loop(
const voltronic_dev_t dev,
const char* buffer,
size_t buffer_length,
const unsigned int timeout_milliseconds) {
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
millisecond_timestamp_t elapsed = 0;
int bytes_left = buffer_length;
while(1) {
const int write_result = voltronic_dev_write(dev, buffer, bytes_left, timeout_milliseconds);
if (write_result >= 0) {
bytes_left -= write_result;
if (bytes_left > 0) {
buffer = &buffer[write_result];
} else {
return buffer_length;
}
elapsed = get_millisecond_timestamp() - start_time;
if (elapsed >= timeout_milliseconds) {
SET_TIMEOUT_REACHED();
return -1;
}
} else {
return write_result;
}
}
}
static int voltronic_send_data(
const voltronic_dev_t dev,
const unsigned int options,
const char* buffer,
const size_t buffer_length,
const unsigned int timeout_milliseconds) {
size_t copy_length;
char* copy;
if ((options & DISABLE_WRITE_VOLTRONIC_CRC) == 0) {
const voltronic_crc_t crc = calculate_voltronic_crc(buffer, buffer_length);
copy_length = buffer_length + NON_DATA_SIZE;
copy = (char*) ALLOCATE_MEMORY(copy_length * sizeof(char));
write_voltronic_crc(crc, &copy[buffer_length]);
} else {
copy_length = buffer_length + END_OF_INPUT_SIZE;
copy = (char*) ALLOCATE_MEMORY(copy_length * sizeof(char));
}
COPY_MEMORY(copy, buffer, buffer_length * sizeof(char));
copy[copy_length - 1] = END_OF_INPUT;
LOG("%s: writing %zu %s:\n",
__func__, copy_length, (copy_length > 1 ? "bytes" : "byte"));
HEXDUMP(copy, copy_length);
const int result = voltronic_write_data_loop(
dev,
copy,
copy_length,
timeout_milliseconds);
FREE_MEMORY(copy);
return result;
}
int voltronic_dev_execute(
const voltronic_dev_t dev,
const unsigned int options,
const char *send_buffer,
size_t send_buffer_length,
char *receive_buffer,
size_t receive_buffer_length,
size_t *received,
const unsigned int timeout_milliseconds) {
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
millisecond_timestamp_t elapsed = 0;
int result;
result = voltronic_send_data(
dev,
options,
send_buffer,
send_buffer_length,
timeout_milliseconds);
if (result > 0) {
elapsed = get_millisecond_timestamp() - start_time;
if (elapsed < timeout_milliseconds) {
result = voltronic_receive_data(
dev,
options,
receive_buffer,
receive_buffer_length,
received,
timeout_milliseconds - elapsed);
if (result > 0) {
return result;
}
} else {
SET_TIMEOUT_REACHED();
}
}
return 0;
}
voltronic_dev_t voltronic_dev_internal_create(void* impl_ptr) {
if (is_platform_supported_by_libvoltronic()) {
if (impl_ptr != 0) {
return ((voltronic_dev_t) (impl_ptr));
}
}
if (impl_ptr != 0) {
voltronic_dev_impl_close(impl_ptr);
}
return 0;
}
#if defined(_WIN32) || defined(WIN32)
static millisecond_timestamp_t get_millisecond_timestamp(void) {
return (millisecond_timestamp_t) GetTickCount();
}
#elif defined(__APPLE__)
static millisecond_timestamp_t get_millisecond_timestamp(void) {
return (millisecond_timestamp_t) mach_absolute_time();
}
#elif defined(ARDUINO)
static millisecond_timestamp_t get_millisecond_timestamp(void) {
return (millisecond_timestamp_t) millis();
}
#else
static millisecond_timestamp_t get_millisecond_timestamp(void) {
millisecond_timestamp_t milliseconds = 0;
#if defined(CLOCK_MONOTONIC)
static int monotonic_clock_error = 0;
if (monotonic_clock_error == 0) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
milliseconds = (millisecond_timestamp_t) ts.tv_sec;
milliseconds *= 1000;
milliseconds += (millisecond_timestamp_t) (ts.tv_nsec / 1000000);
return milliseconds;
} else {
monotonic_clock_error = 1;
}
}
#endif
struct timeval tv;
if (gettimeofday(&tv, 0) == 0) {
milliseconds = (millisecond_timestamp_t) tv.tv_sec;
milliseconds *= 1000;
milliseconds += (millisecond_timestamp_t) (tv.tv_usec / 1000);
}
return milliseconds;
}
#endif
static int is_platform_supported_by_libvoltronic(void) {
/**
* Operating system/cpu architecture validations
* If any of these fail, things don't behave the code expects
*/
if ((sizeof(char) == sizeof(unsigned char)) &&
(sizeof(unsigned char) == 1) &&
(sizeof(int) >= 2) &&
(sizeof(unsigned int) >= 2) &&
(sizeof(voltronic_crc_t) == 2) &&
(sizeof(millisecond_timestamp_t) >= 4)) {
return 1;
} else {
SYSTEM_NOT_SUPPORTED();
return 0;
}
}