Skip to content

Instantly share code, notes, and snippets.

@MatteoRagni
Last active September 9, 2020 06:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MatteoRagni/7fa14cf99df735ca4c6c9a735b83641e to your computer and use it in GitHub Desktop.
Save MatteoRagni/7fa14cf99df735ca4c6c9a735b83641e to your computer and use it in GitHub Desktop.
This is a C++ implementation of a class that handles the COBS transfer.
#ifndef COBS_HPP
#define COBS_HPP
#include <cstdlib>
#include <cstring>
#include <stdexcept>
/*! Macro for declaration of struct to insert specific packed data into
* the union of the COBS class. The resulting struct will be packed
* X is the struct typedefinition and Y is the body of the struct.
*/
#define COBS_DEFINE_STRUCT(X, Y) struct X Y __attribute__((packed)); typedef struct X X;
/*! This union combines the data to send and a buffer to send the data. The struct
* is accessed through the data field.
*/
template <class STRUCT>
union Packet {
STRUCT data; /**< The struct or the data field (like an array) */
unsigned char buffer[sizeof(STRUCT)]; /**< The buffer is the char array equivalent of the data */
};
/*! Read Callback: this is the prototype of function that should return a char at the time.
* It has 2 arguments. The first, in the form of a size_t is the current index in the
* reading, the second is a payload (arbitrary content) in the form a void pointer.
*/
typedef char (*COBS_read_callback)(size_t, void*);
/*! Write Calback: this is the prototype of function that should accept one char at the
* time. The callback has 3 arguments: the index of currently read char, the char itself,
* and a payload (arbitrary content) in the form a void pointer.
*/
typedef void (*COBS_write_callback)(size_t, char, void*);
/*! COBS Class
* The COBS class is an abstraction of the Consistent Overhead Byte Stuffing, an algorithm
* for encoding data byte, in such a way the receiver should not handle necessarely the
* dimension of the packet (the packet is ended when it contains a 0x00). The class is
* abstracted over a template that contains the actual data.
*/
template <class STRUCT>
class COBS {
union Packet<STRUCT> packet; /**< The actual package */
unsigned char * outbound; /**< The container for the COBS encoded package */
void stuff(void); /**< The encoding function */
void unstuff(void); /**< The decoding function */
public:
/*! The class constructor */
COBS() {
outbound = (unsigned char *)malloc(sizeof(STRUCT) + 1);
if (!(outbound))
throw std::runtime_error("Allocation error");
};
/*! The class destructor */
~COBS() { if(outbound) free(outbound); }
/*! Set the struct (or any other kind of data to be sent) */
void set(const STRUCT *s) { memcpy(&(packet.data), s, sizeof(STRUCT)); }
/*! Get the struct (or any other kind of data to be received) */
void get(STRUCT *s) { memcpy(s, &(packet.data), sizeof(STRUCT)); }
/*! Return the current COBS encoded package */
const char * get_packet() const { return outbound; }
/*! Get the size of the handled data */
const size_t size() { return sizeof(STRUCT); }
/*! Read from the transmission channel the COBS package */
void read(COBS_read_callback read_callback, void * payload);
/*! Write in the transmission channel the COBS package */
void write(COBS_write_callback write_callback, void * payload);
};
#define FinishBlock(X) (*code_ptr = (X), code_ptr = dst++, code = 0x01)
template <class STRUCT>
void COBS<STRUCT>::stuff(void) {
memset(outbound, 0, sizeof(STRUCT) + 1);
const unsigned char* end = packet.buffer + sizeof(STRUCT);
const unsigned char* ptr = packet.buffer;
unsigned char * dst = outbound;
unsigned char * code_ptr = dst++;
unsigned char code = 0x01;
while (ptr < end) {
if (*ptr == 0)
FinishBlock(code);
else {
*(dst)++ = *ptr;
if (++code == 0xFF)
FinishBlock(code);
}
ptr++;
}
FinishBlock(code);
}
template <class STRUCT>
void COBS<STRUCT>::unstuff(void) {
const unsigned char* end = outbound + sizeof(STRUCT) + 1;
unsigned char * ptr = outbound;
unsigned char * dst = packet.buffer;
while (ptr < end) {
int code = *ptr++;
for (int i = 1; ptr < end && i < code; i++)
*dst++ = *ptr++;
if (code < 0xFF)
*dst++ = 0;
}
}
template <class STRUCT>
void COBS<STRUCT>::read(COBS_read_callback read_callback, void * payload) {
for (size_t i = 0; i < sizeof(STRUCT) + 1; i++)
outbound[i] = read_callback(i, payload);
unstuff();
}
template <class STRUCT>
void COBS<STRUCT>::write(COBS_write_callback write_callback, void * payload) {
stuff();
for (size_t i = 0; i < sizeof(STRUCT) + 1; i++) {
write_callback(i, outbound[i], payload);
}
}
#endif /* COBS_HPP */
#include <cstdio>
#include "cobs.hpp"
/*! Data Struct
* This is the structure that will be sent and received . It is bigger than 254 bytes.
* Since we want to implement a struct as system to bring data, we need to embrace in the
* definition COBS_DEFINE_STRUCT.
*/
#define TEST 30
COBS_DEFINE_STRUCT(Data, {
int a[TEST];
double b[TEST];
char c[TEST];
});
#define DATA_SIZE sizeof(Data)
/*! Printing Utility
* This function prints the struct on screen to test the
* transfer between the two COBS instances
*/
void print_data(Data d);
/*! Initialization Utility
* This function initialize the Data struct with some values
*/
void initialize_data(Data &d);
/* MAIN TEST */
/*! Transmission pipe (only for testing purposes) */
char pipe[DATA_SIZE + 1] = "";
/*! Reading callback in the form: COBS_read_callback */
char my_read(size_t i, void *) {
return pipe[i++];
}
/*! Writing callback in the form: COBS_write_callback */
void my_write(size_t i, char c, void *) {
pipe[i] = c;
}
int main() {
/* SEND */
COBS<Data> cobs_sender; /**< Sender COBS interface for data parameters */
Data data_sender = { 0 }; /**< First element to be sent */
initialize_data(data_sender);
print_data(data_sender);
cobs_sender.set(&data_sender); /**< Set data in the COBS interface */
cobs_sender.write(my_write, NULL); /**< Actually send over the transmission channel, using the callback */
/* RECEIVE */
COBS<Data> cobs_receiver; /**< Receiver COBS interface for data parameters */
Data data_receiver = { 0 }; /**< First element to be received */
cobs_receiver.read(my_read, NULL); /**< Receive the data from the channel */
cobs_receiver.get(&data_receiver); /**< Save data in the receiver */
print_data(data_receiver);
return 0;
}
/*! Utilities implementation */
void print_data(Data d) {
printf("Data size %lu with content:\n", sizeof(Data));
for (size_t i = 0; i < TEST; i++)
printf("%2lu:\t%3d\t%3.1f\t%c\n", i, d.a[i], d.b[i], d.c[i]);
}
void initialize_data(Data &d) {
for (size_t i = 0; i < TEST; i++) {
d.a[i] = i * 2;
d.b[i] = (double)i / 10;
d.c[i] = (i + 97);
}
}
CXX = g++
CXXFLAGS = --std=c++11 -g -I. -Wall
default:
$(CXX) $(CXXFLAGS) main.cpp -o main
.PHONY: clean
clean:
rm -rf main
Data size 390 with content:
0: 0 0.0 a
1: 2 0.1 b
2: 4 0.2 c
3: 6 0.3 d
4: 8 0.4 e
5: 10 0.5 f
6: 12 0.6 g
7: 14 0.7 h
8: 16 0.8 i
9: 18 0.9 j
10: 20 1.0 k
11: 22 1.1 l
12: 24 1.2 m
13: 26 1.3 n
14: 28 1.4 o
15: 30 1.5 p
16: 32 1.6 q
17: 34 1.7 r
18: 36 1.8 s
19: 38 1.9 t
20: 40 2.0 u
21: 42 2.1 v
22: 44 2.2 w
23: 46 2.3 x
24: 48 2.4 y
25: 50 2.5 z
26: 52 2.6 {
27: 54 2.7 |
28: 56 2.8 }
29: 58 2.9 ~
Data size 390 with content:
0: 0 0.0 a
1: 2 0.1 b
2: 4 0.2 c
3: 6 0.3 d
4: 8 0.4 e
5: 10 0.5 f
6: 12 0.6 g
7: 14 0.7 h
8: 16 0.8 i
9: 18 0.9 j
10: 20 1.0 k
11: 22 1.1 l
12: 24 1.2 m
13: 26 1.3 n
14: 28 1.4 o
15: 30 1.5 p
16: 32 1.6 q
17: 34 1.7 r
18: 36 1.8 s
19: 38 1.9 t
20: 40 2.0 u
21: 42 2.1 v
22: 44 2.2 w
23: 46 2.3 x
24: 48 2.4 y
25: 50 2.5 z
26: 52 2.6 {
27: 54 2.7 |
28: 56 2.8 }
29: 58 2.9 ~
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment