From c86c3c0c9b7dceab2407364b7603de0356260cfa Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Sun, 29 Jan 2023 18:22:56 -0500 Subject: [PATCH 01/16] WIP --- ais_tools/tagblock.c | 122 +++++++++++++++++++++++++++++++++++++++++ setup.py | 8 +++ tests/test_tagblock.py | 7 +++ 3 files changed, 137 insertions(+) create mode 100644 ais_tools/tagblock.c diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c new file mode 100644 index 0000000..1a1d507 --- /dev/null +++ b/ais_tools/tagblock.c @@ -0,0 +1,122 @@ +// tagblock module + +#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ +#include +#include +#include + +#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) + +const size_t MAX_TAGBLOCK_FIELDS = 8; +const size_t MAX_TAGBLOCK_STR_LEN = 1024; +const char * FIELD_SEPARATOR = ","; +const char * KEY_VALUE_SEPARATOR = ":"; + +struct TAGBLOCK_FIELD +{ + const char* key; + const char* value; +}; + +// copy str into dest up to end +// return a pointer to the position immediately after the last position written in dest +// does not copy the null terminator from str +static char *util_cat(char *dest, const char *end, const char *str) +{ + while (dest < end && *str) + *dest++ = *str++; + return dest; +} + +//size_t join_str(char *out_string, size_t out_bufsz, const char *delim, char **chararr) +//{ +// char *ptr = out_string; +// char *strend = out_string + out_bufsz; +// while (ptr < strend && *chararr) +// { +// ptr = util_cat(ptr, strend, *chararr); +// chararr++; +// if (*chararr) +// ptr = util_cat(ptr, strend, delim); +// } +// return ptr - out_string; +//} + +size_t split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, size_t max_fields) +{ + size_t idx = 0; + char * field_save_ptr = NULL; + char * field; + + char * key_value_save_ptr = NULL; + + field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); + while (field && idx < max_fields) + { + fields[idx].key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + fields[idx].value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + idx++; + field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); + } + return idx; +} + +size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) +{ + const char * end = tagblock_str + buf_size - 1; + size_t last_field_idx = num_fields - 1; + char * ptr = tagblock_str; + + for (size_t idx = 0; idx < num_fields; idx++) + { + ptr = util_cat(ptr, end, fields[idx].key); + ptr = util_cat(ptr, end, KEY_VALUE_SEPARATOR); + ptr = util_cat(ptr, end, fields[idx].value); + if (idx < last_field_idx) + { + ptr = util_cat(ptr, end, FIELD_SEPARATOR); + } + } + *ptr++ = '\0'; + return ptr - tagblock_str; +} + + +static PyObject * +tagblock_decode(PyObject *module, PyObject *args) +{ + char *tagblock_str; + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + size_t num_fields = 0; + + if (!PyArg_ParseTuple(args, "s", &tagblock_str)) + return NULL; + + num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); + for (unsigned int i = 0; i < num_fields; i++) + { + printf("%s %s \n", fields[i].key, fields[i].value); + } + + return PyUnicode_FromString("tags"); +} + +static PyMethodDef tagblock_methods[] = { + {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_VARARGS, + "decode a tagblock string. Returns a dict"}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef tagblock_module = { + PyModuleDef_HEAD_INIT, + "_tagblock", + NULL, + -1, + tagblock_methods +}; + +PyMODINIT_FUNC +PyInit__tagblock(void) +{ + return PyModule_Create(&tagblock_module); +} \ No newline at end of file diff --git a/setup.py b/setup.py index 6eba8b7..8a6a952 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,14 @@ sources=["ais_tools/checksum.c"], include_dirs=["ais_tools/"], undef_macros=undef_macros, + ), + Extension( + "ais_tools._tagblock", + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + sources=["ais_tools/tagblock.c"], + include_dirs=["ais_tools/"], + undef_macros=undef_macros, ) ], ) diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index d96d9e7..39da6ba 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -3,6 +3,8 @@ from ais_tools import tagblock from ais_tools.tagblock import DecodeError +from ais_tools import _tagblock + @pytest.mark.parametrize("line,expected", [ ("\\s:rORBCOMM000,q:u,c:1509502436,T:2017-11-01 02.13.56*50\\!AIVDM,1,1,,A,13`el0gP000H=3JN9jb>4?wb0>`<,0*7B", @@ -130,3 +132,8 @@ def test_decode_tagblock_invalid(tagblock_str): ]) def test_update_tagblock(tagblock_str, new_fields, expected): assert expected == tagblock.update_tagblock(tagblock_str, **new_fields) + + + +def test_split_fields(): + assert _tagblock.decode('a:1,b:2') == 'tags' From dd646dbf48c3e8d39250e6d1a74f130a8ef4ed3c Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Sun, 5 Feb 2023 13:43:44 -0500 Subject: [PATCH 02/16] tagblock encode and decode in c` --- ais_tools/checksum.c | 36 +++- ais_tools/checksum.h | 6 + ais_tools/tagblock.c | 394 +++++++++++++++++++++++++++++++++++------ ais_tools/tagblock.py | 108 ++++++----- tests/test_checksum.py | 1 + tests/test_tagblock.py | 52 +++++- utils/perf-test.py | 9 +- 7 files changed, 498 insertions(+), 108 deletions(-) create mode 100644 ais_tools/checksum.h diff --git a/ais_tools/checksum.c b/ais_tools/checksum.c index f707296..78948fa 100644 --- a/ais_tools/checksum.c +++ b/ais_tools/checksum.c @@ -4,6 +4,7 @@ #include #include #include +#include "checksum.h" int _checksum(const char *s) { @@ -23,18 +24,36 @@ char* _checksum_str(const char * s, char* checksum) bool _is_checksum_valid(char* s) { const char * skip_chars = "!?\\"; - const char * separator = "*"; + const char separator = '*'; +// const char * separator = "*"; char* body = s; char* checksum = NULL; char computed_checksum[3]; - char* lasts = NULL; +// char* lasts = NULL; if (*body && strchr(skip_chars, body[0])) body++; - body = strtok_r(body, separator, &lasts); - checksum = strtok_r(NULL, separator, &lasts); + char* ptr = body; + while (*ptr != '\0' && *ptr != separator) + ptr++; + + if (*ptr == '*') + *ptr++ = '\0'; + checksum = ptr; + +// if (*body == *separator) +// { +// // special case with zero-length body +// *body = '\0'; +// checksum = body + 1; +// } +// else +// { +// body = strtok_r(body, separator, &lasts); +// checksum = strtok_r(NULL, separator, &lasts); +// } if (checksum == NULL || strlen(checksum) != 2) return false; @@ -70,11 +89,18 @@ static PyObject * checksum_is_checksum_valid(PyObject *module, PyObject *args) { char *str; + char buffer[MAX_SENTENCE_LENGTH]; if (!PyArg_ParseTuple(args, "s", &str)) return NULL; - return _is_checksum_valid(str) ? Py_True: Py_False; + if (strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + { + PyErr_SetString(PyExc_ValueError, "String too long"); + return NULL; + } + + return _is_checksum_valid(buffer) ? Py_True: Py_False; } diff --git a/ais_tools/checksum.h b/ais_tools/checksum.h new file mode 100644 index 0000000..675094a --- /dev/null +++ b/ais_tools/checksum.h @@ -0,0 +1,6 @@ + +#define MAX_SENTENCE_LENGTH 1024 + +#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) + +char* _checksum_str(const char * s, char* checksum); diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c index 1a1d507..c35fb23 100644 --- a/ais_tools/tagblock.c +++ b/ais_tools/tagblock.c @@ -4,106 +4,398 @@ #include #include #include +#include +#include "checksum.h" -#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) + +#define SUCCESS 0 +#define FAIL -1 const size_t MAX_TAGBLOCK_FIELDS = 8; const size_t MAX_TAGBLOCK_STR_LEN = 1024; +const size_t MAX_KEY_LEN = 32; +const size_t MAX_VALUE_LEN = 256; +const char * CHECKSUM_SEPARATOR = "*"; const char * FIELD_SEPARATOR = ","; const char * KEY_VALUE_SEPARATOR = ":"; +const char * GROUP_SEPARATOR = "-"; struct TAGBLOCK_FIELD { - const char* key; - const char* value; + char key[2]; + const char* value; +}; + +#define TAGBLOCK_TIMESTAMP "tagblock_timestamp" +#define TAGBLOCK_DESTINATION "tagblock_destination" +#define TAGBLOCK_LINE_COUNT "tagblock_line_count" +#define TAGBLOCK_RELATIVE_TIME "tagblock_relative_time" +#define TAGBLOCK_STATION "tagblock_station" +#define TAGBLOCK_TEXT "tagblock_text" +#define TAGBLOCK_SENTENCE "tagblock_sentence" +#define TAGBLOCK_GROUPSIZE "tagblock_groupsize" +#define TAGBLOCK_ID "tagblock_id" +#define CUSTOM_FIELD_PREFIX "tagblock_" +#define TAGBLOCK_GROUP "g" + +typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; +static KEY_MAP key_map[] = { + {"c", TAGBLOCK_TIMESTAMP}, + {"d", TAGBLOCK_DESTINATION}, + {"n", TAGBLOCK_LINE_COUNT}, + {"r", TAGBLOCK_RELATIVE_TIME}, + {"s", TAGBLOCK_STATION}, + {"t", TAGBLOCK_TEXT} }; +static const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; + +const char* lookup_long_key(const char *short_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) + if (0 == strcmp(key_map[i].short_key, short_key)) + return key_map[i].long_key; + return NULL; +} + +const char* lookup_short_key(const char* long_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) + if (0 == strcmp(key_map[i].long_key, long_key)) + return key_map[i].short_key; + return NULL; +} + +size_t lookup_group_field_key(const char* long_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) + if (0 == strcmp(long_key, group_field_keys[i])) + return i + 1; + return 0; +} + +void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) +{ + size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; + + if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) + strlcpy(buffer, &long_key[prefix_len], buf_size); + else + strlcpy(buffer, long_key, buf_size); +} + +void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) +{ + for (size_t i = 0; i < num_fields; i++) + { + fields[i].key[0] = '\0'; + fields[i].value = NULL; + } +} + // copy str into dest up to end // return a pointer to the position immediately after the last position written in dest // does not copy the null terminator from str -static char *util_cat(char *dest, const char *end, const char *str) +static char *unsafe_strcat(char *dest, const char *end, const char *str) { while (dest < end && *str) *dest++ = *str++; return dest; } -//size_t join_str(char *out_string, size_t out_bufsz, const char *delim, char **chararr) -//{ -// char *ptr = out_string; -// char *strend = out_string + out_bufsz; -// while (ptr < strend && *chararr) -// { -// ptr = util_cat(ptr, strend, *chararr); -// chararr++; -// if (*chararr) -// ptr = util_cat(ptr, strend, delim); -// } -// return ptr - out_string; -//} - -size_t split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, size_t max_fields) +// split a tagblock string with structure +// k:value,k:value*cc +int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) { - size_t idx = 0; - char * field_save_ptr = NULL; - char * field; + int idx = 0; + char * field_save_ptr = NULL; + char * field; + + char * key_value_save_ptr = NULL; + + // strip off the checksum if present + field_save_ptr = strstr(tagblock_str, CHECKSUM_SEPARATOR); + if (field_save_ptr != NULL) + *field_save_ptr = '\0'; - char * key_value_save_ptr = NULL; +// tagblock_str = strtok_r(tagblock_str, CHECKSUM_SEPARATOR, &field_save_ptr); - field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); - while (field && idx < max_fields) - { - fields[idx].key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); - fields[idx].value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); - idx++; - field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); - } - return idx; + // make sure we have something to decode + if (!tagblock_str || !*tagblock_str) + return 0; + + // get the first comma delimited field + field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); + while (field && *field && idx < max_fields) + { + // for each field, split into key part and value part + const char* key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + const char* value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + + // if we don't have both key and value, then fail + if (key && value) + { + strlcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); + fields[idx].value = value; + idx++; + } + else + return FAIL; + + // advance to the next field + field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); + } + return idx; } size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) { - const char * end = tagblock_str + buf_size - 1; - size_t last_field_idx = num_fields - 1; - char * ptr = tagblock_str; + const char * end = tagblock_str + buf_size - 1; + size_t last_field_idx = num_fields - 1; + char * ptr = tagblock_str; - for (size_t idx = 0; idx < num_fields; idx++) - { - ptr = util_cat(ptr, end, fields[idx].key); - ptr = util_cat(ptr, end, KEY_VALUE_SEPARATOR); - ptr = util_cat(ptr, end, fields[idx].value); - if (idx < last_field_idx) + for (size_t idx = 0; idx < num_fields; idx++) { - ptr = util_cat(ptr, end, FIELD_SEPARATOR); + ptr = unsafe_strcat(ptr, end, fields[idx].key); + ptr = unsafe_strcat(ptr, end, KEY_VALUE_SEPARATOR); + ptr = unsafe_strcat(ptr, end, fields[idx].value); + if (idx < last_field_idx) + { + ptr = unsafe_strcat(ptr, end, FIELD_SEPARATOR); + } } - } - *ptr++ = '\0'; - return ptr - tagblock_str; + *ptr++ = '\0'; // very important! unsafe_strcat does not add a null at the end of the string + return ptr - tagblock_str; } +int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + char * end; + + long value = strtol(field->value, &end, 10); + if (errno || *end) + return FAIL; + + if (value > 40000000000) + value = value / 1000; + PyDict_SetItemString(dict, key, PyLong_FromLong(value)); + return SUCCESS; +} + +int decode_int_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + char* end; + + long value = strtol(field->value, &end, 10); + if (errno || *end) + return FAIL; + + PyDict_SetItemString(dict, key, PyLong_FromLong(value)); + + return SUCCESS; +} + +int decode_text_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + PyDict_SetItemString(dict, key, PyUnicode_FromString(field->value)); + + return SUCCESS; +} + +int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + size_t idx = 0; + char * save_ptr = NULL; + long values[ARRAY_LENGTH(group_field_keys)]; + char buffer[MAX_VALUE_LEN]; + + if (strlcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + return FAIL; + + char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); + while (f && idx < ARRAY_LENGTH(values)) + { + char* end; + values[idx++] = strtol(f, &end, 10); + if (errno || *end) + return FAIL; + + f = strtok_r(NULL, GROUP_SEPARATOR, &save_ptr); + } + if (idx == ARRAY_LENGTH(values)) + { + for (idx = 0; idx < ARRAY_LENGTH(values); idx++) + PyDict_SetItemString(dict, group_field_keys[idx], PyLong_FromLong(values[idx])); + return SUCCESS; + } + + return FAIL; +} + +int decode_custom_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + char custom_key[MAX_KEY_LEN]; + snprintf(custom_key, ARRAY_LENGTH(custom_key), "%s%s", CUSTOM_FIELD_PREFIX, field->key); + PyDict_SetItemString(dict, custom_key, PyUnicode_FromString(field->value)); + + return SUCCESS; +} + +size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* group_fields) +{ + const char * end = buffer + buf_size - 1; + char * ptr = buffer; + + for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) + { + // make sure that all values are non-empty + if (group_fields[i].value == NULL) + return 0; + + ptr = unsafe_strcat(ptr, end, group_fields[i].value); + if (i < ARRAY_LENGTH(group_field_keys) - 1) + ptr = unsafe_strcat(ptr, end, GROUP_SEPARATOR); + } + *ptr++ = '\0'; // very important! unsafe_strcat does not add a null at the end of the string + + return ptr - buffer; +} static PyObject * tagblock_decode(PyObject *module, PyObject *args) { - char *tagblock_str; + char *param; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - size_t num_fields = 0; + int num_fields = 0; + int status = SUCCESS; + + + if (!PyArg_ParseTuple(args, "s", ¶m)) + return NULL; - if (!PyArg_ParseTuple(args, "s", &tagblock_str)) + if (strlcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) + { + PyErr_SetString(PyExc_ValueError, "Tagblock string too long"); return NULL; + } + + PyObject* dict = PyDict_New(); num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); - for (unsigned int i = 0; i < num_fields; i++) + if (num_fields < 0) + status = FAIL; + + for (int i = 0; i < num_fields && status==SUCCESS; i++) + { + struct TAGBLOCK_FIELD* field = &fields[i]; + const char* key = field->key; + + switch(key[0]) + { + case 'c': + status = decode_timestamp_field(field, dict); + break; + case 'd': + case 's': + case 't': + status = decode_text_field(field, dict); + break; + case 'g': + status = decode_group_field(field, dict); + break; + case 'n': + case 'r': + status = decode_int_field(field, dict); + break; + default: + status = decode_custom_field(field, dict); + } + } + + if (status == SUCCESS) + return dict; + else { - printf("%s %s \n", fields[i].key, fields[i].value); + PyErr_SetString(PyExc_ValueError, "Tagblock decode failure"); + return NULL; } +} + +static PyObject * +tagblock_encode(PyObject *module, PyObject *args) +{ + + PyObject *dict, *key, *value; + Py_ssize_t pos = 0; + + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; + + init_fields (fields, ARRAY_LENGTH(group_fields)); + init_fields (group_fields, ARRAY_LENGTH(group_fields)); + + size_t field_idx = 0; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; + char checksum_str[3]; + char group_field_value [MAX_VALUE_LEN]; + + if (!PyArg_ParseTuple(args, "O", &dict)) + return NULL; + + while (PyDict_Next(dict, &pos, &key, &value) && field_idx < ARRAY_LENGTH(fields) ) { + const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); + const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); + + size_t group_ordinal = lookup_group_field_key(key_str); + if (group_ordinal > 0) + { +// group_fields[group_ordinal - 1].key[0] = '\0'; + group_fields[group_ordinal - 1].value = value_str; + } + else + { + const char* short_key = lookup_short_key (key_str); + + if (short_key) + strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); + else + extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); + + fields[field_idx].value = value_str; + field_idx++; + } + } + + // encode group field and add it to the field list + if (field_idx < ARRAY_LENGTH(fields)) + { + // check the return code to see if there is a complete set of group fields + if (encode_group_fields(group_field_value, ARRAY_LENGTH(group_field_value), group_fields)) + { + strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); + fields[field_idx].value = group_field_value; + field_idx++; + } + } + + join_fields(fields, field_idx, tagblock_str, ARRAY_LENGTH(tagblock_str) - 3); + + _checksum_str(tagblock_str, checksum_str); + strcat(tagblock_str, CHECKSUM_SEPARATOR); + strcat(tagblock_str, checksum_str); - return PyUnicode_FromString("tags"); + return PyUnicode_FromString(tagblock_str); } static PyMethodDef tagblock_methods[] = { {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_VARARGS, "decode a tagblock string. Returns a dict"}, + {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_VARARGS, + "encode a tagblock string from a dict. Returns a string"}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 7a10886..7100af3 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -4,6 +4,7 @@ from ais import DecodeError from ais_tools.checksum import checksumstr from ais_tools.checksum import is_checksum_valid +from ais_tools import _tagblock # import warnings # with warnings.catch_warnings(): @@ -102,58 +103,73 @@ def add_tagblock(tagblock, nmea, overwrite=True): def encode_tagblock(**kwargs): - group_fields = {} - fields = {} - - for k, v in kwargs.items(): - if k in tagblock_group_fields: - group_fields[k] = str(v) - elif k in tagblock_fields_reversed: - fields[tagblock_fields_reversed[k]] = v - else: - fields[k.replace('tagblock_', '')] = v - - if len(group_fields) == 3: - fields['g'] = '-'.join([group_fields[k] for k in tagblock_group_fields]) - - base_str = ','.join(["{}:{}".format(k, v) for k, v in fields.items()]) - return '{}*{}'.format(base_str, checksumstr(base_str)) + try: + return _tagblock.encode(kwargs) + except: + raise DecodeError('unable to encode tagblock') + # print(kwargs) + # print(t1) + + # group_fields = {} + # fields = {} + # + # for k, v in kwargs.items(): + # if k in tagblock_group_fields: + # group_fields[k] = str(v) + # elif k in tagblock_fields_reversed: + # fields[tagblock_fields_reversed[k]] = v + # else: + # fields[k.replace('tagblock_', '')] = v + # + # if len(group_fields) == 3: + # fields['g'] = '-'.join([group_fields[k] for k in tagblock_group_fields]) + # + # base_str = ','.join(["{}:{}".format(k, v) for k, v in fields.items()]) + # t2 = '{}*{}'.format(base_str, checksumstr(base_str)) def decode_tagblock(tagblock_str, validate_checksum=False): - - tagblock = tagblock_str.rsplit("*", 1)[0] - - fields = {} - - if not tagblock: - return fields - if validate_checksum and not is_checksum_valid(tagblock_str): raise DecodeError('Invalid checksum') - for field in tagblock.split(","): - try: - key, value = field.split(":") - - if key == 'g': - parts = [int(part) for part in value.split("-") if part] - if len(parts) != 3: - raise DecodeError('Unable to decode tagblock group') - fields.update(dict(zip(tagblock_group_fields, parts))) - else: - if key in ['n', 'r']: - value = int(value) - elif key == 'c': - value = int(value) - if value > 40000000000: - value = value / 1000.0 - - fields[tagblock_fields.get(key, key)] = value - except ValueError: - raise DecodeError('Unable to decode tagblock string') - - return fields + try: + return _tagblock.decode(tagblock_str) + except: + raise DecodeError('Unable to decode tagblock') + + + # tagblock = tagblock_str.rsplit("*", 1)[0] + # + # fields = {} + # + # if not tagblock: + # return fields + # + # if validate_checksum and not is_checksum_valid(tagblock_str): + # raise DecodeError('Invalid checksum') + # + # for field in tagblock.split(","): + # try: + # key, value = field.split(":") + # + # if key == 'g': + # parts = [int(part) for part in value.split("-") if part] + # if len(parts) != 3: + # raise DecodeError('Unable to decode tagblock group') + # fields.update(dict(zip(tagblock_group_fields, parts))) + # else: + # if key in ['n', 'r']: + # value = int(value) + # elif key == 'c': + # value = int(value) + # if value > 40000000000: + # value = value / 1000.0 + # + # fields[tagblock_fields.get(key, key)] = value + # except ValueError: + # raise DecodeError('Unable to decode tagblock string') + # + # return fields def update_tagblock(nmea, **kwargs): diff --git a/tests/test_checksum.py b/tests/test_checksum.py index 561e5c2..1691f3d 100644 --- a/tests/test_checksum.py +++ b/tests/test_checksum.py @@ -36,6 +36,7 @@ def test_checksum_str(str, expected): @pytest.mark.parametrize("str,expected", [ ("", False), + ("*00", True), ("*", False), ("4", False), ("40", False), diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index 39da6ba..7711e46 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -82,7 +82,7 @@ def test_encode_tagblock(fields, expected): @pytest.mark.parametrize("tagblock_str,expected", [ ('*00', {}), - ('z:123*70', {'z': '123'}), + ('z:123*70', {'tagblock_z': '123'}), ('r:123*78', {'tagblock_relative_time': 123}), ('c:123456789*68', {'tagblock_timestamp': 123456789}), ('c:123456789,s:test,g:1-2-3*5A', @@ -113,8 +113,10 @@ def test_decode_tagblock_invalid_checksum(tagblock_str): ('invalid'), ('c:invalid'), ('c:123456789,z'), + ('n:not_a_number'), ('g:BAD,c:1326055296'), - ('g:1-2,c:1326055296') + ('g:1-2,c:1326055296'), + ('g:1-2-BAD'), ]) def test_decode_tagblock_invalid(tagblock_str): with pytest.raises(DecodeError, match='Unable to decode tagblock'): @@ -135,5 +137,47 @@ def test_update_tagblock(tagblock_str, new_fields, expected): -def test_split_fields(): - assert _tagblock.decode('a:1,b:2') == 'tags' +@pytest.mark.parametrize("tagblock_str,expected", [ + ('', {}), + ('*00', {}), + ('a:1', {'tagblock_a': '1'}), + ('d:dest*00', {'tagblock_destination': 'dest'}), + ('n:42', {'tagblock_line_count': 42}), + ("c:123456789", {'tagblock_timestamp': 123456789}), + ('g:1-2-3', {'tagblock_sentence': 1, 'tagblock_groupsize': 2, 'tagblock_id': 3}) +]) +def test_tagblock_decode(tagblock_str, expected): + assert _tagblock.decode(tagblock_str) == expected + + +@pytest.mark.parametrize("fields,expected", [ + (None,'*00'), + ({},'*00'), + ({'a': 1}, 'a:1*6A'), + ({'tagblock_a': 1}, 'a:1*6A'), + ({'tagblock_a': None}, 'a:None*71'), + ({'tagblock_timestamp': 12345678}, "c:12345678*51"), + ({'tagblock_sentence': 1, 'tagblock_groupsize': 2, 'tagblock_id': 3}, 'g:1-2-3*6D'), + ({'tagblock_line_count': 1}, 'n:1*65') +]) +def test_tagblock_encode(fields, expected): + assert _tagblock.encode(fields) == expected + + +@pytest.mark.parametrize("fields", [ + ({}), + ({'tagblock_a': '1'}), + ({'tagblock_timestamp': 12345678}), + ({'tagblock_timestamp': 12345678, + 'tagblock_destination': 'D', + 'tagblock_line_count': 2, + 'tagblock_station': 'S', + 'tagblock_text': 'T', + 'tagblock_relative_time': 12345678, + 'tagblock_sentence': 1, + 'tagblock_groupsize': 2, + 'tagblock_id': 3 + }), +]) +def test_encode_decode(fields): + assert _tagblock.decode(_tagblock.encode(fields)) == fields diff --git a/utils/perf-test.py b/utils/perf-test.py index d707bcc..81cb7ec 100644 --- a/utils/perf-test.py +++ b/utils/perf-test.py @@ -3,6 +3,7 @@ import pstats from pstats import SortKey from ais_tools.aivdm import AIVDM, AisToolsDecoder +from ais_tools.tagblock import update_tagblock # from ais_tools.aivdm import LibaisDecoder @@ -16,7 +17,6 @@ "\\g:2-2-2243*5A" \ "\\!AIVDM,2,2,1,B,p4l888888888880,2*36" - def libais_vs_aistools(): tests = [type_1, type_18] @@ -60,6 +60,10 @@ def full_decode(n): msg.add_parser_version() +def update_tgblock(n): + for i in range(n): + update_tagblock(message1, tagblock_text='T') + def run_perf_test(func): cProfile.run(func, 'perf-test.stats') @@ -68,7 +72,8 @@ def run_perf_test(func): def main(): - run_perf_test('decode(10000)') + run_perf_test('update_tgblock(1000000)') + # run_perf_test('decode(10000)') # run_perf_test('full_decode(100000)') # checksum_compare() From 0be240af7e26b3739508845f2b39d145f878a014 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 7 Feb 2023 11:45:14 -0500 Subject: [PATCH 03/16] implement update_tagblock in c --- ais_tools/tagblock.c | 258 +++++++++++++++++++++++++++++++++------ ais_tools/tagblock.py | 9 +- tests/test_edge_cases.py | 3 +- tests/test_tagblock.py | 7 ++ 4 files changed, 235 insertions(+), 42 deletions(-) diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c index c35fb23..0ce6f39 100644 --- a/ais_tools/tagblock.c +++ b/ais_tools/tagblock.c @@ -38,6 +38,10 @@ struct TAGBLOCK_FIELD #define CUSTOM_FIELD_PREFIX "tagblock_" #define TAGBLOCK_GROUP "g" +#define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" +#define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" +#define ERR_TOO_MANY_FIELDS "Too many fields" + typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; static KEY_MAP key_map[] = { {"c", TAGBLOCK_TIMESTAMP}, @@ -118,8 +122,6 @@ int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fiel if (field_save_ptr != NULL) *field_save_ptr = '\0'; -// tagblock_str = strtok_r(tagblock_str, CHECKSUM_SEPARATOR, &field_save_ptr); - // make sure we have something to decode if (!tagblock_str || !*tagblock_str) return 0; @@ -148,11 +150,13 @@ int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fiel return idx; } +// TODO: need a return value that indicates the string is too long size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) { - const char * end = tagblock_str + buf_size - 1; + const char * end = tagblock_str + buf_size - 4; size_t last_field_idx = num_fields - 1; char * ptr = tagblock_str; + char checksum_str[3]; for (size_t idx = 0; idx < num_fields; idx++) { @@ -165,7 +169,14 @@ size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* } } *ptr++ = '\0'; // very important! unsafe_strcat does not add a null at the end of the string - return ptr - tagblock_str; + + // TODO: use unsafe_strcat instead of strcat + _checksum_str(tagblock_str, checksum_str); + + strcat(tagblock_str, CHECKSUM_SEPARATOR); + strcat(tagblock_str, checksum_str); + + return strlen(tagblock_str); } int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) @@ -265,21 +276,22 @@ size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* } static PyObject * -tagblock_decode(PyObject *module, PyObject *args) +tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { - char *param; + const char *param; char tagblock_str[MAX_TAGBLOCK_STR_LEN]; struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; int num_fields = 0; int status = SUCCESS; - - if (!PyArg_ParseTuple(args, "s", ¶m)) + if (nargs != 1) return NULL; + param = PyUnicode_AsUTF8(PyObject_Str(args[0])); + if (strlcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) { - PyErr_SetString(PyExc_ValueError, "Tagblock string too long"); + PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); return NULL; } @@ -320,40 +332,31 @@ tagblock_decode(PyObject *module, PyObject *args) return dict; else { - PyErr_SetString(PyExc_ValueError, "Tagblock decode failure"); + PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); return NULL; } } -static PyObject * -tagblock_encode(PyObject *module, PyObject *args) +int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) { - - PyObject *dict, *key, *value; + PyObject *key, *value; Py_ssize_t pos = 0; - - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + size_t field_idx = 0; struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; - init_fields (fields, ARRAY_LENGTH(group_fields)); init_fields (group_fields, ARRAY_LENGTH(group_fields)); - size_t field_idx = 0; - char tagblock_str[MAX_TAGBLOCK_STR_LEN]; - char checksum_str[3]; - char group_field_value [MAX_VALUE_LEN]; - - if (!PyArg_ParseTuple(args, "O", &dict)) - return NULL; + while (PyDict_Next(dict, &pos, &key, &value)) + { + if (field_idx == max_fields) + return FAIL; // no more room in fields - while (PyDict_Next(dict, &pos, &key, &value) && field_idx < ARRAY_LENGTH(fields) ) { const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); size_t group_ordinal = lookup_group_field_key(key_str); if (group_ordinal > 0) { -// group_fields[group_ordinal - 1].key[0] = '\0'; group_fields[group_ordinal - 1].value = value_str; } else @@ -371,31 +374,210 @@ tagblock_encode(PyObject *module, PyObject *args) } // encode group field and add it to the field list - if (field_idx < ARRAY_LENGTH(fields)) + // check the return code to see if there is a complete set of group fields + if (encode_group_fields(buffer, buf_size, group_fields)) { - // check the return code to see if there is a complete set of group fields - if (encode_group_fields(group_field_value, ARRAY_LENGTH(group_field_value), group_fields)) + if (field_idx >= max_fields) + return FAIL; // no more room to add another field + + strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); + fields[field_idx].value = buffer; + field_idx++; + } + + return field_idx; +} + + +int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, + struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) +{ + for (size_t update_idx = 0; update_idx < num_update_fields; update_idx++) + { + char* key = update_fields[update_idx].key; + + size_t fields_idx = 0; + while (fields_idx < num_fields) { - strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); - fields[field_idx].value = group_field_value; - field_idx++; + if (0 == strcmp(key, fields[fields_idx].key)) + break; + fields_idx++; + } + if (fields_idx == num_fields) + { + if (num_fields < max_fields) + num_fields++; + else + return FAIL; } + + fields[fields_idx] = update_fields[update_idx]; + + +// +// for (size_t fields_idx = 0; fields_idx < num_fields; fields_idx++) +// { +// if (0 == strcmp(key, fields[fields_idx].key)) +// { +// // found a matching field. Replace the value +// fields[fields_idx].value = update_fields[update_idx].value; +// break; +// } +// } +// // no matching field found. Append to the end if there is room +// if (num_fields < max_fields) +// fields[num_fields++] = update_fields[update_idx]; +// else +// return FAIL; } - join_fields(fields, field_idx, tagblock_str, ARRAY_LENGTH(tagblock_str) - 3); + return num_fields; +} - _checksum_str(tagblock_str, checksum_str); - strcat(tagblock_str, CHECKSUM_SEPARATOR); - strcat(tagblock_str, checksum_str); + + +static PyObject * +tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + + PyObject *dict; +// , *key, *value; +// Py_ssize_t pos = 0; + + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; +// struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; + + init_fields (fields, ARRAY_LENGTH(fields)); +// init_fields (group_fields, ARRAY_LENGTH(group_fields)); + +// size_t field_idx = 0; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// char checksum_str[3]; + char value_buffer [MAX_VALUE_LEN]; + + if (nargs != 1) + return NULL; + + dict = args[0]; + + int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); + if (num_fields < 0) + { + PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + return NULL; + } + +// while (PyDict_Next(dict, &pos, &key, &value) && field_idx < ARRAY_LENGTH(fields) ) { +// const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); +// const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); +// +// size_t group_ordinal = lookup_group_field_key(key_str); +// if (group_ordinal > 0) +// { +// group_fields[group_ordinal - 1].value = value_str; +// } +// else +// { +// const char* short_key = lookup_short_key (key_str); +// +// if (short_key) +// strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); +// else +// extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); +// +// fields[field_idx].value = value_str; +// field_idx++; +// } +// } +// +// // encode group field and add it to the field list +// if (field_idx < ARRAY_LENGTH(fields)) +// { +// // check the return code to see if there is a complete set of group fields +// if (encode_group_fields(group_field_value, ARRAY_LENGTH(group_field_value), group_fields)) +// { +// strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); +// fields[field_idx].value = group_field_value; +// field_idx++; +// } +// } + + join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); + +// _checksum_str(tagblock_str, checksum_str); +// strcat(tagblock_str, CHECKSUM_SEPARATOR); +// strcat(tagblock_str, checksum_str); return PyUnicode_FromString(tagblock_str); } + +static PyObject * +tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char* str; + PyObject* dict; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + int num_fields = 0; + struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; + int num_update_fields = 0; + char value_buffer[MAX_VALUE_LEN]; + + +// int status = SUCCESS; + + if (nargs != 2) + { + PyErr_SetString(PyExc_TypeError, "update expects 2 arguments"); + return NULL; + } + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + dict = args[1]; + + if (strlcpy(tagblock_str, str, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) + { + PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + return NULL; + } + + num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); + if (num_fields < 0) + { + PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); + return NULL; + } + + // TODO: return failure if (num_update_fields < 0) + num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), + value_buffer, ARRAY_LENGTH(value_buffer)); + if (num_update_fields < 0) + { + PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + return NULL; + } + + num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); + if (num_fields < 0) + { + PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + return NULL; + } + + join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); + + return PyUnicode_FromString(tagblock_str); +} + + static PyMethodDef tagblock_methods[] = { - {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_VARARGS, + {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_FASTCALL, "decode a tagblock string. Returns a dict"}, - {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_VARARGS, + {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_FASTCALL, "encode a tagblock string from a dict. Returns a string"}, + {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, + "update a tagblock string from a dict. Returns a string"}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 7100af3..f911ccc 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -174,9 +174,12 @@ def decode_tagblock(tagblock_str, validate_checksum=False): def update_tagblock(nmea, **kwargs): tagblock_str, nmea = split_tagblock(nmea) - tagblock = decode_tagblock(tagblock_str) - tagblock.update(kwargs) - tagblock_str = encode_tagblock(**tagblock) + + tagblock_str = _tagblock.update(tagblock_str, kwargs) + # tagblock = decode_tagblock(tagblock_str) + # tagblock.update(kwargs) + # tagblock_str = encode_tagblock(**tagblock) + return join_tagblock(tagblock_str, nmea) diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 08380e2..16f0b42 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -10,9 +10,10 @@ def test_issue_40(): runner = CliRunner() input = "\\g:BAD-GROUP,c:1326055296*3C\\!AIVDM,2,1,3,A,E7`B1:dW7oHth@@@@@@@@@@@@@@6@6R;mMQM@10888Qr8`8888888888,0*65" args = '--station=test' + expected = "\\g:BAD-GROUP,c:1326055296,s:test*65\\!AIVDM,2,1,3,A,E7`B1:dW7oHth@@@@@@@@@@@@@@6@6R;mMQM@10888Qr8`8888888888,0*65" result = runner.invoke(update_tagblock, input=input, args=args) assert not result.exception - assert result.output.strip() == input + assert result.output.strip() == expected def test_tagblock_group_with_extra_delimiters(): diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index 7711e46..16b3288 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -181,3 +181,10 @@ def test_tagblock_encode(fields, expected): ]) def test_encode_decode(fields): assert _tagblock.decode(_tagblock.encode(fields)) == fields + + +def test_update(): + tagblock_str="z:1*71" + fields = {'tagblock_text': 'ABC'} + expected = "z:1,t:ABC*53" + assert _tagblock.update(tagblock_str, fields) == expected From e03259b7d48ac979f87a15eea5b43324eb10afd0 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Thu, 9 Feb 2023 15:30:33 -0500 Subject: [PATCH 04/16] more better tagblock parsing --- ais_tools/nmea.py | 4 + ais_tools/tagblock.c | 312 +++++++++++++++++++++++++++-------------- ais_tools/tagblock.py | 25 ++-- tests/test_tagblock.py | 7 +- 4 files changed, 232 insertions(+), 116 deletions(-) diff --git a/ais_tools/nmea.py b/ais_tools/nmea.py index 64554b7..17d92a9 100644 --- a/ais_tools/nmea.py +++ b/ais_tools/nmea.py @@ -14,6 +14,10 @@ def expand_nmea(line, validate_checksum=False): tagblock_str, nmea = split_tagblock(line) + if not nmea: + nmea = tagblock_str + tagblock_str = '' + tagblock = decode_tagblock(tagblock_str, validate_checksum=validate_checksum) nmea = nmea.strip() diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c index 0ce6f39..ea554dc 100644 --- a/ais_tools/tagblock.c +++ b/ais_tools/tagblock.c @@ -19,6 +19,9 @@ const char * CHECKSUM_SEPARATOR = "*"; const char * FIELD_SEPARATOR = ","; const char * KEY_VALUE_SEPARATOR = ":"; const char * GROUP_SEPARATOR = "-"; +const char* TAGBLOCK_SEPARATOR = "\\"; +const char* AIVDM_START = "!"; +const char * EMPTY_STRING = ""; struct TAGBLOCK_FIELD { @@ -41,6 +44,7 @@ struct TAGBLOCK_FIELD #define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" #define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" #define ERR_TOO_MANY_FIELDS "Too many fields" +#define ERR_NMEA_TOO_LONG "NMEA string too long" typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; static KEY_MAP key_map[] = { @@ -107,24 +111,143 @@ static char *unsafe_strcat(char *dest, const char *end, const char *str) return dest; } +int split_tagblock(char* message, const char** tagblock, const char** nmea) +{ + // Four cases for the given message string + + // starts with '!' - no tagblock, message is all nmea + // starts with '\!' - no tagblock, strip off '\', message is all nmea + // starts with '\[^!]' - is a tagblock , strip off leading '\', nmea is whatever comes after the next '\'. + // start with '[^\!]' - is a tagblock, nmea is whatever comes after the next '\'. + // starts with '[^!]' and there are no `\` delimiters - tagblock is empty, entire string in nmea + + char* ptr; + int tagblock_len = 0; + + ptr = message; + if (*ptr == *TAGBLOCK_SEPARATOR) + ptr ++; + if (*ptr == *AIVDM_START) + { + *nmea = ptr; + *tagblock = EMPTY_STRING; + } + else + { + *tagblock = ptr; + for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); + tagblock_len = ptr - *tagblock; + if (*ptr) + { + *ptr = '\0'; + *nmea = ptr + 1; + } +// else if (*message == *TAGBLOCK_SEPARATOR) +// { +// *nmea = *tagblock; +// *tagblock = EMPTY_STRING; +// } + else + *nmea = EMPTY_STRING; + } + + return tagblock_len; + + +// +// if (message[0] == *AIVDM_START) +// { +// *nmea = message; +// *tagblock = EMPTY_STRING; +// } +// else if (message[0] == *TAGBLOCK_SEPARATOR && message[1] != *AIVDM_START) +// { +// if (message[1] == *AIVDM_START) +// { +// *nmea = message; +// *tagblock = EMPTY_STRING; +// } +// else +// { +// for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); +// if (*ptr) +// { +// *ptr = '\0'; +// *tagblock = &message[1]; +// *nmea = ptr + 1; +// return ptr - message - 1; +// } +// } +// } +// else +// { +// *tagblock_str = message; +// *nmea = EMPTY_STRING; +// } +// +// if (message[0] != *AIVDM_START) +// *nmea = message; +// *tagblock = EMPTY_STRING; +// return 0; +} + +int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) +{ + char* end = buffer + buf_size - 1; + char* ptr = buffer; + + if (*tagblock_str && *nmea_str) + { + if (*tagblock_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcat(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcat(ptr, end, tagblock_str); + + if (*nmea_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcat(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcat(ptr, end, nmea_str); + } + else + { + ptr = unsafe_strcat(ptr, end, tagblock_str); + ptr = unsafe_strcat(ptr, end, nmea_str); + } + + if (ptr <= end) + { + *ptr = '\0'; + return ptr - buffer; + } + else + { + *end = '\0'; + return FAIL; + } + + return SUCCESS; +} + // split a tagblock string with structure +// k:value,k:value // k:value,k:value*cc +// \\k:value,k:value*cc\\other_stuff + int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) { int idx = 0; + char * ptr; char * field_save_ptr = NULL; char * field; char * key_value_save_ptr = NULL; - // strip off the checksum if present - field_save_ptr = strstr(tagblock_str, CHECKSUM_SEPARATOR); - if (field_save_ptr != NULL) - *field_save_ptr = '\0'; + // skip leading tagblock delimiter + if (*tagblock_str == *TAGBLOCK_SEPARATOR) + tagblock_str++; - // make sure we have something to decode - if (!tagblock_str || !*tagblock_str) - return 0; + // seek forward to find either the checksum, the next tagblock separator, or the end of the string. + // Terminate the string at the first delimiter found + for (ptr = tagblock_str; *ptr != *TAGBLOCK_SEPARATOR && *ptr != *CHECKSUM_SEPARATOR && *ptr != '\0'; ptr++); + *ptr = '\0'; // get the first comma delimited field field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); @@ -153,7 +276,7 @@ int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fiel // TODO: need a return value that indicates the string is too long size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) { - const char * end = tagblock_str + buf_size - 4; + const char * end = tagblock_str + buf_size - 5; // leave room for 3 chars for the checkcum plus trailing null size_t last_field_idx = num_fields - 1; char * ptr = tagblock_str; char checksum_str[3]; @@ -412,23 +535,6 @@ int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_f } fields[fields_idx] = update_fields[update_idx]; - - -// -// for (size_t fields_idx = 0; fields_idx < num_fields; fields_idx++) -// { -// if (0 == strcmp(key, fields[fields_idx].key)) -// { -// // found a matching field. Replace the value -// fields[fields_idx].value = update_fields[update_idx].value; -// break; -// } -// } -// // no matching field found. Append to the end if there is room -// if (num_fields < max_fields) -// fields[num_fields++] = update_fields[update_idx]; -// else -// return FAIL; } return num_fields; @@ -441,18 +547,12 @@ tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *dict; -// , *key, *value; -// Py_ssize_t pos = 0; struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; -// struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; init_fields (fields, ARRAY_LENGTH(fields)); -// init_fields (group_fields, ARRAY_LENGTH(group_fields)); -// size_t field_idx = 0; char tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// char checksum_str[3]; char value_buffer [MAX_VALUE_LEN]; if (nargs != 1) @@ -467,47 +567,8 @@ tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return NULL; } -// while (PyDict_Next(dict, &pos, &key, &value) && field_idx < ARRAY_LENGTH(fields) ) { -// const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); -// const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); -// -// size_t group_ordinal = lookup_group_field_key(key_str); -// if (group_ordinal > 0) -// { -// group_fields[group_ordinal - 1].value = value_str; -// } -// else -// { -// const char* short_key = lookup_short_key (key_str); -// -// if (short_key) -// strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); -// else -// extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); -// -// fields[field_idx].value = value_str; -// field_idx++; -// } -// } -// -// // encode group field and add it to the field list -// if (field_idx < ARRAY_LENGTH(fields)) -// { -// // check the return code to see if there is a complete set of group fields -// if (encode_group_fields(group_field_value, ARRAY_LENGTH(group_field_value), group_fields)) -// { -// strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); -// fields[field_idx].value = group_field_value; -// field_idx++; -// } -// } - join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); -// _checksum_str(tagblock_str, checksum_str); -// strcat(tagblock_str, CHECKSUM_SEPARATOR); -// strcat(tagblock_str, checksum_str); - return PyUnicode_FromString(tagblock_str); } @@ -517,59 +578,100 @@ tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { const char* str; PyObject* dict; - char tagblock_str[MAX_TAGBLOCK_STR_LEN]; - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - int num_fields = 0; - struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; - int num_update_fields = 0; - char value_buffer[MAX_VALUE_LEN]; - + char message[MAX_SENTENCE_LENGTH]; + char updated_message[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; -// int status = SUCCESS; if (nargs != 2) - { - PyErr_SetString(PyExc_TypeError, "update expects 2 arguments"); - return NULL; - } + return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); str = PyUnicode_AsUTF8(PyObject_Str(args[0])); dict = args[1]; - if (strlcpy(tagblock_str, str, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) - { - PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); - return NULL; - } + if (strlcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); + split_tagblock(message, &tagblock_str, &nmea_str); + + + char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; + if (strlcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + + + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + int num_fields = 0; + num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); if (num_fields < 0) - { - PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); - return NULL; - } + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); + - // TODO: return failure if (num_update_fields < 0) + struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; + int num_update_fields = 0; + char value_buffer[MAX_VALUE_LEN]; num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), value_buffer, ARRAY_LENGTH(value_buffer)); if (num_update_fields < 0) - { - PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - return NULL; - } + return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); if (num_fields < 0) - { - PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - return NULL; - } + return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); - return PyUnicode_FromString(tagblock_str); + char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; + join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); + + int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); + if (msg_len < 0) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + + return PyUnicode_FromString(updated_message); + } +static PyObject * +tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char* str; + char buffer[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; + + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + if (strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + split_tagblock(buffer, &tagblock_str, &nmea_str); + + return PyTuple_Pack(2, PyUnicode_FromString(tagblock_str), PyUnicode_FromString(nmea_str)); +} + +static PyObject * +tagblock_join(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + char buffer[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; + + if (nargs != 2) + return PyErr_Format(PyExc_TypeError, "join expects 2 arguments"); + + tagblock_str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + nmea_str = PyUnicode_AsUTF8(PyObject_Str(args[1])); + + if (FAIL == join_tagblock(buffer, ARRAY_LENGTH(buffer), tagblock_str, nmea_str)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + return PyUnicode_FromString(buffer); +} static PyMethodDef tagblock_methods[] = { {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_FASTCALL, @@ -578,6 +680,10 @@ static PyMethodDef tagblock_methods[] = { "encode a tagblock string from a dict. Returns a string"}, {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, "update a tagblock string from a dict. Returns a string"}, + {"split", (PyCFunction)(void(*)(void))tagblock_split, METH_FASTCALL, + "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, + {"join", (PyCFunction)(void(*)(void))tagblock_join, METH_FASTCALL, + "Join a tagblock to an AIVDM message. Returns a string."}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index f911ccc..1c02ce8 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -57,22 +57,27 @@ def split_tagblock(nmea): Note that if the nmea is a concatenated multipart message then only the tagblock of the first message will be split off """ - tagblock = '' - if nmea.startswith("\\") and not nmea.startswith("\\!"): - parts = nmea[1:].split("\\", 1) - if len(parts) == 2: - tagblock, nmea = parts - return tagblock, nmea + return _tagblock.split(nmea) + + # tagblock = '' + # if nmea.startswith("\\") and not nmea.startswith("\\!"): + # parts = nmea[1:].split("\\", 1) + # if len(parts) == 2: + # tagblock, nmea = parts + # return tagblock, nmea def join_tagblock(tagblock, nmea): """ Join a tagblock to an AIVDM message that does not already have a tagblock """ - if tagblock and nmea: - return "\\{}\\{}".format(tagblock.lstrip('\\'), nmea.lstrip('\\')) - else: - return "{}{}".format(tagblock, nmea) + + return _tagblock.join(tagblock, nmea) + + # if tagblock and nmea: + # return "\\{}\\{}".format(tagblock.lstrip('\\'), nmea.lstrip('\\')) + # else: + # return "{}{}".format(tagblock, nmea) def add_tagblock(tagblock, nmea, overwrite=True): diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index 16b3288..dc405b0 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -34,9 +34,10 @@ def test_create_tagblock(station, timestamp, add_tagblock_t, expected): @pytest.mark.parametrize("nmea,expected", [ ("!AIVDM", ('', '!AIVDM')), - ("\\!AIVDM", ('', '\\!AIVDM')), + ("\\!AIVDM", ('', '!AIVDM')), ("\\c:1000,s:sta*5B\\!AIVDM", ('c:1000,s:sta*5B', '!AIVDM')), - ("NOT A MESSAGE", ('', 'NOT A MESSAGE')), + ("\\c:1000,s:sta*5B", ('c:1000,s:sta*5B', '')), + ("NOT A MESSAGE", ('NOT A MESSAGE', '')), ]) def test_split_tagblock(nmea, expected): assert expected == tagblock.split_tagblock(nmea) @@ -184,7 +185,7 @@ def test_encode_decode(fields): def test_update(): - tagblock_str="z:1*71" + tagblock_str="\\z:1*71\\" fields = {'tagblock_text': 'ABC'} expected = "z:1,t:ABC*53" assert _tagblock.update(tagblock_str, fields) == expected From 301d27ecab9e5064c7aec90061c88f1d651b051d Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Thu, 9 Feb 2023 16:22:28 -0500 Subject: [PATCH 05/16] clean up dead code --- ais_tools/tagblock.c | 37 ------------------------------------- ais_tools/tagblock.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c index ea554dc..efe8e15 100644 --- a/ais_tools/tagblock.c +++ b/ais_tools/tagblock.c @@ -152,43 +152,6 @@ int split_tagblock(char* message, const char** tagblock, const char** nmea) } return tagblock_len; - - -// -// if (message[0] == *AIVDM_START) -// { -// *nmea = message; -// *tagblock = EMPTY_STRING; -// } -// else if (message[0] == *TAGBLOCK_SEPARATOR && message[1] != *AIVDM_START) -// { -// if (message[1] == *AIVDM_START) -// { -// *nmea = message; -// *tagblock = EMPTY_STRING; -// } -// else -// { -// for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); -// if (*ptr) -// { -// *ptr = '\0'; -// *tagblock = &message[1]; -// *nmea = ptr + 1; -// return ptr - message - 1; -// } -// } -// } -// else -// { -// *tagblock_str = message; -// *nmea = EMPTY_STRING; -// } -// -// if (message[0] != *AIVDM_START) -// *nmea = message; -// *tagblock = EMPTY_STRING; -// return 0; } int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 1c02ce8..16c9098 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -178,15 +178,15 @@ def decode_tagblock(tagblock_str, validate_checksum=False): def update_tagblock(nmea, **kwargs): - tagblock_str, nmea = split_tagblock(nmea) - - tagblock_str = _tagblock.update(tagblock_str, kwargs) - # tagblock = decode_tagblock(tagblock_str) - # tagblock.update(kwargs) - # tagblock_str = encode_tagblock(**tagblock) - - return join_tagblock(tagblock_str, nmea) - + # tagblock_str, nmea = split_tagblock(nmea) + # + # tagblock_str = _tagblock.update(tagblock_str, kwargs) + # # tagblock = decode_tagblock(tagblock_str) + # # tagblock.update(kwargs) + # # tagblock_str = encode_tagblock(**tagblock) + # + # return join_tagblock(tagblock_str, nmea) + return _tagblock.update(nmea, kwargs) def safe_update_tagblock(nmea, **kwargs): try: From 2cb678e0ff361a7f33deea54d4a8aee858deed7f Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Sat, 11 Feb 2023 20:08:33 -0500 Subject: [PATCH 06/16] add local implemention of non-standard lib func strlcpy --- ais_tools/{checksum.h => ais-tools.h} | 2 + ais_tools/checksum.c | 18 ++------- ais_tools/strlcpy.c | 55 +++++++++++++++++++++++++++ ais_tools/tagblock.c | 22 +++++------ setup.py | 4 +- 5 files changed, 73 insertions(+), 28 deletions(-) rename ais_tools/{checksum.h => ais-tools.h} (70%) create mode 100644 ais_tools/strlcpy.c diff --git a/ais_tools/checksum.h b/ais_tools/ais-tools.h similarity index 70% rename from ais_tools/checksum.h rename to ais_tools/ais-tools.h index 675094a..b49d3f1 100644 --- a/ais_tools/checksum.h +++ b/ais_tools/ais-tools.h @@ -4,3 +4,5 @@ #define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) char* _checksum_str(const char * s, char* checksum); + +size_t zzz_strlcpy(char * dst, const char * src, size_t dsize); diff --git a/ais_tools/checksum.c b/ais_tools/checksum.c index 78948fa..7aeecf0 100644 --- a/ais_tools/checksum.c +++ b/ais_tools/checksum.c @@ -4,7 +4,7 @@ #include #include #include -#include "checksum.h" +#include "ais-tools.h" int _checksum(const char *s) { @@ -25,12 +25,10 @@ bool _is_checksum_valid(char* s) { const char * skip_chars = "!?\\"; const char separator = '*'; -// const char * separator = "*"; char* body = s; char* checksum = NULL; char computed_checksum[3]; -// char* lasts = NULL; if (*body && strchr(skip_chars, body[0])) body++; @@ -43,17 +41,7 @@ bool _is_checksum_valid(char* s) *ptr++ = '\0'; checksum = ptr; -// if (*body == *separator) -// { -// // special case with zero-length body -// *body = '\0'; -// checksum = body + 1; -// } -// else -// { -// body = strtok_r(body, separator, &lasts); -// checksum = strtok_r(NULL, separator, &lasts); -// } + if (checksum == NULL || strlen(checksum) != 2) return false; @@ -94,7 +82,7 @@ checksum_is_checksum_valid(PyObject *module, PyObject *args) if (!PyArg_ParseTuple(args, "s", &str)) return NULL; - if (strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + if (zzz_strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) { PyErr_SetString(PyExc_ValueError, "String too long"); return NULL; diff --git a/ais_tools/strlcpy.c b/ais_tools/strlcpy.c new file mode 100644 index 0000000..e196bbf --- /dev/null +++ b/ais_tools/strlcpy.c @@ -0,0 +1,55 @@ +// Original file: https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c + +/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +//__FBSDID("$FreeBSD$"); + +#include +//#include + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t +zzz_strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} \ No newline at end of file diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c index efe8e15..e27613e 100644 --- a/ais_tools/tagblock.c +++ b/ais_tools/tagblock.c @@ -5,7 +5,7 @@ #include #include #include -#include "checksum.h" +#include "ais-tools.h" #define SUCCESS 0 @@ -87,9 +87,9 @@ void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_ke size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) - strlcpy(buffer, &long_key[prefix_len], buf_size); + zzz_strlcpy(buffer, &long_key[prefix_len], buf_size); else - strlcpy(buffer, long_key, buf_size); + zzz_strlcpy(buffer, long_key, buf_size); } void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) @@ -223,7 +223,7 @@ int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fiel // if we don't have both key and value, then fail if (key && value) { - strlcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); + zzz_strlcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); fields[idx].value = value; idx++; } @@ -309,7 +309,7 @@ int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) long values[ARRAY_LENGTH(group_field_keys)]; char buffer[MAX_VALUE_LEN]; - if (strlcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + if (zzz_strlcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) return FAIL; char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); @@ -375,7 +375,7 @@ tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) param = PyUnicode_AsUTF8(PyObject_Str(args[0])); - if (strlcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) + if (zzz_strlcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) { PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); return NULL; @@ -450,7 +450,7 @@ int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fiel const char* short_key = lookup_short_key (key_str); if (short_key) - strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); + zzz_strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); else extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); @@ -466,7 +466,7 @@ int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fiel if (field_idx >= max_fields) return FAIL; // no more room to add another field - strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); + zzz_strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); fields[field_idx].value = buffer; field_idx++; } @@ -553,14 +553,14 @@ tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) str = PyUnicode_AsUTF8(PyObject_Str(args[0])); dict = args[1]; - if (strlcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) + if (zzz_strlcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); split_tagblock(message, &tagblock_str, &nmea_str); char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; - if (strlcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) + if (zzz_strlcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); @@ -609,7 +609,7 @@ tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); str = PyUnicode_AsUTF8(PyObject_Str(args[0])); - if (strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + if (zzz_strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); split_tagblock(buffer, &tagblock_str, &nmea_str); diff --git a/setup.py b/setup.py index 8a6a952..c442f47 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ "ais_tools.checksum", extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, - sources=["ais_tools/checksum.c"], + sources=["ais_tools/checksum.c", "ais_tools/strlcpy.c"], include_dirs=["ais_tools/"], undef_macros=undef_macros, ), @@ -78,7 +78,7 @@ "ais_tools._tagblock", extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, - sources=["ais_tools/tagblock.c"], + sources=["ais_tools/tagblock.c", "ais_tools/strlcpy.c"], include_dirs=["ais_tools/"], undef_macros=undef_macros, ) From 7692a01411faa0f2719443b66e4699162404f720 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Sat, 11 Feb 2023 20:15:23 -0500 Subject: [PATCH 07/16] missing dependency in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c442f47..c66e306 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ "ais_tools._tagblock", extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, - sources=["ais_tools/tagblock.c", "ais_tools/strlcpy.c"], + sources=["ais_tools/tagblock.c", "ais_tools/checksum.c", "ais_tools/strlcpy.c"], include_dirs=["ais_tools/"], undef_macros=undef_macros, ) From d4e4e3923f1adc9a5e6cc6d413c0b8b5526f26f5 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Sat, 11 Feb 2023 20:22:22 -0500 Subject: [PATCH 08/16] remove commented code --- ais_tools/tagblock.py | 76 ------------------------------------------- 1 file changed, 76 deletions(-) diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 16c9098..e2eda54 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -6,10 +6,6 @@ from ais_tools.checksum import is_checksum_valid from ais_tools import _tagblock -# import warnings -# with warnings.catch_warnings(): -# warnings.simplefilter("ignore") -# from ais.stream import parseTagBlock # noqa: F401 TAGBLOCK_T_FORMAT = '%Y-%m-%d %H.%M.%S' @@ -59,12 +55,6 @@ def split_tagblock(nmea): """ return _tagblock.split(nmea) - # tagblock = '' - # if nmea.startswith("\\") and not nmea.startswith("\\!"): - # parts = nmea[1:].split("\\", 1) - # if len(parts) == 2: - # tagblock, nmea = parts - # return tagblock, nmea def join_tagblock(tagblock, nmea): @@ -74,10 +64,6 @@ def join_tagblock(tagblock, nmea): return _tagblock.join(tagblock, nmea) - # if tagblock and nmea: - # return "\\{}\\{}".format(tagblock.lstrip('\\'), nmea.lstrip('\\')) - # else: - # return "{}{}".format(tagblock, nmea) def add_tagblock(tagblock, nmea, overwrite=True): @@ -112,80 +98,18 @@ def encode_tagblock(**kwargs): return _tagblock.encode(kwargs) except: raise DecodeError('unable to encode tagblock') - # print(kwargs) - # print(t1) - - # group_fields = {} - # fields = {} - # - # for k, v in kwargs.items(): - # if k in tagblock_group_fields: - # group_fields[k] = str(v) - # elif k in tagblock_fields_reversed: - # fields[tagblock_fields_reversed[k]] = v - # else: - # fields[k.replace('tagblock_', '')] = v - # - # if len(group_fields) == 3: - # fields['g'] = '-'.join([group_fields[k] for k in tagblock_group_fields]) - # - # base_str = ','.join(["{}:{}".format(k, v) for k, v in fields.items()]) - # t2 = '{}*{}'.format(base_str, checksumstr(base_str)) def decode_tagblock(tagblock_str, validate_checksum=False): if validate_checksum and not is_checksum_valid(tagblock_str): raise DecodeError('Invalid checksum') - try: return _tagblock.decode(tagblock_str) except: raise DecodeError('Unable to decode tagblock') - # tagblock = tagblock_str.rsplit("*", 1)[0] - # - # fields = {} - # - # if not tagblock: - # return fields - # - # if validate_checksum and not is_checksum_valid(tagblock_str): - # raise DecodeError('Invalid checksum') - # - # for field in tagblock.split(","): - # try: - # key, value = field.split(":") - # - # if key == 'g': - # parts = [int(part) for part in value.split("-") if part] - # if len(parts) != 3: - # raise DecodeError('Unable to decode tagblock group') - # fields.update(dict(zip(tagblock_group_fields, parts))) - # else: - # if key in ['n', 'r']: - # value = int(value) - # elif key == 'c': - # value = int(value) - # if value > 40000000000: - # value = value / 1000.0 - # - # fields[tagblock_fields.get(key, key)] = value - # except ValueError: - # raise DecodeError('Unable to decode tagblock string') - # - # return fields - - def update_tagblock(nmea, **kwargs): - # tagblock_str, nmea = split_tagblock(nmea) - # - # tagblock_str = _tagblock.update(tagblock_str, kwargs) - # # tagblock = decode_tagblock(tagblock_str) - # # tagblock.update(kwargs) - # # tagblock_str = encode_tagblock(**tagblock) - # - # return join_tagblock(tagblock_str, nmea) return _tagblock.update(nmea, kwargs) def safe_update_tagblock(nmea, **kwargs): From 0d770eb243370f59857f1d8b23f177ad1382b5ed Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Wed, 15 Feb 2023 11:56:25 -0500 Subject: [PATCH 09/16] refactor all the tagblock stuff --- ais_tools/ais-tools.h | 8 - ais_tools/aivdm.py | 4 +- ais_tools/checksum.c | 117 ------ ais_tools/core/checksum.c | 156 +++++++ ais_tools/core/core.h | 54 +++ ais_tools/core/join_tagblock.c | 41 ++ ais_tools/core/methods.c | 136 +++++++ ais_tools/core/module.c | 37 ++ ais_tools/core/split_tagblock.c | 52 +++ ais_tools/core/strcpy.c | 50 +++ ais_tools/core/tagblock.c | 669 +++++++++++++++++++++++++++++++ ais_tools/core/tagblock.h | 41 ++ ais_tools/core/tagblock_decode.c | 150 +++++++ ais_tools/core/tagblock_encode.c | 95 +++++ ais_tools/core/tagblock_fields.c | 169 ++++++++ ais_tools/nmea.py | 2 +- ais_tools/strlcpy.c | 55 --- ais_tools/tagblock.c | 665 ------------------------------ ais_tools/tagblock.py | 41 +- setup.py | 21 +- tests/test_checksum.py | 8 +- tests/test_tagblock.py | 8 +- 22 files changed, 1694 insertions(+), 885 deletions(-) delete mode 100644 ais_tools/ais-tools.h delete mode 100644 ais_tools/checksum.c create mode 100644 ais_tools/core/checksum.c create mode 100644 ais_tools/core/core.h create mode 100644 ais_tools/core/join_tagblock.c create mode 100644 ais_tools/core/methods.c create mode 100644 ais_tools/core/module.c create mode 100644 ais_tools/core/split_tagblock.c create mode 100644 ais_tools/core/strcpy.c create mode 100644 ais_tools/core/tagblock.c create mode 100644 ais_tools/core/tagblock.h create mode 100644 ais_tools/core/tagblock_decode.c create mode 100644 ais_tools/core/tagblock_encode.c create mode 100644 ais_tools/core/tagblock_fields.c delete mode 100644 ais_tools/strlcpy.c delete mode 100644 ais_tools/tagblock.c diff --git a/ais_tools/ais-tools.h b/ais_tools/ais-tools.h deleted file mode 100644 index b49d3f1..0000000 --- a/ais_tools/ais-tools.h +++ /dev/null @@ -1,8 +0,0 @@ - -#define MAX_SENTENCE_LENGTH 1024 - -#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) - -char* _checksum_str(const char * s, char* checksum); - -size_t zzz_strlcpy(char * dst, const char * src, size_t dsize); diff --git a/ais_tools/aivdm.py b/ais_tools/aivdm.py index 62b2346..169131a 100644 --- a/ais_tools/aivdm.py +++ b/ais_tools/aivdm.py @@ -8,7 +8,7 @@ from ais_tools.ais import AISMessageTranscoder from ais_tools.nmea import split_multipart from ais_tools.nmea import expand_nmea -from ais_tools.checksum import checksumstr +from ais_tools.core import checksum_str from ais_tools.message import Message @@ -166,7 +166,7 @@ def safe_encode(self, message): def encode(self, message): body, pad = self.encode_payload(message) sentence = "AIVDM,1,1,,A,{},{}".format(body, pad) - return Message("!{}*{}".format(sentence, checksumstr(sentence))) + return Message("!{}*{}".format(sentence, checksum_str(sentence))) def encode_payload(self, message): return self.encoder.encode_payload(message) diff --git a/ais_tools/checksum.c b/ais_tools/checksum.c deleted file mode 100644 index 7aeecf0..0000000 --- a/ais_tools/checksum.c +++ /dev/null @@ -1,117 +0,0 @@ -// checksum module - -#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ -#include -#include -#include -#include "ais-tools.h" - -int _checksum(const char *s) -{ - int c = 0; - while (*s) - c = c ^ *s++; - return c; -} - -char* _checksum_str(const char * s, char* checksum) -{ - int c = _checksum(s); - sprintf(checksum, "%02X", c); - return checksum; -} - -bool _is_checksum_valid(char* s) -{ - const char * skip_chars = "!?\\"; - const char separator = '*'; - - char* body = s; - char* checksum = NULL; - char computed_checksum[3]; - - if (*body && strchr(skip_chars, body[0])) - body++; - - char* ptr = body; - while (*ptr != '\0' && *ptr != separator) - ptr++; - - if (*ptr == '*') - *ptr++ = '\0'; - checksum = ptr; - - - if (checksum == NULL || strlen(checksum) != 2) - return false; - - _checksum_str(body, computed_checksum); - return strcasecmp(checksum, computed_checksum) == 0; -} - -static PyObject * -checksum_compute_checksum(PyObject *module, PyObject *args) -{ - const char *str; - int c; - - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - c = _checksum(str); - return PyLong_FromLong(c); -} - -static PyObject * -checksum_compute_checksumstr(PyObject *module, PyObject *args) -{ - const char *str; - char c_str[3]; - - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - _checksum_str(str, c_str); - return PyUnicode_FromString(c_str); -} - -static PyObject * -checksum_is_checksum_valid(PyObject *module, PyObject *args) -{ - char *str; - char buffer[MAX_SENTENCE_LENGTH]; - - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - - if (zzz_strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) - { - PyErr_SetString(PyExc_ValueError, "String too long"); - return NULL; - } - - return _is_checksum_valid(buffer) ? Py_True: Py_False; -} - - -static PyMethodDef checksum_methods[] = { - {"checksum", (PyCFunction)(void(*)(void))checksum_compute_checksum, METH_VARARGS, - "Compute checksum of a string. returns an integer value"}, - {"checksumstr", (PyCFunction)(void(*)(void))checksum_compute_checksumstr, METH_VARARGS, - "Compute checksum of a string. returns a 2-character hex string"}, - {"is_checksum_valid", (PyCFunction)(void(*)(void))checksum_is_checksum_valid, METH_VARARGS, - "Returns True if the given string is terminated with a valid checksum, else False"}, - {NULL, NULL, 0, NULL} /* sentinel */ -}; - -static struct PyModuleDef checksum_module = { - PyModuleDef_HEAD_INIT, - "checksum", - NULL, - -1, - checksum_methods -}; - -PyMODINIT_FUNC -PyInit_checksum(void) -{ - return PyModule_Create(&checksum_module); -} \ No newline at end of file diff --git a/ais_tools/core/checksum.c b/ais_tools/core/checksum.c new file mode 100644 index 0000000..632e174 --- /dev/null +++ b/ais_tools/core/checksum.c @@ -0,0 +1,156 @@ +// checksum module + +#include +#include +#include +#include "core.h" + +/* + * Compute the checksum value of a string. This is + * computed by xor-ing the integer value of each character in the string + * sequentially. + */ +int checksum(const char *s) +{ + int c = 0; + while (*s) + c = c ^ *s++; + return c; +} + +/* + * Get the checksum value of a string as a 2-character hex string + * This is always uppercase. The destination buffer must be at least 3 chars + * (one for the terminating null character) + * + * Returns a pointer to the destination buffer + * Returns null if the dest buffer is too small + */ +char* checksum_str(char * __restrict dst, const char* __restrict src, size_t dsize) +{ + if (dsize < 3) + return NULL; + + int c = checksum(src); + sprintf(dst, "%02X", c); + return dst; +} +// +//char* checksum_str(const char * s, char* c_str) +//{ +// int c = checksum(s); +// sprintf(c_str, "%02X", c); +// return c_str; +//} + +/* + * Compute the checksum value of the given string and compare it to the checksum + * that appears at the end of the string. + * the checksum should be a 2 character hex value at the end separated by a '*' + * + * For example: + * c:1000,s:old*5A + * + * If the string starts with any of these characrters ?!\ then the first character is ignored + * for purposes of computing the checksum + * + * If no checksum is found at at the end of the string then the return is false + * + * Returns true if the checksum at the end of the string matches the computed checksum, else false + * + * NOTE: The given string will be modified to separate it into the body portion and the checksum portion + */ +bool is_checksum_valid(char* s) +{ + const char * skip_chars = "!?\\"; + const char separator = '*'; + + char* body = s; + char* c_str = NULL; + char computed_checksum[3]; + + if (*body && strchr(skip_chars, body[0])) + body++; + + char* ptr = body; + while (*ptr != '\0' && *ptr != separator) + ptr++; + + if (*ptr == '*') + *ptr++ = '\0'; + c_str = ptr; + + if (c_str == NULL || strlen(c_str) != 2) + return false; + + checksum_str(computed_checksum, body, ARRAY_LENGTH(computed_checksum)); + return strcasecmp(c_str, computed_checksum) == 0; +} + +// +//PyObject * +//core_compute_checksum(PyObject *module, PyObject *args) +//{ +// const char *str; +// int c; +// +// if (!PyArg_ParseTuple(args, "s", &str)) +// return NULL; +// c = checksum(str); +// return PyLong_FromLong(c); +//} +// +//PyObject * +//core_compute_checksum_str(PyObject *module, PyObject *args) +//{ +// const char *str; +// char c_str[3]; +// +// if (!PyArg_ParseTuple(args, "s", &str)) +// return NULL; +// checksum_str(c_str, str, ARRAY_LENGTH(c_str)); +// return PyUnicode_FromString(c_str); +//} +// +//PyObject * +//core_is_checksum_valid(PyObject *module, PyObject *args) +//{ +// char *str; +// char buffer[MAX_SENTENCE_LENGTH]; +// +// if (!PyArg_ParseTuple(args, "s", &str)) +// return NULL; +// +// if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) +// { +// PyErr_SetString(PyExc_ValueError, "String too long"); +// return NULL; +// } +// +// return is_checksum_valid(buffer) ? Py_True: Py_False; +//} + +// +//static PyMethodDef checksum_methods[] = { +// {"checksum", (PyCFunction)(void(*)(void))checksum_compute_checksum, METH_VARARGS, +// "Compute checksum of a string. returns an integer value"}, +// {"checksumstr", (PyCFunction)(void(*)(void))checksum_compute_checksumstr, METH_VARARGS, +// "Compute checksum of a string. returns a 2-character hex string"}, +// {"is_checksum_valid", (PyCFunction)(void(*)(void))checksum_is_checksum_valid, METH_VARARGS, +// "Returns True if the given string is terminated with a valid checksum, else False"}, +// {NULL, NULL, 0, NULL} /* sentinel */ +//}; +// +//static struct PyModuleDef checksum_module = { +// PyModuleDef_HEAD_INIT, +// "checksum", +// NULL, +// -1, +// checksum_methods +//}; +// +//PyMODINIT_FUNC +//PyInit_checksum(void) +//{ +// return PyModule_Create(&checksum_module); +//} \ No newline at end of file diff --git a/ais_tools/core/core.h b/ais_tools/core/core.h new file mode 100644 index 0000000..4304dc7 --- /dev/null +++ b/ais_tools/core/core.h @@ -0,0 +1,54 @@ +/* AIS Tools core functions implemented in C */ + +#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ + +#define SUCCESS 0 +#define FAIL -1 +#define FAIL_STRING_TOO_LONG -101 +#define FAIL_TOO_MANY_FIELDS -102 + + +#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0])) + +#define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" +#define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" +#define ERR_TOO_MANY_FIELDS "Too many fields" +#define ERR_NMEA_TOO_LONG "NMEA string too long" + +#define MAX_TAGBLOCK_FIELDS 8 // max number of fields allowed in a tagblock +#define MAX_TAGBLOCK_STR_LEN 1024 // max length of a tagblock string +#define MAX_KEY_LEN 32 // max length of a single key in a tagblock +#define MAX_VALUE_LEN 256 // max length of a single value in a tagblock +#define MAX_SENTENCE_LENGTH 1024 // max length of a single nmea sentence (tagblock + AIVDM) + +#define TAGBLOCK_SEPARATOR "\\" +#define CHECKSUM_SEPARATOR "*" +#define FIELD_SEPARATOR "," +#define KEY_VALUE_SEPARATOR ":" +#define GROUP_SEPARATOR "-" +#define AIVDM_START "!" +#define EMPTY_STRING "" + + +// string copy utils +char * unsafe_strcpy(char * dest, const char * dest_end, const char * src); +size_t safe_strcpy(char * __restrict dst, const char * __restrict src, size_t dsize); + +// checksum functions +int checksum(const char *s); +char* checksum_str(char * dst, const char* src, size_t dsize); +bool is_checksum_valid(char* s); + +// tagblock functions +int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str); +int split_tagblock(char* message, const char** tagblock, const char** nmea); + + +// Module methods +PyObject * method_compute_checksum(PyObject *module, PyObject *args); +PyObject * method_compute_checksum_str(PyObject *module, PyObject *args); +PyObject * method_is_checksum_valid(PyObject *module, PyObject *args); +PyObject * method_join_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_split_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_decode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); diff --git a/ais_tools/core/join_tagblock.c b/ais_tools/core/join_tagblock.c new file mode 100644 index 0000000..a303f8c --- /dev/null +++ b/ais_tools/core/join_tagblock.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include "core.h" + + +int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) +{ + char* end = buffer + buf_size - 1; + char* ptr = buffer; + + if (*tagblock_str && *nmea_str) + { + if (*tagblock_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, tagblock_str); + + if (*nmea_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, nmea_str); + } + else + { + ptr = unsafe_strcpy(ptr, end, tagblock_str); + ptr = unsafe_strcpy(ptr, end, nmea_str); + } + + if (ptr <= end) + { + *ptr = '\0'; + return ptr - buffer; + } + else + { + *end = '\0'; + return FAIL; + } + + return SUCCESS; +} diff --git a/ais_tools/core/methods.c b/ais_tools/core/methods.c new file mode 100644 index 0000000..66b1708 --- /dev/null +++ b/ais_tools/core/methods.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include "core.h" +#include "tagblock.h" + +PyObject * +method_compute_checksum(PyObject *module, PyObject *args) +{ + const char *str; + int c; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + c = checksum(str); + return PyLong_FromLong(c); +} + +PyObject * +method_compute_checksum_str(PyObject *module, PyObject *args) +{ + const char *str; + char c_str[3]; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + checksum_str(c_str, str, ARRAY_LENGTH(c_str)); + return PyUnicode_FromString(c_str); +} + +PyObject * +method_is_checksum_valid(PyObject *module, PyObject *args) +{ + char *str; + char buffer[MAX_SENTENCE_LENGTH]; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + { + PyErr_SetString(PyExc_ValueError, "String too long"); + return NULL; + } + + return is_checksum_valid(buffer) ? Py_True: Py_False; +} + +PyObject * +method_join_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + char buffer[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; + + if (nargs != 2) + return PyErr_Format(PyExc_TypeError, "join expects 2 arguments"); + + tagblock_str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + nmea_str = PyUnicode_AsUTF8(PyObject_Str(args[1])); + + if (FAIL == join_tagblock(buffer, ARRAY_LENGTH(buffer), tagblock_str, nmea_str)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + return PyUnicode_FromString(buffer); +} + +PyObject * +method_split_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char* str; + char buffer[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; + + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + split_tagblock(buffer, &tagblock_str, &nmea_str); + + return PyTuple_Pack(2, PyUnicode_FromString(tagblock_str), PyUnicode_FromString(nmea_str)); +} + +PyObject * +method_decode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char *str; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; + + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "decode expects only 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + + if (safe_strcpy(tagblock_str, str, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + + PyObject* dict = decode_tagblock(tagblock_str); + + if (!dict) + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); + + return dict; +} + + +PyObject * +method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + + PyObject *dict; + int result; + char tagblock_str[MAX_TAGBLOCK_STR_LEN]; + + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "encode expects only 1 argument"); + + dict = args[0]; + + result = encode_tagblock(tagblock_str, dict, ARRAY_LENGTH(tagblock_str)); + + if (result < 0) + switch(result) + { + case FAIL_TOO_MANY_FIELDS: + return PyErr_Format(PyExc_ValueError, "encode failed: too many fields"); + case FAIL_STRING_TOO_LONG: + return PyErr_Format(PyExc_ValueError, "encode failed: encoded string is too long"); + } + + return PyUnicode_FromString(tagblock_str); +} \ No newline at end of file diff --git a/ais_tools/core/module.c b/ais_tools/core/module.c new file mode 100644 index 0000000..af0a2cb --- /dev/null +++ b/ais_tools/core/module.c @@ -0,0 +1,37 @@ +#include +#include +#include "core.h" + +static PyMethodDef core_methods[] = { + {"checksum", (PyCFunction)(void(*)(void))method_compute_checksum, METH_VARARGS, + "Compute checksum of a string. returns an integer value"}, + {"checksum_str", (PyCFunction)(void(*)(void))method_compute_checksum_str, METH_VARARGS, + "Compute checksum of a string. returns a 2-character hex string"}, + {"is_checksum_valid", (PyCFunction)(void(*)(void))method_is_checksum_valid, METH_VARARGS, + "Returns True if the given string is terminated with a valid checksum, else False"}, + {"decode_tagblock", (PyCFunction)(void(*)(void))method_decode_tagblock, METH_FASTCALL, + "decode a tagblock string. Returns a dict"}, + {"encode_tagblock", (PyCFunction)(void(*)(void))method_encode_tagblock, METH_FASTCALL, + "encode a tagblock string from a dict. Returns a string"}, +// {"update_tagblock", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, +// "update a tagblock string from a dict. Returns a string"}, + {"split_tagblock", (PyCFunction)(void(*)(void))method_split_tagblock, METH_FASTCALL, + "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, + {"join_tagblock", (PyCFunction)(void(*)(void))method_join_tagblock, METH_FASTCALL, + "Join a tagblock to an AIVDM message. Returns a string."}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef core_module = { + PyModuleDef_HEAD_INIT, + "core", + NULL, + -1, + core_methods +}; + +PyMODINIT_FUNC +PyInit_core(void) +{ + return PyModule_Create(&core_module); +} \ No newline at end of file diff --git a/ais_tools/core/split_tagblock.c b/ais_tools/core/split_tagblock.c new file mode 100644 index 0000000..ffda255 --- /dev/null +++ b/ais_tools/core/split_tagblock.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include "core.h" + +/* + * Split a sentence into the tagblock part and the nmea part + * + * This method modifies the given message to write a null character to terminate + * the tagblock part + * + * returns the length of the tagblock part + * + * Four cases for the given message string + * + * starts with '!' - no tagblock, entire message in nmea + * starts with '\!' - no tagblock, strip off '\',entire message in nmea + * starts with '\[^!]' - is a tagblock , strip off leading '\', nmea is whatever comes after the next '\'. + * starts with '[^\!]' - is a tagblock, nmea is whatever comes after the next '\'. + * starts with '[^!]' and there are no `\` delimiters - tagblock is empty, entire message in nmea + */ +int split_tagblock(char* message, const char** tagblock, const char** nmea) +{ + + char* ptr; + int tagblock_len = 0; + + ptr = message; + if (*ptr == *TAGBLOCK_SEPARATOR) + ptr ++; + if (*ptr == *AIVDM_START) + { + *nmea = ptr; + *tagblock = EMPTY_STRING; + } + else + { + *tagblock = ptr; + for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); + tagblock_len = ptr - *tagblock; + if (*ptr) + { + *ptr = '\0'; + *nmea = ptr + 1; + } + else + *nmea = EMPTY_STRING; + } + + return tagblock_len; +} \ No newline at end of file diff --git a/ais_tools/core/strcpy.c b/ais_tools/core/strcpy.c new file mode 100644 index 0000000..c0ba88e --- /dev/null +++ b/ais_tools/core/strcpy.c @@ -0,0 +1,50 @@ +#include +#include + +/* + * Copy a source str to a dest str + * + * Does not write a null terminator + * dest_end should point to the last position in the dest string buffer. This method will + * stops at the character immediately before this position + * + * returns a pointer the the position immediately after the last position written in dest + * you can use the return pointer for a subsequent copy, or if you are finished, write a + * null char to that position to terminate the string + */ +char * unsafe_strcpy(char * dest, const char * dest_end, const char * src) +{ + while (dest < dest_end && *src) + *dest++ = *src++; + return dest; +} + + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t safe_strcpy(char * __restrict dst, const char * __restrict src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} \ No newline at end of file diff --git a/ais_tools/core/tagblock.c b/ais_tools/core/tagblock.c new file mode 100644 index 0000000..5ade74b --- /dev/null +++ b/ais_tools/core/tagblock.c @@ -0,0 +1,669 @@ +// tagblock module + +#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ +#include +#include +#include +#include +#include "core.h" +#include "tagblock.h" + +//#define SUCCESS 0 +//#define FAIL -1 +// +//const size_t MAX_TAGBLOCK_FIELDS = 8; +//const size_t MAX_TAGBLOCK_STR_LEN = 1024; +//const size_t MAX_KEY_LEN = 32; +//const size_t MAX_VALUE_LEN = 256; +//const char * CHECKSUM_SEPARATOR = "*"; +//const char * FIELD_SEPARATOR = ","; +//const char * KEY_VALUE_SEPARATOR = ":"; +//const char * GROUP_SEPARATOR = "-"; +//const char * TAGBLOCK_SEPARATOR = "\\"; +//const char * AIVDM_START = "!"; +//const char * EMPTY_STRING = ""; + +//struct TAGBLOCK_FIELD +//{ +// char key[2]; +// const char* value; +//}; + +//#define TAGBLOCK_TIMESTAMP "tagblock_timestamp" +//#define TAGBLOCK_DESTINATION "tagblock_destination" +//#define TAGBLOCK_LINE_COUNT "tagblock_line_count" +//#define TAGBLOCK_RELATIVE_TIME "tagblock_relative_time" +//#define TAGBLOCK_STATION "tagblock_station" +//#define TAGBLOCK_TEXT "tagblock_text" +//#define TAGBLOCK_SENTENCE "tagblock_sentence" +//#define TAGBLOCK_GROUPSIZE "tagblock_groupsize" +//#define TAGBLOCK_ID "tagblock_id" +//#define CUSTOM_FIELD_PREFIX "tagblock_" +//#define TAGBLOCK_GROUP "g" + +//#define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" +//#define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" +//#define ERR_TOO_MANY_FIELDS "Too many fields" +//#define ERR_NMEA_TOO_LONG "NMEA string too long" +// +//typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; +//static KEY_MAP key_map[] = { +// {"c", TAGBLOCK_TIMESTAMP}, +// {"d", TAGBLOCK_DESTINATION}, +// {"n", TAGBLOCK_LINE_COUNT}, +// {"r", TAGBLOCK_RELATIVE_TIME}, +// {"s", TAGBLOCK_STATION}, +// {"t", TAGBLOCK_TEXT} +//}; +// +//static const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; +// +//const char* lookup_long_key(const char *short_key) +//{ +// for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) +// if (0 == strcmp(key_map[i].short_key, short_key)) +// return key_map[i].long_key; +// return NULL; +//} +// +//const char* lookup_short_key(const char* long_key) +//{ +// for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) +// if (0 == strcmp(key_map[i].long_key, long_key)) +// return key_map[i].short_key; +// return NULL; +//} +// +//size_t lookup_group_field_key(const char* long_key) +//{ +// for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) +// if (0 == strcmp(long_key, group_field_keys[i])) +// return i + 1; +// return 0; +//} +// +//void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) +//{ +// size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; +// +// if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) +// safe_strcpy(buffer, &long_key[prefix_len], buf_size); +// else +// safe_strcpy(buffer, long_key, buf_size); +//} +// +//void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) +//{ +// for (size_t i = 0; i < num_fields; i++) +// { +// fields[i].key[0] = '\0'; +// fields[i].value = NULL; +// } +//} + +///* +// * Copy a source str to a dest str +// * +// * Does not write a null terminator +// * dest_end should point to the last position in the dest string buffer. This method will +// * stops at the character immediately before this position +// * +// * returns a pointer the the position immediately after the last position written in dest +// */ +//char * unsafe_strcpy(char * dest, const char * dest_end, const char * src) +//{ +// while (dest < dest_end && *src) +// *dest++ = *src++; +// return dest; +//} + +int split_tagblock(char* message, const char** tagblock, const char** nmea) +{ + // Four cases for the given message string + + // starts with '!' - no tagblock, message is all nmea + // starts with '\!' - no tagblock, strip off '\', message is all nmea + // starts with '\[^!]' - is a tagblock , strip off leading '\', nmea is whatever comes after the next '\'. + // start with '[^\!]' - is a tagblock, nmea is whatever comes after the next '\'. + // starts with '[^!]' and there are no `\` delimiters - tagblock is empty, entire string in nmea + + char* ptr; + int tagblock_len = 0; + + ptr = message; + if (*ptr == *TAGBLOCK_SEPARATOR) + ptr ++; + if (*ptr == *AIVDM_START) + { + *nmea = ptr; + *tagblock = EMPTY_STRING; + } + else + { + *tagblock = ptr; + for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); + tagblock_len = ptr - *tagblock; + if (*ptr) + { + *ptr = '\0'; + *nmea = ptr + 1; + } + else + *nmea = EMPTY_STRING; + } + + return tagblock_len; +} + +int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) +{ + char* end = buffer + buf_size - 1; + char* ptr = buffer; + + if (*tagblock_str && *nmea_str) + { + if (*tagblock_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, tagblock_str); + + if (*nmea_str != *TAGBLOCK_SEPARATOR) + ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, nmea_str); + } + else + { + ptr = unsafe_strcpy(ptr, end, tagblock_str); + ptr = unsafe_strcpy(ptr, end, nmea_str); + } + + if (ptr <= end) + { + *ptr = '\0'; + return ptr - buffer; + } + else + { + *end = '\0'; + return FAIL; + } + + return SUCCESS; +} + +// split a tagblock string with structure +// k:value,k:value +// k:value,k:value*cc +// \\k:value,k:value*cc\\other_stuff + +//int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) +//{ +// int idx = 0; +// char * ptr; +// char * field_save_ptr = NULL; +// char * field; +// +// char * key_value_save_ptr = NULL; +// +// // skip leading tagblock delimiter +// if (*tagblock_str == *TAGBLOCK_SEPARATOR) +// tagblock_str++; +// +// // seek forward to find either the checksum, the next tagblock separator, or the end of the string. +// // Terminate the string at the first delimiter found +// for (ptr = tagblock_str; *ptr != *TAGBLOCK_SEPARATOR && *ptr != *CHECKSUM_SEPARATOR && *ptr != '\0'; ptr++); +// *ptr = '\0'; +// +// // get the first comma delimited field +// field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); +// while (field && *field && idx < max_fields) +// { +// // for each field, split into key part and value part +// const char* key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); +// const char* value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); +// +// // if we don't have both key and value, then fail +// if (key && value) +// { +// safe_strcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); +// fields[idx].value = value; +// idx++; +// } +// else +// return FAIL; +// +// // advance to the next field +// field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); +// } +// return idx; +//} + +//// TODO: need a return value that indicates the string is too long +//size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) +//{ +// const char * end = tagblock_str + buf_size - 1; +// size_t last_field_idx = num_fields - 1; +// char * ptr = tagblock_str; +// char checksum[3]; +// +// for (size_t idx = 0; idx < num_fields; idx++) +// { +// ptr = unsafe_strcpy(ptr, end, fields[idx].key); +// ptr = unsafe_strcpy(ptr, end, KEY_VALUE_SEPARATOR); +// ptr = unsafe_strcpy(ptr, end, fields[idx].value); +// if (idx < last_field_idx) +// { +// ptr = unsafe_strcpy(ptr, end, FIELD_SEPARATOR); +// } +// } +// *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string +// +// checksum_str(checksum, tagblock_str, ARRAY_LENGTH(checksum)); +// +// ptr = unsafe_strcpy(ptr, end, CHECKSUM_SEPARATOR); +// ptr = unsafe_strcpy(ptr, end, checksum); +// +// if (ptr == end) +// return FAIL; +// +// *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string +// return ptr - tagblock_str; +//} + +//int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +//{ +// const char* key = lookup_long_key(field->key); +// char * end; +// +// long value = strtol(field->value, &end, 10); +// if (errno || *end) +// return FAIL; +// +// if (value > 40000000000) +// value = value / 1000; +// PyDict_SetItemString(dict, key, PyLong_FromLong(value)); +// return SUCCESS; +//} +// +//int decode_int_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +//{ +// const char* key = lookup_long_key(field->key); +// char* end; +// +// long value = strtol(field->value, &end, 10); +// if (errno || *end) +// return FAIL; +// +// PyDict_SetItemString(dict, key, PyLong_FromLong(value)); +// +// return SUCCESS; +//} +// +//int decode_text_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +//{ +// const char* key = lookup_long_key(field->key); +// PyDict_SetItemString(dict, key, PyUnicode_FromString(field->value)); +// +// return SUCCESS; +//} +// +//int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +//{ +// size_t idx = 0; +// char * save_ptr = NULL; +// long values[ARRAY_LENGTH(group_field_keys)]; +// char buffer[MAX_VALUE_LEN]; +// +// if (safe_strcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) +// return FAIL; +// +// char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); +// while (f && idx < ARRAY_LENGTH(values)) +// { +// char* end; +// values[idx++] = strtol(f, &end, 10); +// if (errno || *end) +// return FAIL; +// +// f = strtok_r(NULL, GROUP_SEPARATOR, &save_ptr); +// } +// if (idx == ARRAY_LENGTH(values)) +// { +// for (idx = 0; idx < ARRAY_LENGTH(values); idx++) +// PyDict_SetItemString(dict, group_field_keys[idx], PyLong_FromLong(values[idx])); +// return SUCCESS; +// } +// +// return FAIL; +//} +// +//int decode_custom_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +//{ +// char custom_key[MAX_KEY_LEN]; +// snprintf(custom_key, ARRAY_LENGTH(custom_key), "%s%s", CUSTOM_FIELD_PREFIX, field->key); +// PyDict_SetItemString(dict, custom_key, PyUnicode_FromString(field->value)); +// +// return SUCCESS; +//} + +//size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* group_fields) +//{ +// const char * end = buffer + buf_size - 1; +// char * ptr = buffer; +// +// for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) +// { +// // make sure that all values are non-empty +// if (group_fields[i].value == NULL) +// return 0; +// +// ptr = unsafe_strcpy(ptr, end, group_fields[i].value); +// if (i < ARRAY_LENGTH(group_field_keys) - 1) +// ptr = unsafe_strcpy(ptr, end, GROUP_SEPARATOR); +// } +// *ptr++ = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string +// +// return ptr - buffer; +//} + +//static PyObject * +//tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// const char *param; +// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; +// int num_fields = 0; +// int status = SUCCESS; +// +// if (nargs != 1) +// return NULL; +// +// param = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// +// if (safe_strcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) +// { +// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); +// return NULL; +// } +// +// PyObject* dict = PyDict_New(); +// +// num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); +// if (num_fields < 0) +// status = FAIL; +// +// for (int i = 0; i < num_fields && status==SUCCESS; i++) +// { +// struct TAGBLOCK_FIELD* field = &fields[i]; +// const char* key = field->key; +// +// switch(key[0]) +// { +// case 'c': +// status = decode_timestamp_field(field, dict); +// break; +// case 'd': +// case 's': +// case 't': +// status = decode_text_field(field, dict); +// break; +// case 'g': +// status = decode_group_field(field, dict); +// break; +// case 'n': +// case 'r': +// status = decode_int_field(field, dict); +// break; +// default: +// status = decode_custom_field(field, dict); +// } +// } +// +// if (status == SUCCESS) +// return dict; +// else +// { +// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); +// return NULL; +// } +//} + +//int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) +//{ +// PyObject *key, *value; +// Py_ssize_t pos = 0; +// size_t field_idx = 0; +// struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; +// +// init_fields (group_fields, ARRAY_LENGTH(group_fields)); +// +// while (PyDict_Next(dict, &pos, &key, &value)) +// { +// if (field_idx == max_fields) +// return FAIL; // no more room in fields +// +// const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); +// const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); +// +// size_t group_ordinal = lookup_group_field_key(key_str); +// if (group_ordinal > 0) +// { +// group_fields[group_ordinal - 1].value = value_str; +// } +// else +// { +// const char* short_key = lookup_short_key (key_str); +// +// if (short_key) +// safe_strcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); +// else +// extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); +// +// fields[field_idx].value = value_str; +// field_idx++; +// } +// } +// +// // encode group field and add it to the field list +// // check the return code to see if there is a complete set of group fields +// if (encode_group_fields(buffer, buf_size, group_fields)) +// { +// if (field_idx >= max_fields) +// return FAIL; // no more room to add another field +// +// safe_strcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); +// fields[field_idx].value = buffer; +// field_idx++; +// } +// +// return field_idx; +//} + + +//int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, +// struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) +//{ +// for (size_t update_idx = 0; update_idx < num_update_fields; update_idx++) +// { +// char* key = update_fields[update_idx].key; +// +// size_t fields_idx = 0; +// while (fields_idx < num_fields) +// { +// if (0 == strcmp(key, fields[fields_idx].key)) +// break; +// fields_idx++; +// } +// if (fields_idx == num_fields) +// { +// if (num_fields < max_fields) +// num_fields++; +// else +// return FAIL; +// } +// +// fields[fields_idx] = update_fields[update_idx]; +// } +// +// return num_fields; +//} + + + +//static PyObject * +//tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// +// PyObject *dict; +// +// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; +// +// init_fields (fields, ARRAY_LENGTH(fields)); +// +// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// char value_buffer [MAX_VALUE_LEN]; +// +// if (nargs != 1) +// return NULL; +// +// dict = args[0]; +// +// int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); +// if (num_fields < 0) +// { +// PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); +// return NULL; +// } +// +// join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); +// +// return PyUnicode_FromString(tagblock_str); +//} + + +static PyObject * +tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char* str; + PyObject* dict; + char message[MAX_SENTENCE_LENGTH]; + char updated_message[MAX_SENTENCE_LENGTH]; + const char* tagblock_str; + const char* nmea_str; + + + if (nargs != 2) + return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + dict = args[1]; + + if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + split_tagblock(message, &tagblock_str, &nmea_str); + + + char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; + if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + + + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + int num_fields = 0; + num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); + if (num_fields < 0) + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); + + + struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; + int num_update_fields = 0; + char value_buffer[MAX_VALUE_LEN]; + num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), + value_buffer, ARRAY_LENGTH(value_buffer)); + if (num_update_fields < 0) + return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + + + num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); + if (num_fields < 0) + return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + + + char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; + join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); + + int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); + if (msg_len < 0) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + + return PyUnicode_FromString(updated_message); + +} + +//static PyObject * +//tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// const char* str; +// char buffer[MAX_SENTENCE_LENGTH]; +// const char* tagblock_str; +// const char* nmea_str; +// +// if (nargs != 1) +// return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); +// +// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); +// +// split_tagblock(buffer, &tagblock_str, &nmea_str); +// +// return PyTuple_Pack(2, PyUnicode_FromString(tagblock_str), PyUnicode_FromString(nmea_str)); +//} + +//PyObject * +//tagblock_join(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// char buffer[MAX_SENTENCE_LENGTH]; +// const char* tagblock_str; +// const char* nmea_str; +// +// if (nargs != 2) +// return PyErr_Format(PyExc_TypeError, "join expects 2 arguments"); +// +// tagblock_str = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// nmea_str = PyUnicode_AsUTF8(PyObject_Str(args[1])); +// +// if (FAIL == join_tagblock(buffer, ARRAY_LENGTH(buffer), tagblock_str, nmea_str)) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); +// +// return PyUnicode_FromString(buffer); +//} + +static PyMethodDef tagblock_methods[] = { +// {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_FASTCALL, +// "decode a tagblock string. Returns a dict"}, +// {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_FASTCALL, +// "encode a tagblock string from a dict. Returns a string"}, + {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, + "update a tagblock string from a dict. Returns a string"}, +// {"split", (PyCFunction)(void(*)(void))tagblock_split, METH_FASTCALL, +// "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, +// {"join", (PyCFunction)(void(*)(void))tagblock_join, METH_FASTCALL, +// "Join a tagblock to an AIVDM message. Returns a string."}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef tagblock_module = { + PyModuleDef_HEAD_INIT, + "_tagblock", + NULL, + -1, + tagblock_methods +}; + +PyMODINIT_FUNC +PyInit__tagblock(void) +{ + return PyModule_Create(&tagblock_module); +} \ No newline at end of file diff --git a/ais_tools/core/tagblock.h b/ais_tools/core/tagblock.h new file mode 100644 index 0000000..066ed22 --- /dev/null +++ b/ais_tools/core/tagblock.h @@ -0,0 +1,41 @@ +/* tagblock definitions */ + + +#define TAGBLOCK_TIMESTAMP "tagblock_timestamp" +#define TAGBLOCK_DESTINATION "tagblock_destination" +#define TAGBLOCK_LINE_COUNT "tagblock_line_count" +#define TAGBLOCK_RELATIVE_TIME "tagblock_relative_time" +#define TAGBLOCK_STATION "tagblock_station" +#define TAGBLOCK_TEXT "tagblock_text" +#define TAGBLOCK_SENTENCE "tagblock_sentence" +#define TAGBLOCK_GROUPSIZE "tagblock_groupsize" +#define TAGBLOCK_ID "tagblock_id" +#define CUSTOM_FIELD_PREFIX "tagblock_" +#define TAGBLOCK_GROUP "g" + + +struct TAGBLOCK_FIELD +{ + char key[2]; + const char* value; +}; + +extern const char* group_field_keys[3]; + +const char* lookup_long_key(const char *short_key); +const char* lookup_short_key(const char* long_key); +size_t lookup_group_field_key(const char* long_key); +void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key); +void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields); + +int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields); +int join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size); +int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, + struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields); + +int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size); + +int encode_tagblock(char * dest, PyObject *dict, size_t dest_buf_size); +PyObject * decode_tagblock(char * tagblock_str); + + diff --git a/ais_tools/core/tagblock_decode.c b/ais_tools/core/tagblock_decode.c new file mode 100644 index 0000000..6fb2490 --- /dev/null +++ b/ais_tools/core/tagblock_decode.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include "core.h" +#include "tagblock.h" + +int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + char * end; + + long value = strtol(field->value, &end, 10); + if (errno || *end) + return FAIL; + + if (value > 40000000000) + value = value / 1000; + PyDict_SetItemString(dict, key, PyLong_FromLong(value)); + return SUCCESS; +} + +int decode_int_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + char* end; + + long value = strtol(field->value, &end, 10); + if (errno || *end) + return FAIL; + + PyDict_SetItemString(dict, key, PyLong_FromLong(value)); + + return SUCCESS; +} + +int decode_text_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + const char* key = lookup_long_key(field->key); + PyDict_SetItemString(dict, key, PyUnicode_FromString(field->value)); + + return SUCCESS; +} + +int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + size_t idx = 0; + char * save_ptr = NULL; + long values[ARRAY_LENGTH(group_field_keys)]; + char buffer[MAX_VALUE_LEN]; + + if (safe_strcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) + return FAIL; + + char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); + while (f && idx < ARRAY_LENGTH(values)) + { + char* end; + values[idx++] = strtol(f, &end, 10); + if (errno || *end) + return FAIL; + + f = strtok_r(NULL, GROUP_SEPARATOR, &save_ptr); + } + if (idx == ARRAY_LENGTH(values)) + { + for (idx = 0; idx < ARRAY_LENGTH(values); idx++) + PyDict_SetItemString(dict, group_field_keys[idx], PyLong_FromLong(values[idx])); + return SUCCESS; + } + + return FAIL; +} + +int decode_custom_field(struct TAGBLOCK_FIELD* field, PyObject* dict) +{ + char custom_key[MAX_KEY_LEN]; + snprintf(custom_key, ARRAY_LENGTH(custom_key), "%s%s", CUSTOM_FIELD_PREFIX, field->key); + PyDict_SetItemString(dict, custom_key, PyUnicode_FromString(field->value)); + + return SUCCESS; +} + +PyObject * decode_tagblock(char * tagblock_str) +{ + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + int num_fields = 0; + int status = SUCCESS; + + PyObject* dict = PyDict_New(); + + num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); + if (num_fields < 0) + status = FAIL; + + for (int i = 0; i < num_fields && status==SUCCESS; i++) + { + struct TAGBLOCK_FIELD* field = &fields[i]; + const char* key = field->key; + + switch(key[0]) + { + case 'c': + status = decode_timestamp_field(field, dict); + break; + case 'd': + case 's': + case 't': + status = decode_text_field(field, dict); + break; + case 'g': + status = decode_group_field(field, dict); + break; + case 'n': + case 'r': + status = decode_int_field(field, dict); + break; + default: + status = decode_custom_field(field, dict); + } + } + if (status == SUCCESS) + return dict; + else + return NULL; +} + +//PyObject * +//tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// const char *param; +// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// +// if (nargs != 1) +// return NULL; +// +// param = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// +// if (safe_strcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) +// { +// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); +// return NULL; +// } +// +// PyObject* dict = decode_tagblock(tagblock_str); +// +// if (!dict) +// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); +// +// return dict; +//} \ No newline at end of file diff --git a/ais_tools/core/tagblock_encode.c b/ais_tools/core/tagblock_encode.c new file mode 100644 index 0000000..d938d80 --- /dev/null +++ b/ais_tools/core/tagblock_encode.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include "core.h" +#include "tagblock.h" + + +size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* group_fields) +{ + const char * end = buffer + buf_size - 1; + char * ptr = buffer; + + for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) + { + // make sure that all values are non-empty + if (group_fields[i].value == NULL) + return 0; + + ptr = unsafe_strcpy(ptr, end, group_fields[i].value); + if (i < ARRAY_LENGTH(group_field_keys) - 1) + ptr = unsafe_strcpy(ptr, end, GROUP_SEPARATOR); + } + *ptr++ = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string + + return ptr - buffer; +} + + +int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) +{ + PyObject *key, *value; + Py_ssize_t pos = 0; + size_t field_idx = 0; + struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; + + init_fields (group_fields, ARRAY_LENGTH(group_fields)); + + while (PyDict_Next(dict, &pos, &key, &value)) + { + if (field_idx == max_fields) + return FAIL; // no more room in fields + + const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); + const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); + + size_t group_ordinal = lookup_group_field_key(key_str); + if (group_ordinal > 0) + { + group_fields[group_ordinal - 1].value = value_str; + } + else + { + const char* short_key = lookup_short_key (key_str); + + if (short_key) + safe_strcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); + else + extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); + + fields[field_idx].value = value_str; + field_idx++; + } + } + + // encode group field and add it to the field list + // check the return code to see if there is a complete set of group fields + if (encode_group_fields(buffer, buf_size, group_fields)) + { + if (field_idx >= max_fields) + return FAIL; // no more room to add another field + + safe_strcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); + fields[field_idx].value = buffer; + field_idx++; + } + + return field_idx; +} + +int encode_tagblock(char * dest, PyObject *dict, size_t dest_buf_size) +{ + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + char value_buffer [MAX_VALUE_LEN]; + + init_fields (fields, ARRAY_LENGTH(fields)); + + int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); + if (num_fields < 0) + return FAIL_TOO_MANY_FIELDS; + + int str_len = join_fields(fields, num_fields, dest, dest_buf_size); + if (str_len < 0) + return FAIL_STRING_TOO_LONG; + return str_len; +} \ No newline at end of file diff --git a/ais_tools/core/tagblock_fields.c b/ais_tools/core/tagblock_fields.c new file mode 100644 index 0000000..f21be8c --- /dev/null +++ b/ais_tools/core/tagblock_fields.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include "core.h" +#include "tagblock.h" + + +typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; +static KEY_MAP key_map[] = { + {"c", TAGBLOCK_TIMESTAMP}, + {"d", TAGBLOCK_DESTINATION}, + {"n", TAGBLOCK_LINE_COUNT}, + {"r", TAGBLOCK_RELATIVE_TIME}, + {"s", TAGBLOCK_STATION}, + {"t", TAGBLOCK_TEXT} +}; + +const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; + +const char* lookup_long_key(const char *short_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) + if (0 == strcmp(key_map[i].short_key, short_key)) + return key_map[i].long_key; + return NULL; +} + +const char* lookup_short_key(const char* long_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) + if (0 == strcmp(key_map[i].long_key, long_key)) + return key_map[i].short_key; + return NULL; +} + +size_t lookup_group_field_key(const char* long_key) +{ + for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) + if (0 == strcmp(long_key, group_field_keys[i])) + return i + 1; + return 0; +} + +void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) +{ + size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; + + if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) + safe_strcpy(buffer, &long_key[prefix_len], buf_size); + else + safe_strcpy(buffer, long_key, buf_size); +} + +void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) +{ + for (size_t i = 0; i < num_fields; i++) + { + fields[i].key[0] = '\0'; + fields[i].value = NULL; + } +} + +// split a tagblock string with structure +// k:value,k:value +// k:value,k:value*cc +// \\k:value,k:value*cc\\other_stuff + +int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) +{ + int idx = 0; + char * ptr; + char * field_save_ptr = NULL; + char * field; + + char * key_value_save_ptr = NULL; + + // skip leading tagblock delimiter + if (*tagblock_str == *TAGBLOCK_SEPARATOR) + tagblock_str++; + + // seek forward to find either the checksum, the next tagblock separator, or the end of the string. + // Terminate the string at the first delimiter found + for (ptr = tagblock_str; *ptr != *TAGBLOCK_SEPARATOR && *ptr != *CHECKSUM_SEPARATOR && *ptr != '\0'; ptr++); + *ptr = '\0'; + + // get the first comma delimited field + field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); + while (field && *field && idx < max_fields) + { + // for each field, split into key part and value part + const char* key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + const char* value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); + + // if we don't have both key and value, then fail + if (key && value) + { + safe_strcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); + fields[idx].value = value; + idx++; + } + else + return FAIL; + + // advance to the next field + field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); + } + return idx; +} + + +int join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) +{ + const char * end = tagblock_str + buf_size - 1; + size_t last_field_idx = num_fields - 1; + char * ptr = tagblock_str; + char checksum[3]; + + for (size_t idx = 0; idx < num_fields; idx++) + { + ptr = unsafe_strcpy(ptr, end, fields[idx].key); + ptr = unsafe_strcpy(ptr, end, KEY_VALUE_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, fields[idx].value); + if (idx < last_field_idx) + { + ptr = unsafe_strcpy(ptr, end, FIELD_SEPARATOR); + } + } + *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string + + checksum_str(checksum, tagblock_str, ARRAY_LENGTH(checksum)); + + ptr = unsafe_strcpy(ptr, end, CHECKSUM_SEPARATOR); + ptr = unsafe_strcpy(ptr, end, checksum); + + if (ptr == end) + return FAIL; + + *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string + return ptr - tagblock_str; +} + +int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, + struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) +{ + for (size_t update_idx = 0; update_idx < num_update_fields; update_idx++) + { + char* key = update_fields[update_idx].key; + + size_t fields_idx = 0; + while (fields_idx < num_fields) + { + if (0 == strcmp(key, fields[fields_idx].key)) + break; + fields_idx++; + } + if (fields_idx == num_fields) + { + if (num_fields < max_fields) + num_fields++; + else + return FAIL; + } + + fields[fields_idx] = update_fields[update_idx]; + } + + return num_fields; +} \ No newline at end of file diff --git a/ais_tools/nmea.py b/ais_tools/nmea.py index 17d92a9..19179b1 100644 --- a/ais_tools/nmea.py +++ b/ais_tools/nmea.py @@ -3,7 +3,7 @@ import re from ais import DecodeError -from ais_tools.checksum import is_checksum_valid +from ais_tools.core import is_checksum_valid from ais_tools.tagblock import split_tagblock from ais_tools.tagblock import decode_tagblock diff --git a/ais_tools/strlcpy.c b/ais_tools/strlcpy.c deleted file mode 100644 index e196bbf..0000000 --- a/ais_tools/strlcpy.c +++ /dev/null @@ -1,55 +0,0 @@ -// Original file: https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c - -/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ - -/* - * Copyright (c) 1998, 2015 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -//__FBSDID("$FreeBSD$"); - -#include -//#include - -/* - * Copy string src to buffer dst of size dsize. At most dsize-1 - * chars will be copied. Always NUL terminates (unless dsize == 0). - * Returns strlen(src); if retval >= dsize, truncation occurred. - */ -size_t -zzz_strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) -{ - const char *osrc = src; - size_t nleft = dsize; - - /* Copy as many bytes as will fit. */ - if (nleft != 0) { - while (--nleft != 0) { - if ((*dst++ = *src++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src. */ - if (nleft == 0) { - if (dsize != 0) - *dst = '\0'; /* NUL-terminate dst */ - while (*src++) - ; - } - - return(src - osrc - 1); /* count does not include NUL */ -} \ No newline at end of file diff --git a/ais_tools/tagblock.c b/ais_tools/tagblock.c deleted file mode 100644 index e27613e..0000000 --- a/ais_tools/tagblock.c +++ /dev/null @@ -1,665 +0,0 @@ -// tagblock module - -#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ -#include -#include -#include -#include -#include "ais-tools.h" - - -#define SUCCESS 0 -#define FAIL -1 - -const size_t MAX_TAGBLOCK_FIELDS = 8; -const size_t MAX_TAGBLOCK_STR_LEN = 1024; -const size_t MAX_KEY_LEN = 32; -const size_t MAX_VALUE_LEN = 256; -const char * CHECKSUM_SEPARATOR = "*"; -const char * FIELD_SEPARATOR = ","; -const char * KEY_VALUE_SEPARATOR = ":"; -const char * GROUP_SEPARATOR = "-"; -const char* TAGBLOCK_SEPARATOR = "\\"; -const char* AIVDM_START = "!"; -const char * EMPTY_STRING = ""; - -struct TAGBLOCK_FIELD -{ - char key[2]; - const char* value; -}; - -#define TAGBLOCK_TIMESTAMP "tagblock_timestamp" -#define TAGBLOCK_DESTINATION "tagblock_destination" -#define TAGBLOCK_LINE_COUNT "tagblock_line_count" -#define TAGBLOCK_RELATIVE_TIME "tagblock_relative_time" -#define TAGBLOCK_STATION "tagblock_station" -#define TAGBLOCK_TEXT "tagblock_text" -#define TAGBLOCK_SENTENCE "tagblock_sentence" -#define TAGBLOCK_GROUPSIZE "tagblock_groupsize" -#define TAGBLOCK_ID "tagblock_id" -#define CUSTOM_FIELD_PREFIX "tagblock_" -#define TAGBLOCK_GROUP "g" - -#define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" -#define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" -#define ERR_TOO_MANY_FIELDS "Too many fields" -#define ERR_NMEA_TOO_LONG "NMEA string too long" - -typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; -static KEY_MAP key_map[] = { - {"c", TAGBLOCK_TIMESTAMP}, - {"d", TAGBLOCK_DESTINATION}, - {"n", TAGBLOCK_LINE_COUNT}, - {"r", TAGBLOCK_RELATIVE_TIME}, - {"s", TAGBLOCK_STATION}, - {"t", TAGBLOCK_TEXT} -}; - -static const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; - -const char* lookup_long_key(const char *short_key) -{ - for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) - if (0 == strcmp(key_map[i].short_key, short_key)) - return key_map[i].long_key; - return NULL; -} - -const char* lookup_short_key(const char* long_key) -{ - for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) - if (0 == strcmp(key_map[i].long_key, long_key)) - return key_map[i].short_key; - return NULL; -} - -size_t lookup_group_field_key(const char* long_key) -{ - for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) - if (0 == strcmp(long_key, group_field_keys[i])) - return i + 1; - return 0; -} - -void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) -{ - size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; - - if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) - zzz_strlcpy(buffer, &long_key[prefix_len], buf_size); - else - zzz_strlcpy(buffer, long_key, buf_size); -} - -void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) -{ - for (size_t i = 0; i < num_fields; i++) - { - fields[i].key[0] = '\0'; - fields[i].value = NULL; - } -} - -// copy str into dest up to end -// return a pointer to the position immediately after the last position written in dest -// does not copy the null terminator from str -static char *unsafe_strcat(char *dest, const char *end, const char *str) -{ - while (dest < end && *str) - *dest++ = *str++; - return dest; -} - -int split_tagblock(char* message, const char** tagblock, const char** nmea) -{ - // Four cases for the given message string - - // starts with '!' - no tagblock, message is all nmea - // starts with '\!' - no tagblock, strip off '\', message is all nmea - // starts with '\[^!]' - is a tagblock , strip off leading '\', nmea is whatever comes after the next '\'. - // start with '[^\!]' - is a tagblock, nmea is whatever comes after the next '\'. - // starts with '[^!]' and there are no `\` delimiters - tagblock is empty, entire string in nmea - - char* ptr; - int tagblock_len = 0; - - ptr = message; - if (*ptr == *TAGBLOCK_SEPARATOR) - ptr ++; - if (*ptr == *AIVDM_START) - { - *nmea = ptr; - *tagblock = EMPTY_STRING; - } - else - { - *tagblock = ptr; - for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); - tagblock_len = ptr - *tagblock; - if (*ptr) - { - *ptr = '\0'; - *nmea = ptr + 1; - } -// else if (*message == *TAGBLOCK_SEPARATOR) -// { -// *nmea = *tagblock; -// *tagblock = EMPTY_STRING; -// } - else - *nmea = EMPTY_STRING; - } - - return tagblock_len; -} - -int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) -{ - char* end = buffer + buf_size - 1; - char* ptr = buffer; - - if (*tagblock_str && *nmea_str) - { - if (*tagblock_str != *TAGBLOCK_SEPARATOR) - ptr = unsafe_strcat(ptr, end, TAGBLOCK_SEPARATOR); - ptr = unsafe_strcat(ptr, end, tagblock_str); - - if (*nmea_str != *TAGBLOCK_SEPARATOR) - ptr = unsafe_strcat(ptr, end, TAGBLOCK_SEPARATOR); - ptr = unsafe_strcat(ptr, end, nmea_str); - } - else - { - ptr = unsafe_strcat(ptr, end, tagblock_str); - ptr = unsafe_strcat(ptr, end, nmea_str); - } - - if (ptr <= end) - { - *ptr = '\0'; - return ptr - buffer; - } - else - { - *end = '\0'; - return FAIL; - } - - return SUCCESS; -} - -// split a tagblock string with structure -// k:value,k:value -// k:value,k:value*cc -// \\k:value,k:value*cc\\other_stuff - -int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) -{ - int idx = 0; - char * ptr; - char * field_save_ptr = NULL; - char * field; - - char * key_value_save_ptr = NULL; - - // skip leading tagblock delimiter - if (*tagblock_str == *TAGBLOCK_SEPARATOR) - tagblock_str++; - - // seek forward to find either the checksum, the next tagblock separator, or the end of the string. - // Terminate the string at the first delimiter found - for (ptr = tagblock_str; *ptr != *TAGBLOCK_SEPARATOR && *ptr != *CHECKSUM_SEPARATOR && *ptr != '\0'; ptr++); - *ptr = '\0'; - - // get the first comma delimited field - field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); - while (field && *field && idx < max_fields) - { - // for each field, split into key part and value part - const char* key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); - const char* value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); - - // if we don't have both key and value, then fail - if (key && value) - { - zzz_strlcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); - fields[idx].value = value; - idx++; - } - else - return FAIL; - - // advance to the next field - field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); - } - return idx; -} - -// TODO: need a return value that indicates the string is too long -size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) -{ - const char * end = tagblock_str + buf_size - 5; // leave room for 3 chars for the checkcum plus trailing null - size_t last_field_idx = num_fields - 1; - char * ptr = tagblock_str; - char checksum_str[3]; - - for (size_t idx = 0; idx < num_fields; idx++) - { - ptr = unsafe_strcat(ptr, end, fields[idx].key); - ptr = unsafe_strcat(ptr, end, KEY_VALUE_SEPARATOR); - ptr = unsafe_strcat(ptr, end, fields[idx].value); - if (idx < last_field_idx) - { - ptr = unsafe_strcat(ptr, end, FIELD_SEPARATOR); - } - } - *ptr++ = '\0'; // very important! unsafe_strcat does not add a null at the end of the string - - // TODO: use unsafe_strcat instead of strcat - _checksum_str(tagblock_str, checksum_str); - - strcat(tagblock_str, CHECKSUM_SEPARATOR); - strcat(tagblock_str, checksum_str); - - return strlen(tagblock_str); -} - -int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -{ - const char* key = lookup_long_key(field->key); - char * end; - - long value = strtol(field->value, &end, 10); - if (errno || *end) - return FAIL; - - if (value > 40000000000) - value = value / 1000; - PyDict_SetItemString(dict, key, PyLong_FromLong(value)); - return SUCCESS; -} - -int decode_int_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -{ - const char* key = lookup_long_key(field->key); - char* end; - - long value = strtol(field->value, &end, 10); - if (errno || *end) - return FAIL; - - PyDict_SetItemString(dict, key, PyLong_FromLong(value)); - - return SUCCESS; -} - -int decode_text_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -{ - const char* key = lookup_long_key(field->key); - PyDict_SetItemString(dict, key, PyUnicode_FromString(field->value)); - - return SUCCESS; -} - -int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -{ - size_t idx = 0; - char * save_ptr = NULL; - long values[ARRAY_LENGTH(group_field_keys)]; - char buffer[MAX_VALUE_LEN]; - - if (zzz_strlcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) - return FAIL; - - char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); - while (f && idx < ARRAY_LENGTH(values)) - { - char* end; - values[idx++] = strtol(f, &end, 10); - if (errno || *end) - return FAIL; - - f = strtok_r(NULL, GROUP_SEPARATOR, &save_ptr); - } - if (idx == ARRAY_LENGTH(values)) - { - for (idx = 0; idx < ARRAY_LENGTH(values); idx++) - PyDict_SetItemString(dict, group_field_keys[idx], PyLong_FromLong(values[idx])); - return SUCCESS; - } - - return FAIL; -} - -int decode_custom_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -{ - char custom_key[MAX_KEY_LEN]; - snprintf(custom_key, ARRAY_LENGTH(custom_key), "%s%s", CUSTOM_FIELD_PREFIX, field->key); - PyDict_SetItemString(dict, custom_key, PyUnicode_FromString(field->value)); - - return SUCCESS; -} - -size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* group_fields) -{ - const char * end = buffer + buf_size - 1; - char * ptr = buffer; - - for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) - { - // make sure that all values are non-empty - if (group_fields[i].value == NULL) - return 0; - - ptr = unsafe_strcat(ptr, end, group_fields[i].value); - if (i < ARRAY_LENGTH(group_field_keys) - 1) - ptr = unsafe_strcat(ptr, end, GROUP_SEPARATOR); - } - *ptr++ = '\0'; // very important! unsafe_strcat does not add a null at the end of the string - - return ptr - buffer; -} - -static PyObject * -tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - const char *param; - char tagblock_str[MAX_TAGBLOCK_STR_LEN]; - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - int num_fields = 0; - int status = SUCCESS; - - if (nargs != 1) - return NULL; - - param = PyUnicode_AsUTF8(PyObject_Str(args[0])); - - if (zzz_strlcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) - { - PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); - return NULL; - } - - PyObject* dict = PyDict_New(); - - num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); - if (num_fields < 0) - status = FAIL; - - for (int i = 0; i < num_fields && status==SUCCESS; i++) - { - struct TAGBLOCK_FIELD* field = &fields[i]; - const char* key = field->key; - - switch(key[0]) - { - case 'c': - status = decode_timestamp_field(field, dict); - break; - case 'd': - case 's': - case 't': - status = decode_text_field(field, dict); - break; - case 'g': - status = decode_group_field(field, dict); - break; - case 'n': - case 'r': - status = decode_int_field(field, dict); - break; - default: - status = decode_custom_field(field, dict); - } - } - - if (status == SUCCESS) - return dict; - else - { - PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); - return NULL; - } -} - -int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) -{ - PyObject *key, *value; - Py_ssize_t pos = 0; - size_t field_idx = 0; - struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; - - init_fields (group_fields, ARRAY_LENGTH(group_fields)); - - while (PyDict_Next(dict, &pos, &key, &value)) - { - if (field_idx == max_fields) - return FAIL; // no more room in fields - - const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); - const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); - - size_t group_ordinal = lookup_group_field_key(key_str); - if (group_ordinal > 0) - { - group_fields[group_ordinal - 1].value = value_str; - } - else - { - const char* short_key = lookup_short_key (key_str); - - if (short_key) - zzz_strlcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); - else - extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); - - fields[field_idx].value = value_str; - field_idx++; - } - } - - // encode group field and add it to the field list - // check the return code to see if there is a complete set of group fields - if (encode_group_fields(buffer, buf_size, group_fields)) - { - if (field_idx >= max_fields) - return FAIL; // no more room to add another field - - zzz_strlcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); - fields[field_idx].value = buffer; - field_idx++; - } - - return field_idx; -} - - -int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, - struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) -{ - for (size_t update_idx = 0; update_idx < num_update_fields; update_idx++) - { - char* key = update_fields[update_idx].key; - - size_t fields_idx = 0; - while (fields_idx < num_fields) - { - if (0 == strcmp(key, fields[fields_idx].key)) - break; - fields_idx++; - } - if (fields_idx == num_fields) - { - if (num_fields < max_fields) - num_fields++; - else - return FAIL; - } - - fields[fields_idx] = update_fields[update_idx]; - } - - return num_fields; -} - - - -static PyObject * -tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - - PyObject *dict; - - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - - init_fields (fields, ARRAY_LENGTH(fields)); - - char tagblock_str[MAX_TAGBLOCK_STR_LEN]; - char value_buffer [MAX_VALUE_LEN]; - - if (nargs != 1) - return NULL; - - dict = args[0]; - - int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); - if (num_fields < 0) - { - PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - return NULL; - } - - join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); - - return PyUnicode_FromString(tagblock_str); -} - - -static PyObject * -tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - const char* str; - PyObject* dict; - char message[MAX_SENTENCE_LENGTH]; - char updated_message[MAX_SENTENCE_LENGTH]; - const char* tagblock_str; - const char* nmea_str; - - - if (nargs != 2) - return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); - - str = PyUnicode_AsUTF8(PyObject_Str(args[0])); - dict = args[1]; - - if (zzz_strlcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - split_tagblock(message, &tagblock_str, &nmea_str); - - - char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; - if (zzz_strlcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) - return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); - - - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - int num_fields = 0; - num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); - if (num_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); - - - struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; - int num_update_fields = 0; - char value_buffer[MAX_VALUE_LEN]; - num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), - value_buffer, ARRAY_LENGTH(value_buffer)); - if (num_update_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - - num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); - if (num_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - - char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; - join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); - - int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); - if (msg_len < 0) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - - return PyUnicode_FromString(updated_message); - -} - -static PyObject * -tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - const char* str; - char buffer[MAX_SENTENCE_LENGTH]; - const char* tagblock_str; - const char* nmea_str; - - if (nargs != 1) - return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); - - str = PyUnicode_AsUTF8(PyObject_Str(args[0])); - if (zzz_strlcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - split_tagblock(buffer, &tagblock_str, &nmea_str); - - return PyTuple_Pack(2, PyUnicode_FromString(tagblock_str), PyUnicode_FromString(nmea_str)); -} - -static PyObject * -tagblock_join(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - char buffer[MAX_SENTENCE_LENGTH]; - const char* tagblock_str; - const char* nmea_str; - - if (nargs != 2) - return PyErr_Format(PyExc_TypeError, "join expects 2 arguments"); - - tagblock_str = PyUnicode_AsUTF8(PyObject_Str(args[0])); - nmea_str = PyUnicode_AsUTF8(PyObject_Str(args[1])); - - if (FAIL == join_tagblock(buffer, ARRAY_LENGTH(buffer), tagblock_str, nmea_str)) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - return PyUnicode_FromString(buffer); -} - -static PyMethodDef tagblock_methods[] = { - {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_FASTCALL, - "decode a tagblock string. Returns a dict"}, - {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_FASTCALL, - "encode a tagblock string from a dict. Returns a string"}, - {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, - "update a tagblock string from a dict. Returns a string"}, - {"split", (PyCFunction)(void(*)(void))tagblock_split, METH_FASTCALL, - "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, - {"join", (PyCFunction)(void(*)(void))tagblock_join, METH_FASTCALL, - "Join a tagblock to an AIVDM message. Returns a string."}, - {NULL, NULL, 0, NULL} /* sentinel */ -}; - -static struct PyModuleDef tagblock_module = { - PyModuleDef_HEAD_INIT, - "_tagblock", - NULL, - -1, - tagblock_methods -}; - -PyMODINIT_FUNC -PyInit__tagblock(void) -{ - return PyModule_Create(&tagblock_module); -} \ No newline at end of file diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index e2eda54..f202496 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -2,9 +2,10 @@ from datetime import timezone from ais import DecodeError -from ais_tools.checksum import checksumstr -from ais_tools.checksum import is_checksum_valid +# from ais_tools.core import checksum_str +from ais_tools.core import is_checksum_valid from ais_tools import _tagblock +from ais_tools import core TAGBLOCK_T_FORMAT = '%Y-%m-%d %H.%M.%S' @@ -42,8 +43,10 @@ def create_tagblock(station, timestamp=None, add_tagblock_t=True): ) if add_tagblock_t: params['T'] = datetime.fromtimestamp(t, tz=timezone.utc).strftime(TAGBLOCK_T_FORMAT) - param_str = ','.join(["{}:{}".format(k, v) for k, v in params.items()]) - return '{}*{}'.format(param_str, checksumstr(param_str)) + return core.encode_tagblock(params) + + # param_str = ','.join(["{}:{}".format(k, v) for k, v in params.items()]) + # return '{}*{}'.format(param_str, checksum_str(param_str)) def split_tagblock(nmea): @@ -53,7 +56,7 @@ def split_tagblock(nmea): Note that if the nmea is a concatenated multipart message then only the tagblock of the first message will be split off """ - return _tagblock.split(nmea) + return core.split_tagblock(nmea) @@ -62,7 +65,7 @@ def join_tagblock(tagblock, nmea): Join a tagblock to an AIVDM message that does not already have a tagblock """ - return _tagblock.join(tagblock, nmea) + return core.join_tagblock(tagblock, nmea) @@ -79,34 +82,20 @@ def add_tagblock(tagblock, nmea, overwrite=True): return join_tagblock(tagblock, nmea) -tagblock_fields = { - 'c': 'tagblock_timestamp', - 'n': 'tagblock_line_count', - 'r': 'tagblock_relative_time', - 'd': 'tagblock_destination', - 's': 'tagblock_station', - 't': 'tagblock_text', -} - -tagblock_fields_reversed = {v: k for k, v in tagblock_fields.items()} - -tagblock_group_fields = ["tagblock_sentence", "tagblock_groupsize", "tagblock_id"] - - def encode_tagblock(**kwargs): try: - return _tagblock.encode(kwargs) - except: - raise DecodeError('unable to encode tagblock') + return core.encode_tagblock(kwargs) + except ValueError as e: + raise DecodeError(e) def decode_tagblock(tagblock_str, validate_checksum=False): if validate_checksum and not is_checksum_valid(tagblock_str): raise DecodeError('Invalid checksum') try: - return _tagblock.decode(tagblock_str) - except: - raise DecodeError('Unable to decode tagblock') + return core.decode_tagblock(tagblock_str) + except ValueError as e: + raise DecodeError(e) def update_tagblock(nmea, **kwargs): diff --git a/setup.py b/setup.py index c66e306..1b36c5c 100644 --- a/setup.py +++ b/setup.py @@ -67,10 +67,19 @@ ''', ext_modules=[ Extension( - "ais_tools.checksum", + "ais_tools.core", extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, - sources=["ais_tools/checksum.c", "ais_tools/strlcpy.c"], + sources=["ais_tools/core/module.c", + "ais_tools/core/methods.c", + "ais_tools/core/join_tagblock.c", + "ais_tools/core/split_tagblock.c", + "ais_tools/core/tagblock_fields.c", + "ais_tools/core/tagblock_decode.c", + "ais_tools/core/tagblock_encode.c", + "ais_tools/core/checksum.c", + "ais_tools/core/strcpy.c", + ], include_dirs=["ais_tools/"], undef_macros=undef_macros, ), @@ -78,7 +87,13 @@ "ais_tools._tagblock", extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, - sources=["ais_tools/tagblock.c", "ais_tools/checksum.c", "ais_tools/strlcpy.c"], + sources=["ais_tools/core/tagblock.c", + "ais_tools/core/tagblock_fields.c", + "ais_tools/core/tagblock_decode.c", + "ais_tools/core/tagblock_encode.c", + "ais_tools/core/methods.c", + "ais_tools/core/checksum.c", + "ais_tools/core/strcpy.c"], include_dirs=["ais_tools/"], undef_macros=undef_macros, ) diff --git a/tests/test_checksum.py b/tests/test_checksum.py index 1691f3d..6f9ccd6 100644 --- a/tests/test_checksum.py +++ b/tests/test_checksum.py @@ -1,8 +1,8 @@ import pytest -from ais_tools.checksum import checksum -from ais_tools.checksum import is_checksum_valid -from ais_tools.checksum import checksumstr +from ais_tools.core import checksum +from ais_tools.core import is_checksum_valid +from ais_tools.core import checksum_str import warnings with warnings.catch_warnings(): @@ -27,7 +27,7 @@ def test_checksum(str, expected): ('', '00'), ]) def test_checksum_str(str, expected): - actual = checksumstr(str) + actual = checksum_str(str) assert actual == expected if len(str) > 1: diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index dc405b0..bfdd379 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -4,7 +4,7 @@ from ais_tools.tagblock import DecodeError from ais_tools import _tagblock - +from ais_tools import core @pytest.mark.parametrize("line,expected", [ ("\\s:rORBCOMM000,q:u,c:1509502436,T:2017-11-01 02.13.56*50\\!AIVDM,1,1,,A,13`el0gP000H=3JN9jb>4?wb0>`<,0*7B", @@ -148,7 +148,7 @@ def test_update_tagblock(tagblock_str, new_fields, expected): ('g:1-2-3', {'tagblock_sentence': 1, 'tagblock_groupsize': 2, 'tagblock_id': 3}) ]) def test_tagblock_decode(tagblock_str, expected): - assert _tagblock.decode(tagblock_str) == expected + assert core.decode_tagblock(tagblock_str) == expected @pytest.mark.parametrize("fields,expected", [ @@ -162,7 +162,7 @@ def test_tagblock_decode(tagblock_str, expected): ({'tagblock_line_count': 1}, 'n:1*65') ]) def test_tagblock_encode(fields, expected): - assert _tagblock.encode(fields) == expected + assert core.encode_tagblock(fields) == expected @pytest.mark.parametrize("fields", [ @@ -181,7 +181,7 @@ def test_tagblock_encode(fields, expected): }), ]) def test_encode_decode(fields): - assert _tagblock.decode(_tagblock.encode(fields)) == fields + assert core.decode_tagblock(core.encode_tagblock(fields)) == fields def test_update(): From 3546314665c27479cc252a28d0bff75737d0f2d6 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 21 Feb 2023 12:07:18 -0500 Subject: [PATCH 10/16] much refactoring --- ais_tools/core/core.h | 11 +-- ais_tools/core/methods.c | 35 ++++++++- ais_tools/core/methods.h | 9 +++ ais_tools/core/module.c | 5 +- ais_tools/core/tagblock.c | 124 +++++++++++++++---------------- ais_tools/core/tagblock.h | 2 +- ais_tools/core/tagblock_update.c | 121 ++++++++++++++++++++++++++++++ ais_tools/tagblock.py | 5 +- setup.py | 45 +++-------- tests/test_tagblock.py | 2 +- 10 files changed, 247 insertions(+), 112 deletions(-) create mode 100644 ais_tools/core/methods.h create mode 100644 ais_tools/core/tagblock_update.c diff --git a/ais_tools/core/core.h b/ais_tools/core/core.h index 4304dc7..d91d1b3 100644 --- a/ais_tools/core/core.h +++ b/ais_tools/core/core.h @@ -14,6 +14,7 @@ #define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" #define ERR_TOO_MANY_FIELDS "Too many fields" #define ERR_NMEA_TOO_LONG "NMEA string too long" +#define ERR_UNKNOWN "Unknown error" #define MAX_TAGBLOCK_FIELDS 8 // max number of fields allowed in a tagblock #define MAX_TAGBLOCK_STR_LEN 1024 // max length of a tagblock string @@ -42,13 +43,3 @@ bool is_checksum_valid(char* s); // tagblock functions int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str); int split_tagblock(char* message, const char** tagblock, const char** nmea); - - -// Module methods -PyObject * method_compute_checksum(PyObject *module, PyObject *args); -PyObject * method_compute_checksum_str(PyObject *module, PyObject *args); -PyObject * method_is_checksum_valid(PyObject *module, PyObject *args); -PyObject * method_join_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_split_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_decode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); diff --git a/ais_tools/core/methods.c b/ais_tools/core/methods.c index 66b1708..6c43c0f 100644 --- a/ais_tools/core/methods.c +++ b/ais_tools/core/methods.c @@ -133,4 +133,37 @@ method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs } return PyUnicode_FromString(tagblock_str); -} \ No newline at end of file +} + +PyObject * +method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + const char* str; + PyObject* dict; + char message[MAX_SENTENCE_LENGTH]; + char updated_message[MAX_SENTENCE_LENGTH]; + + if (nargs != 2) + return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + dict = args[1]; + + if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) + return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + int message_len = update_tagblock(updated_message, ARRAY_LENGTH(updated_message), message, dict); + + if (message_len < 0) + switch(message_len) + { + case FAIL_STRING_TOO_LONG: + return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + case FAIL_TOO_MANY_FIELDS: + return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + default: + return PyErr_Format(PyExc_ValueError, ERR_UNKNOWN); + } + + return PyUnicode_FromString(updated_message); +} diff --git a/ais_tools/core/methods.h b/ais_tools/core/methods.h new file mode 100644 index 0000000..0894c82 --- /dev/null +++ b/ais_tools/core/methods.h @@ -0,0 +1,9 @@ +// Module methods +PyObject * method_compute_checksum(PyObject *module, PyObject *args); +PyObject * method_compute_checksum_str(PyObject *module, PyObject *args); +PyObject * method_is_checksum_valid(PyObject *module, PyObject *args); +PyObject * method_join_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_split_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_decode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); \ No newline at end of file diff --git a/ais_tools/core/module.c b/ais_tools/core/module.c index af0a2cb..c97e293 100644 --- a/ais_tools/core/module.c +++ b/ais_tools/core/module.c @@ -1,6 +1,7 @@ #include #include #include "core.h" +#include "methods.h" static PyMethodDef core_methods[] = { {"checksum", (PyCFunction)(void(*)(void))method_compute_checksum, METH_VARARGS, @@ -13,8 +14,8 @@ static PyMethodDef core_methods[] = { "decode a tagblock string. Returns a dict"}, {"encode_tagblock", (PyCFunction)(void(*)(void))method_encode_tagblock, METH_FASTCALL, "encode a tagblock string from a dict. Returns a string"}, -// {"update_tagblock", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, -// "update a tagblock string from a dict. Returns a string"}, + {"update_tagblock", (PyCFunction)(void(*)(void))method_update_tagblock, METH_FASTCALL, + "update a tagblock string from a dict. Returns a string"}, {"split_tagblock", (PyCFunction)(void(*)(void))method_split_tagblock, METH_FASTCALL, "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, {"join_tagblock", (PyCFunction)(void(*)(void))method_join_tagblock, METH_FASTCALL, diff --git a/ais_tools/core/tagblock.c b/ais_tools/core/tagblock.c index 5ade74b..bb6ae14 100644 --- a/ais_tools/core/tagblock.c +++ b/ais_tools/core/tagblock.c @@ -540,66 +540,66 @@ int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const //} -static PyObject * -tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - const char* str; - PyObject* dict; - char message[MAX_SENTENCE_LENGTH]; - char updated_message[MAX_SENTENCE_LENGTH]; - const char* tagblock_str; - const char* nmea_str; - - - if (nargs != 2) - return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); - - str = PyUnicode_AsUTF8(PyObject_Str(args[0])); - dict = args[1]; - - if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - split_tagblock(message, &tagblock_str, &nmea_str); - - - char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; - if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) - return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); - - - struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; - int num_fields = 0; - num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); - if (num_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); - - - struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; - int num_update_fields = 0; - char value_buffer[MAX_VALUE_LEN]; - num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), - value_buffer, ARRAY_LENGTH(value_buffer)); - if (num_update_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - - num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); - if (num_fields < 0) - return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - - char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; - join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); - - int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); - if (msg_len < 0) - return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - - return PyUnicode_FromString(updated_message); - -} +//static PyObject * +//tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// const char* str; +// PyObject* dict; +// char message[MAX_SENTENCE_LENGTH]; +// char updated_message[MAX_SENTENCE_LENGTH]; +// const char* tagblock_str; +// const char* nmea_str; +// +// +// if (nargs != 2) +// return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); +// +// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// dict = args[1]; +// +// if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); +// +// split_tagblock(message, &tagblock_str, &nmea_str); +// +// +// char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; +// if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) +// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); +// +// +// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; +// int num_fields = 0; +// num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); +// if (num_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); +// +// +// struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; +// int num_update_fields = 0; +// char value_buffer[MAX_VALUE_LEN]; +// num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), +// value_buffer, ARRAY_LENGTH(value_buffer)); +// if (num_update_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); +// +// +// num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); +// if (num_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); +// +// +// char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); +// +// int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); +// if (msg_len < 0) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); +// +// +// return PyUnicode_FromString(updated_message); +// +//} //static PyObject * //tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) @@ -645,8 +645,8 @@ static PyMethodDef tagblock_methods[] = { // "decode a tagblock string. Returns a dict"}, // {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_FASTCALL, // "encode a tagblock string from a dict. Returns a string"}, - {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, - "update a tagblock string from a dict. Returns a string"}, +// {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, +// "update a tagblock string from a dict. Returns a string"}, // {"split", (PyCFunction)(void(*)(void))tagblock_split, METH_FASTCALL, // "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, // {"join", (PyCFunction)(void(*)(void))tagblock_join, METH_FASTCALL, diff --git a/ais_tools/core/tagblock.h b/ais_tools/core/tagblock.h index 066ed22..771e13a 100644 --- a/ais_tools/core/tagblock.h +++ b/ais_tools/core/tagblock.h @@ -37,5 +37,5 @@ int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fiel int encode_tagblock(char * dest, PyObject *dict, size_t dest_buf_size); PyObject * decode_tagblock(char * tagblock_str); - +int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dict); diff --git a/ais_tools/core/tagblock_update.c b/ais_tools/core/tagblock_update.c new file mode 100644 index 0000000..9378753 --- /dev/null +++ b/ais_tools/core/tagblock_update.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "core.h" +#include "tagblock.h" + + +int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dict) +{ + const char* tagblock_str; + const char* nmea_str; + char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; + struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; + int num_fields = 0; + + split_tagblock(message, &tagblock_str, &nmea_str); + + if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) + return FAIL_STRING_TOO_LONG; + + num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); + if (num_fields < 0) + return FAIL_TOO_MANY_FIELDS; + + struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; + int num_update_fields = 0; + char value_buffer[MAX_VALUE_LEN]; + num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), + value_buffer, ARRAY_LENGTH(value_buffer)); + if (num_update_fields < 0) + return FAIL_TOO_MANY_FIELDS; + + num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); + if (num_fields < 0) + return FAIL_TOO_MANY_FIELDS; + + char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; + join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); + + int msg_len = join_tagblock(dest, dest_size, updated_tagblock_str, nmea_str); + if (msg_len < 0) + return FAIL_STRING_TOO_LONG; + + return msg_len; +} + + +//// TODO: Move to methods.c +//static PyObject * +//method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +//{ +// const char* str; +// PyObject* dict; +// char message[MAX_SENTENCE_LENGTH]; +// char updated_message[MAX_SENTENCE_LENGTH]; +//// const char* tagblock_str; +//// const char* nmea_str; +// +// +// if (nargs != 2) +// return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); +// +// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); +// dict = args[1]; +// +// if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); +// +// int message_len = update_tagblock(updated_message); +// +// if (message_len < 0) +// switch(message_len) +// { +// case FAIL_STRING_TOO_LONG: +// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); +// case FAIL_TOO_MANY_FIELDS: +// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); +// default: +// return PyErr_Format(PyExc_ValueError, ERR_UNKNOWN); +// } +// +// return PyUnicode_FromString(updated_message); +// +//} +// split_tagblock(message, &tagblock_str, &nmea_str); + + +// char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; +// if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) +// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); + + +// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; +// int num_fields = 0; +// num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); +// if (num_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); + + +// struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; +// int num_update_fields = 0; +// char value_buffer[MAX_VALUE_LEN]; +// num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), +// value_buffer, ARRAY_LENGTH(value_buffer)); +// if (num_update_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + + +// num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); +// if (num_fields < 0) +// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); + + +// char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; +// join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); +// +// int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); +// if (msg_len < 0) +// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); + + diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index f202496..9f13c45 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -99,11 +99,12 @@ def decode_tagblock(tagblock_str, validate_checksum=False): def update_tagblock(nmea, **kwargs): - return _tagblock.update(nmea, kwargs) + return core.update_tagblock(nmea, kwargs) + def safe_update_tagblock(nmea, **kwargs): try: - nmea = update_tagblock(nmea, **kwargs) + nmea = core.update_tagblock(nmea, kwargs) except DecodeError: pass return nmea diff --git a/setup.py b/setup.py index 1b36c5c..4b1428f 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ import codecs import sys +import os from setuptools import Extension package = __import__('ais_tools') @@ -24,6 +25,16 @@ else: extra_compile_args += ["-std=c11", "-Wall", "-Werror", "-O3"] +source_path = 'ais_tools/core/' +sources = [f'{source_path}{file}' for file in os.listdir(source_path) if file.endswith('.c')] +core_module = Extension( + "ais_tools.core", + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + sources=sources, + include_dirs=[source_path], + undef_macros=undef_macros, +) DEPENDENCIES = [ "libais", @@ -65,37 +76,5 @@ [console_scripts] ais-tools=ais_tools.cli:cli ''', - ext_modules=[ - Extension( - "ais_tools.core", - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - sources=["ais_tools/core/module.c", - "ais_tools/core/methods.c", - "ais_tools/core/join_tagblock.c", - "ais_tools/core/split_tagblock.c", - "ais_tools/core/tagblock_fields.c", - "ais_tools/core/tagblock_decode.c", - "ais_tools/core/tagblock_encode.c", - "ais_tools/core/checksum.c", - "ais_tools/core/strcpy.c", - ], - include_dirs=["ais_tools/"], - undef_macros=undef_macros, - ), - Extension( - "ais_tools._tagblock", - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - sources=["ais_tools/core/tagblock.c", - "ais_tools/core/tagblock_fields.c", - "ais_tools/core/tagblock_decode.c", - "ais_tools/core/tagblock_encode.c", - "ais_tools/core/methods.c", - "ais_tools/core/checksum.c", - "ais_tools/core/strcpy.c"], - include_dirs=["ais_tools/"], - undef_macros=undef_macros, - ) - ], + ext_modules=[core_module], ) diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index bfdd379..d48cda3 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -188,4 +188,4 @@ def test_update(): tagblock_str="\\z:1*71\\" fields = {'tagblock_text': 'ABC'} expected = "z:1,t:ABC*53" - assert _tagblock.update(tagblock_str, fields) == expected + assert core.update_tagblock(tagblock_str, fields) == expected From 221655bb1e1a11d838ca492d898d0f574a8dd483 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 28 Feb 2023 17:31:30 -0500 Subject: [PATCH 11/16] refactor interfaces and document --- ais_tools/core/checksum.c | 77 +- ais_tools/core/checksum.h | 5 + ais_tools/core/core.h | 10 +- ais_tools/core/methods.c | 1 + ais_tools/core/strcpy.c | 4 +- ais_tools/core/tagblock.c | 669 ------------------ ais_tools/core/tagblock.h | 23 +- ais_tools/core/tagblock_decode.c | 27 +- ais_tools/core/tagblock_encode.c | 12 +- ais_tools/core/tagblock_fields.c | 94 ++- .../core/{join_tagblock.c => tagblock_join.c} | 0 .../{split_tagblock.c => tagblock_split.c} | 0 ais_tools/core/tagblock_update.c | 81 +-- ais_tools/tagblock.py | 2 - tests/test_tagblock.py | 1 - 15 files changed, 121 insertions(+), 885 deletions(-) create mode 100644 ais_tools/core/checksum.h delete mode 100644 ais_tools/core/tagblock.c rename ais_tools/core/{join_tagblock.c => tagblock_join.c} (100%) rename ais_tools/core/{split_tagblock.c => tagblock_split.c} (100%) diff --git a/ais_tools/core/checksum.c b/ais_tools/core/checksum.c index 632e174..25686cf 100644 --- a/ais_tools/core/checksum.c +++ b/ais_tools/core/checksum.c @@ -4,6 +4,7 @@ #include #include #include "core.h" +#include "checksum.h" /* * Compute the checksum value of a string. This is @@ -35,13 +36,7 @@ char* checksum_str(char * __restrict dst, const char* __restrict src, size_t dsi sprintf(dst, "%02X", c); return dst; } -// -//char* checksum_str(const char * s, char* c_str) -//{ -// int c = checksum(s); -// sprintf(c_str, "%02X", c); -// return c_str; -//} + /* * Compute the checksum value of the given string and compare it to the checksum @@ -86,71 +81,3 @@ bool is_checksum_valid(char* s) checksum_str(computed_checksum, body, ARRAY_LENGTH(computed_checksum)); return strcasecmp(c_str, computed_checksum) == 0; } - -// -//PyObject * -//core_compute_checksum(PyObject *module, PyObject *args) -//{ -// const char *str; -// int c; -// -// if (!PyArg_ParseTuple(args, "s", &str)) -// return NULL; -// c = checksum(str); -// return PyLong_FromLong(c); -//} -// -//PyObject * -//core_compute_checksum_str(PyObject *module, PyObject *args) -//{ -// const char *str; -// char c_str[3]; -// -// if (!PyArg_ParseTuple(args, "s", &str)) -// return NULL; -// checksum_str(c_str, str, ARRAY_LENGTH(c_str)); -// return PyUnicode_FromString(c_str); -//} -// -//PyObject * -//core_is_checksum_valid(PyObject *module, PyObject *args) -//{ -// char *str; -// char buffer[MAX_SENTENCE_LENGTH]; -// -// if (!PyArg_ParseTuple(args, "s", &str)) -// return NULL; -// -// if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) -// { -// PyErr_SetString(PyExc_ValueError, "String too long"); -// return NULL; -// } -// -// return is_checksum_valid(buffer) ? Py_True: Py_False; -//} - -// -//static PyMethodDef checksum_methods[] = { -// {"checksum", (PyCFunction)(void(*)(void))checksum_compute_checksum, METH_VARARGS, -// "Compute checksum of a string. returns an integer value"}, -// {"checksumstr", (PyCFunction)(void(*)(void))checksum_compute_checksumstr, METH_VARARGS, -// "Compute checksum of a string. returns a 2-character hex string"}, -// {"is_checksum_valid", (PyCFunction)(void(*)(void))checksum_is_checksum_valid, METH_VARARGS, -// "Returns True if the given string is terminated with a valid checksum, else False"}, -// {NULL, NULL, 0, NULL} /* sentinel */ -//}; -// -//static struct PyModuleDef checksum_module = { -// PyModuleDef_HEAD_INIT, -// "checksum", -// NULL, -// -1, -// checksum_methods -//}; -// -//PyMODINIT_FUNC -//PyInit_checksum(void) -//{ -// return PyModule_Create(&checksum_module); -//} \ No newline at end of file diff --git a/ais_tools/core/checksum.h b/ais_tools/core/checksum.h new file mode 100644 index 0000000..00bbbac --- /dev/null +++ b/ais_tools/core/checksum.h @@ -0,0 +1,5 @@ +/* AIS Tools checksum functions */ + +int checksum(const char *s); +char* checksum_str(char * __restrict dst, const char* __restrict src, size_t dsize); +bool is_checksum_valid(char* s); diff --git a/ais_tools/core/core.h b/ais_tools/core/core.h index d91d1b3..25685e7 100644 --- a/ais_tools/core/core.h +++ b/ais_tools/core/core.h @@ -32,14 +32,6 @@ // string copy utils -char * unsafe_strcpy(char * dest, const char * dest_end, const char * src); +char * unsafe_strcpy(char * __restrict dest, const char * __restrict est_end, const char * __restrict src); size_t safe_strcpy(char * __restrict dst, const char * __restrict src, size_t dsize); -// checksum functions -int checksum(const char *s); -char* checksum_str(char * dst, const char* src, size_t dsize); -bool is_checksum_valid(char* s); - -// tagblock functions -int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str); -int split_tagblock(char* message, const char** tagblock, const char** nmea); diff --git a/ais_tools/core/methods.c b/ais_tools/core/methods.c index 6c43c0f..7c58b22 100644 --- a/ais_tools/core/methods.c +++ b/ais_tools/core/methods.c @@ -2,6 +2,7 @@ #include #include #include "core.h" +#include "checksum.h" #include "tagblock.h" PyObject * diff --git a/ais_tools/core/strcpy.c b/ais_tools/core/strcpy.c index c0ba88e..a1645b1 100644 --- a/ais_tools/core/strcpy.c +++ b/ais_tools/core/strcpy.c @@ -6,13 +6,13 @@ * * Does not write a null terminator * dest_end should point to the last position in the dest string buffer. This method will - * stops at the character immediately before this position + * stop at the character immediately before this position * * returns a pointer the the position immediately after the last position written in dest * you can use the return pointer for a subsequent copy, or if you are finished, write a * null char to that position to terminate the string */ -char * unsafe_strcpy(char * dest, const char * dest_end, const char * src) +char * unsafe_strcpy(char * __restrict dest, const char * __restrict dest_end, const char * __restrict src) { while (dest < dest_end && *src) *dest++ = *src++; diff --git a/ais_tools/core/tagblock.c b/ais_tools/core/tagblock.c deleted file mode 100644 index bb6ae14..0000000 --- a/ais_tools/core/tagblock.c +++ /dev/null @@ -1,669 +0,0 @@ -// tagblock module - -#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ -#include -#include -#include -#include -#include "core.h" -#include "tagblock.h" - -//#define SUCCESS 0 -//#define FAIL -1 -// -//const size_t MAX_TAGBLOCK_FIELDS = 8; -//const size_t MAX_TAGBLOCK_STR_LEN = 1024; -//const size_t MAX_KEY_LEN = 32; -//const size_t MAX_VALUE_LEN = 256; -//const char * CHECKSUM_SEPARATOR = "*"; -//const char * FIELD_SEPARATOR = ","; -//const char * KEY_VALUE_SEPARATOR = ":"; -//const char * GROUP_SEPARATOR = "-"; -//const char * TAGBLOCK_SEPARATOR = "\\"; -//const char * AIVDM_START = "!"; -//const char * EMPTY_STRING = ""; - -//struct TAGBLOCK_FIELD -//{ -// char key[2]; -// const char* value; -//}; - -//#define TAGBLOCK_TIMESTAMP "tagblock_timestamp" -//#define TAGBLOCK_DESTINATION "tagblock_destination" -//#define TAGBLOCK_LINE_COUNT "tagblock_line_count" -//#define TAGBLOCK_RELATIVE_TIME "tagblock_relative_time" -//#define TAGBLOCK_STATION "tagblock_station" -//#define TAGBLOCK_TEXT "tagblock_text" -//#define TAGBLOCK_SENTENCE "tagblock_sentence" -//#define TAGBLOCK_GROUPSIZE "tagblock_groupsize" -//#define TAGBLOCK_ID "tagblock_id" -//#define CUSTOM_FIELD_PREFIX "tagblock_" -//#define TAGBLOCK_GROUP "g" - -//#define ERR_TAGBLOCK_DECODE "Unable to decode tagblock string" -//#define ERR_TAGBLOCK_TOO_LONG "Tagblock string too long" -//#define ERR_TOO_MANY_FIELDS "Too many fields" -//#define ERR_NMEA_TOO_LONG "NMEA string too long" -// -//typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; -//static KEY_MAP key_map[] = { -// {"c", TAGBLOCK_TIMESTAMP}, -// {"d", TAGBLOCK_DESTINATION}, -// {"n", TAGBLOCK_LINE_COUNT}, -// {"r", TAGBLOCK_RELATIVE_TIME}, -// {"s", TAGBLOCK_STATION}, -// {"t", TAGBLOCK_TEXT} -//}; -// -//static const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; -// -//const char* lookup_long_key(const char *short_key) -//{ -// for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) -// if (0 == strcmp(key_map[i].short_key, short_key)) -// return key_map[i].long_key; -// return NULL; -//} -// -//const char* lookup_short_key(const char* long_key) -//{ -// for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) -// if (0 == strcmp(key_map[i].long_key, long_key)) -// return key_map[i].short_key; -// return NULL; -//} -// -//size_t lookup_group_field_key(const char* long_key) -//{ -// for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) -// if (0 == strcmp(long_key, group_field_keys[i])) -// return i + 1; -// return 0; -//} -// -//void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) -//{ -// size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; -// -// if (0 == strncmp(CUSTOM_FIELD_PREFIX, long_key, prefix_len)) -// safe_strcpy(buffer, &long_key[prefix_len], buf_size); -// else -// safe_strcpy(buffer, long_key, buf_size); -//} -// -//void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) -//{ -// for (size_t i = 0; i < num_fields; i++) -// { -// fields[i].key[0] = '\0'; -// fields[i].value = NULL; -// } -//} - -///* -// * Copy a source str to a dest str -// * -// * Does not write a null terminator -// * dest_end should point to the last position in the dest string buffer. This method will -// * stops at the character immediately before this position -// * -// * returns a pointer the the position immediately after the last position written in dest -// */ -//char * unsafe_strcpy(char * dest, const char * dest_end, const char * src) -//{ -// while (dest < dest_end && *src) -// *dest++ = *src++; -// return dest; -//} - -int split_tagblock(char* message, const char** tagblock, const char** nmea) -{ - // Four cases for the given message string - - // starts with '!' - no tagblock, message is all nmea - // starts with '\!' - no tagblock, strip off '\', message is all nmea - // starts with '\[^!]' - is a tagblock , strip off leading '\', nmea is whatever comes after the next '\'. - // start with '[^\!]' - is a tagblock, nmea is whatever comes after the next '\'. - // starts with '[^!]' and there are no `\` delimiters - tagblock is empty, entire string in nmea - - char* ptr; - int tagblock_len = 0; - - ptr = message; - if (*ptr == *TAGBLOCK_SEPARATOR) - ptr ++; - if (*ptr == *AIVDM_START) - { - *nmea = ptr; - *tagblock = EMPTY_STRING; - } - else - { - *tagblock = ptr; - for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); - tagblock_len = ptr - *tagblock; - if (*ptr) - { - *ptr = '\0'; - *nmea = ptr + 1; - } - else - *nmea = EMPTY_STRING; - } - - return tagblock_len; -} - -int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str) -{ - char* end = buffer + buf_size - 1; - char* ptr = buffer; - - if (*tagblock_str && *nmea_str) - { - if (*tagblock_str != *TAGBLOCK_SEPARATOR) - ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); - ptr = unsafe_strcpy(ptr, end, tagblock_str); - - if (*nmea_str != *TAGBLOCK_SEPARATOR) - ptr = unsafe_strcpy(ptr, end, TAGBLOCK_SEPARATOR); - ptr = unsafe_strcpy(ptr, end, nmea_str); - } - else - { - ptr = unsafe_strcpy(ptr, end, tagblock_str); - ptr = unsafe_strcpy(ptr, end, nmea_str); - } - - if (ptr <= end) - { - *ptr = '\0'; - return ptr - buffer; - } - else - { - *end = '\0'; - return FAIL; - } - - return SUCCESS; -} - -// split a tagblock string with structure -// k:value,k:value -// k:value,k:value*cc -// \\k:value,k:value*cc\\other_stuff - -//int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) -//{ -// int idx = 0; -// char * ptr; -// char * field_save_ptr = NULL; -// char * field; -// -// char * key_value_save_ptr = NULL; -// -// // skip leading tagblock delimiter -// if (*tagblock_str == *TAGBLOCK_SEPARATOR) -// tagblock_str++; -// -// // seek forward to find either the checksum, the next tagblock separator, or the end of the string. -// // Terminate the string at the first delimiter found -// for (ptr = tagblock_str; *ptr != *TAGBLOCK_SEPARATOR && *ptr != *CHECKSUM_SEPARATOR && *ptr != '\0'; ptr++); -// *ptr = '\0'; -// -// // get the first comma delimited field -// field = strtok_r(tagblock_str, FIELD_SEPARATOR, &field_save_ptr); -// while (field && *field && idx < max_fields) -// { -// // for each field, split into key part and value part -// const char* key = strtok_r(field, KEY_VALUE_SEPARATOR, &key_value_save_ptr); -// const char* value = strtok_r(NULL, KEY_VALUE_SEPARATOR, &key_value_save_ptr); -// -// // if we don't have both key and value, then fail -// if (key && value) -// { -// safe_strcpy(fields[idx].key, key, ARRAY_LENGTH(fields[idx].key)); -// fields[idx].value = value; -// idx++; -// } -// else -// return FAIL; -// -// // advance to the next field -// field = strtok_r(NULL, FIELD_SEPARATOR, &field_save_ptr); -// } -// return idx; -//} - -//// TODO: need a return value that indicates the string is too long -//size_t join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) -//{ -// const char * end = tagblock_str + buf_size - 1; -// size_t last_field_idx = num_fields - 1; -// char * ptr = tagblock_str; -// char checksum[3]; -// -// for (size_t idx = 0; idx < num_fields; idx++) -// { -// ptr = unsafe_strcpy(ptr, end, fields[idx].key); -// ptr = unsafe_strcpy(ptr, end, KEY_VALUE_SEPARATOR); -// ptr = unsafe_strcpy(ptr, end, fields[idx].value); -// if (idx < last_field_idx) -// { -// ptr = unsafe_strcpy(ptr, end, FIELD_SEPARATOR); -// } -// } -// *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string -// -// checksum_str(checksum, tagblock_str, ARRAY_LENGTH(checksum)); -// -// ptr = unsafe_strcpy(ptr, end, CHECKSUM_SEPARATOR); -// ptr = unsafe_strcpy(ptr, end, checksum); -// -// if (ptr == end) -// return FAIL; -// -// *ptr = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string -// return ptr - tagblock_str; -//} - -//int decode_timestamp_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -//{ -// const char* key = lookup_long_key(field->key); -// char * end; -// -// long value = strtol(field->value, &end, 10); -// if (errno || *end) -// return FAIL; -// -// if (value > 40000000000) -// value = value / 1000; -// PyDict_SetItemString(dict, key, PyLong_FromLong(value)); -// return SUCCESS; -//} -// -//int decode_int_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -//{ -// const char* key = lookup_long_key(field->key); -// char* end; -// -// long value = strtol(field->value, &end, 10); -// if (errno || *end) -// return FAIL; -// -// PyDict_SetItemString(dict, key, PyLong_FromLong(value)); -// -// return SUCCESS; -//} -// -//int decode_text_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -//{ -// const char* key = lookup_long_key(field->key); -// PyDict_SetItemString(dict, key, PyUnicode_FromString(field->value)); -// -// return SUCCESS; -//} -// -//int decode_group_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -//{ -// size_t idx = 0; -// char * save_ptr = NULL; -// long values[ARRAY_LENGTH(group_field_keys)]; -// char buffer[MAX_VALUE_LEN]; -// -// if (safe_strcpy(buffer, field->value, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) -// return FAIL; -// -// char * f = strtok_r(buffer, GROUP_SEPARATOR, &save_ptr); -// while (f && idx < ARRAY_LENGTH(values)) -// { -// char* end; -// values[idx++] = strtol(f, &end, 10); -// if (errno || *end) -// return FAIL; -// -// f = strtok_r(NULL, GROUP_SEPARATOR, &save_ptr); -// } -// if (idx == ARRAY_LENGTH(values)) -// { -// for (idx = 0; idx < ARRAY_LENGTH(values); idx++) -// PyDict_SetItemString(dict, group_field_keys[idx], PyLong_FromLong(values[idx])); -// return SUCCESS; -// } -// -// return FAIL; -//} -// -//int decode_custom_field(struct TAGBLOCK_FIELD* field, PyObject* dict) -//{ -// char custom_key[MAX_KEY_LEN]; -// snprintf(custom_key, ARRAY_LENGTH(custom_key), "%s%s", CUSTOM_FIELD_PREFIX, field->key); -// PyDict_SetItemString(dict, custom_key, PyUnicode_FromString(field->value)); -// -// return SUCCESS; -//} - -//size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* group_fields) -//{ -// const char * end = buffer + buf_size - 1; -// char * ptr = buffer; -// -// for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) -// { -// // make sure that all values are non-empty -// if (group_fields[i].value == NULL) -// return 0; -// -// ptr = unsafe_strcpy(ptr, end, group_fields[i].value); -// if (i < ARRAY_LENGTH(group_field_keys) - 1) -// ptr = unsafe_strcpy(ptr, end, GROUP_SEPARATOR); -// } -// *ptr++ = '\0'; // very important! unsafe_strcpy does not add a null at the end of the string -// -// return ptr - buffer; -//} - -//static PyObject * -//tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// const char *param; -// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; -// int num_fields = 0; -// int status = SUCCESS; -// -// if (nargs != 1) -// return NULL; -// -// param = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// -// if (safe_strcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) -// { -// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); -// return NULL; -// } -// -// PyObject* dict = PyDict_New(); -// -// num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); -// if (num_fields < 0) -// status = FAIL; -// -// for (int i = 0; i < num_fields && status==SUCCESS; i++) -// { -// struct TAGBLOCK_FIELD* field = &fields[i]; -// const char* key = field->key; -// -// switch(key[0]) -// { -// case 'c': -// status = decode_timestamp_field(field, dict); -// break; -// case 'd': -// case 's': -// case 't': -// status = decode_text_field(field, dict); -// break; -// case 'g': -// status = decode_group_field(field, dict); -// break; -// case 'n': -// case 'r': -// status = decode_int_field(field, dict); -// break; -// default: -// status = decode_custom_field(field, dict); -// } -// } -// -// if (status == SUCCESS) -// return dict; -// else -// { -// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); -// return NULL; -// } -//} - -//int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) -//{ -// PyObject *key, *value; -// Py_ssize_t pos = 0; -// size_t field_idx = 0; -// struct TAGBLOCK_FIELD group_fields[ARRAY_LENGTH(group_field_keys)]; -// -// init_fields (group_fields, ARRAY_LENGTH(group_fields)); -// -// while (PyDict_Next(dict, &pos, &key, &value)) -// { -// if (field_idx == max_fields) -// return FAIL; // no more room in fields -// -// const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); -// const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); -// -// size_t group_ordinal = lookup_group_field_key(key_str); -// if (group_ordinal > 0) -// { -// group_fields[group_ordinal - 1].value = value_str; -// } -// else -// { -// const char* short_key = lookup_short_key (key_str); -// -// if (short_key) -// safe_strcpy(fields[field_idx].key, short_key, ARRAY_LENGTH(fields[field_idx].key)); -// else -// extract_custom_short_key(fields[field_idx].key, ARRAY_LENGTH(fields[field_idx].key), key_str); -// -// fields[field_idx].value = value_str; -// field_idx++; -// } -// } -// -// // encode group field and add it to the field list -// // check the return code to see if there is a complete set of group fields -// if (encode_group_fields(buffer, buf_size, group_fields)) -// { -// if (field_idx >= max_fields) -// return FAIL; // no more room to add another field -// -// safe_strcpy(fields[field_idx].key, TAGBLOCK_GROUP, ARRAY_LENGTH(fields[field_idx].key)); -// fields[field_idx].value = buffer; -// field_idx++; -// } -// -// return field_idx; -//} - - -//int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, -// struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) -//{ -// for (size_t update_idx = 0; update_idx < num_update_fields; update_idx++) -// { -// char* key = update_fields[update_idx].key; -// -// size_t fields_idx = 0; -// while (fields_idx < num_fields) -// { -// if (0 == strcmp(key, fields[fields_idx].key)) -// break; -// fields_idx++; -// } -// if (fields_idx == num_fields) -// { -// if (num_fields < max_fields) -// num_fields++; -// else -// return FAIL; -// } -// -// fields[fields_idx] = update_fields[update_idx]; -// } -// -// return num_fields; -//} - - - -//static PyObject * -//tagblock_encode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// -// PyObject *dict; -// -// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; -// -// init_fields (fields, ARRAY_LENGTH(fields)); -// -// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// char value_buffer [MAX_VALUE_LEN]; -// -// if (nargs != 1) -// return NULL; -// -// dict = args[0]; -// -// int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); -// if (num_fields < 0) -// { -// PyErr_SetString(PyExc_ValueError, ERR_TOO_MANY_FIELDS); -// return NULL; -// } -// -// join_fields(fields, num_fields, tagblock_str, ARRAY_LENGTH(tagblock_str)); -// -// return PyUnicode_FromString(tagblock_str); -//} - - -//static PyObject * -//tagblock_update(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// const char* str; -// PyObject* dict; -// char message[MAX_SENTENCE_LENGTH]; -// char updated_message[MAX_SENTENCE_LENGTH]; -// const char* tagblock_str; -// const char* nmea_str; -// -// -// if (nargs != 2) -// return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); -// -// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// dict = args[1]; -// -// if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); -// -// split_tagblock(message, &tagblock_str, &nmea_str); -// -// -// char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; -// if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) -// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); -// -// -// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; -// int num_fields = 0; -// num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); -// if (num_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); -// -// -// struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; -// int num_update_fields = 0; -// char value_buffer[MAX_VALUE_LEN]; -// num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), -// value_buffer, ARRAY_LENGTH(value_buffer)); -// if (num_update_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); -// -// -// num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); -// if (num_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); -// -// -// char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); -// -// int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); -// if (msg_len < 0) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); -// -// -// return PyUnicode_FromString(updated_message); -// -//} - -//static PyObject * -//tagblock_split(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// const char* str; -// char buffer[MAX_SENTENCE_LENGTH]; -// const char* tagblock_str; -// const char* nmea_str; -// -// if (nargs != 1) -// return PyErr_Format(PyExc_TypeError, "split expects only 1 argument"); -// -// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); -// -// split_tagblock(buffer, &tagblock_str, &nmea_str); -// -// return PyTuple_Pack(2, PyUnicode_FromString(tagblock_str), PyUnicode_FromString(nmea_str)); -//} - -//PyObject * -//tagblock_join(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// char buffer[MAX_SENTENCE_LENGTH]; -// const char* tagblock_str; -// const char* nmea_str; -// -// if (nargs != 2) -// return PyErr_Format(PyExc_TypeError, "join expects 2 arguments"); -// -// tagblock_str = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// nmea_str = PyUnicode_AsUTF8(PyObject_Str(args[1])); -// -// if (FAIL == join_tagblock(buffer, ARRAY_LENGTH(buffer), tagblock_str, nmea_str)) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); -// -// return PyUnicode_FromString(buffer); -//} - -static PyMethodDef tagblock_methods[] = { -// {"decode", (PyCFunction)(void(*)(void))tagblock_decode, METH_FASTCALL, -// "decode a tagblock string. Returns a dict"}, -// {"encode", (PyCFunction)(void(*)(void))tagblock_encode, METH_FASTCALL, -// "encode a tagblock string from a dict. Returns a string"}, -// {"update", (PyCFunction)(void(*)(void))tagblock_update, METH_FASTCALL, -// "update a tagblock string from a dict. Returns a string"}, -// {"split", (PyCFunction)(void(*)(void))tagblock_split, METH_FASTCALL, -// "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, -// {"join", (PyCFunction)(void(*)(void))tagblock_join, METH_FASTCALL, -// "Join a tagblock to an AIVDM message. Returns a string."}, - {NULL, NULL, 0, NULL} /* sentinel */ -}; - -static struct PyModuleDef tagblock_module = { - PyModuleDef_HEAD_INIT, - "_tagblock", - NULL, - -1, - tagblock_methods -}; - -PyMODINIT_FUNC -PyInit__tagblock(void) -{ - return PyModule_Create(&tagblock_module); -} \ No newline at end of file diff --git a/ais_tools/core/tagblock.h b/ais_tools/core/tagblock.h index 771e13a..ce4f186 100644 --- a/ais_tools/core/tagblock.h +++ b/ais_tools/core/tagblock.h @@ -1,6 +1,5 @@ /* tagblock definitions */ - #define TAGBLOCK_TIMESTAMP "tagblock_timestamp" #define TAGBLOCK_DESTINATION "tagblock_destination" #define TAGBLOCK_LINE_COUNT "tagblock_line_count" @@ -14,6 +13,8 @@ #define TAGBLOCK_GROUP "g" +/* tagblock_fields */ + struct TAGBLOCK_FIELD { char key[2]; @@ -24,18 +25,28 @@ extern const char* group_field_keys[3]; const char* lookup_long_key(const char *short_key); const char* lookup_short_key(const char* long_key); -size_t lookup_group_field_key(const char* long_key); +int lookup_group_field_key(const char* long_key); void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key); -void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields); +void init_fields(struct TAGBLOCK_FIELD* fields, size_t num_fields); -int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields); -int join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size); +int split_fields(struct TAGBLOCK_FIELD* fields, char* tagblock_str, int max_fields); +int join_fields(char* tagblock_str, size_t buf_size, const struct TAGBLOCK_FIELD* fields, size_t num_fields); int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields); -int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size); +/* tagblock_join */ +int join_tagblock(char* buffer, size_t buf_size, const char* tagblock_str, const char* nmea_str); + +/* tagblock_split */ +int split_tagblock(char* message, const char** tagblock, const char** nmea); +/* tagblock_encode */ +int encode_fields(struct TAGBLOCK_FIELD* fields, size_t max_fields, PyObject* dict, char* buffer, size_t buf_size); int encode_tagblock(char * dest, PyObject *dict, size_t dest_buf_size); + +/* tagblock_decode */ PyObject * decode_tagblock(char * tagblock_str); + +/* tagblock_update */ int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dict); diff --git a/ais_tools/core/tagblock_decode.c b/ais_tools/core/tagblock_decode.c index 6fb2490..ced4f4d 100644 --- a/ais_tools/core/tagblock_decode.c +++ b/ais_tools/core/tagblock_decode.c @@ -88,7 +88,7 @@ PyObject * decode_tagblock(char * tagblock_str) PyObject* dict = PyDict_New(); - num_fields = split_fields(tagblock_str, fields, ARRAY_LENGTH(fields)); + num_fields = split_fields(fields, tagblock_str, ARRAY_LENGTH(fields)); if (num_fields < 0) status = FAIL; @@ -123,28 +123,3 @@ PyObject * decode_tagblock(char * tagblock_str) else return NULL; } - -//PyObject * -//tagblock_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// const char *param; -// char tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// -// if (nargs != 1) -// return NULL; -// -// param = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// -// if (safe_strcpy(tagblock_str, param, ARRAY_LENGTH(tagblock_str)) >= ARRAY_LENGTH(tagblock_str)) -// { -// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); -// return NULL; -// } -// -// PyObject* dict = decode_tagblock(tagblock_str); -// -// if (!dict) -// PyErr_SetString(PyExc_ValueError, ERR_TAGBLOCK_DECODE); -// -// return dict; -//} \ No newline at end of file diff --git a/ais_tools/core/tagblock_encode.c b/ais_tools/core/tagblock_encode.c index d938d80..a580f22 100644 --- a/ais_tools/core/tagblock_encode.c +++ b/ais_tools/core/tagblock_encode.c @@ -26,7 +26,7 @@ size_t encode_group_fields(char* buffer, size_t buf_size, struct TAGBLOCK_FIELD* } -int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fields, char* buffer, size_t buf_size) +int encode_fields(struct TAGBLOCK_FIELD* fields, size_t max_fields, PyObject* dict, char* buffer, size_t buf_size) { PyObject *key, *value; Py_ssize_t pos = 0; @@ -43,10 +43,10 @@ int encode_fields(PyObject* dict, struct TAGBLOCK_FIELD* fields, size_t max_fiel const char* key_str = PyUnicode_AsUTF8(PyObject_Str(key)); const char* value_str = PyUnicode_AsUTF8(PyObject_Str(value)); - size_t group_ordinal = lookup_group_field_key(key_str); - if (group_ordinal > 0) + int group_field_idx = lookup_group_field_key(key_str); + if (group_field_idx >= 0) { - group_fields[group_ordinal - 1].value = value_str; + group_fields[group_field_idx].value = value_str; } else { @@ -84,11 +84,11 @@ int encode_tagblock(char * dest, PyObject *dict, size_t dest_buf_size) init_fields (fields, ARRAY_LENGTH(fields)); - int num_fields = encode_fields(dict, fields, ARRAY_LENGTH(fields), value_buffer, ARRAY_LENGTH(value_buffer)); + int num_fields = encode_fields(fields, ARRAY_LENGTH(fields), dict, value_buffer, ARRAY_LENGTH(value_buffer)); if (num_fields < 0) return FAIL_TOO_MANY_FIELDS; - int str_len = join_fields(fields, num_fields, dest, dest_buf_size); + int str_len = join_fields(dest, dest_buf_size, fields, num_fields); if (str_len < 0) return FAIL_STRING_TOO_LONG; return str_len; diff --git a/ais_tools/core/tagblock_fields.c b/ais_tools/core/tagblock_fields.c index f21be8c..0d5328c 100644 --- a/ais_tools/core/tagblock_fields.c +++ b/ais_tools/core/tagblock_fields.c @@ -3,9 +3,11 @@ #include #include #include "core.h" +#include "checksum.h" #include "tagblock.h" +/* static mapping of short (one-character) field names to long field names (to be used as dict keys) */ typedef struct {char short_key[2]; const char* long_key;} KEY_MAP; static KEY_MAP key_map[] = { {"c", TAGBLOCK_TIMESTAMP}, @@ -16,8 +18,18 @@ static KEY_MAP key_map[] = { {"t", TAGBLOCK_TEXT} }; +/* array of long field names (to be used as dict keys) corresponding to the 3 values in a + * group field. eg in the tagblock "g:1-2-3", TAGBLOCK_SENTENCE=1, TAGBLOCK_GROUPSIZE=2 and TAGBLOCK_ID=3 + */ const char* group_field_keys[3] = {TAGBLOCK_SENTENCE, TAGBLOCK_GROUPSIZE, TAGBLOCK_ID}; +/* + * find the long tagblock field key that corresponds to a given short key + * + * Returns a pointer to the long key found in KEYMAP if there is a matching short key, + * else returns NULL + * +*/ const char* lookup_long_key(const char *short_key) { for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) @@ -26,6 +38,13 @@ const char* lookup_long_key(const char *short_key) return NULL; } +/* + * find the xhort tagblock field key that corresponds to a given long key + * + * Returns a pointer to the short key found in KEYMAP if there is a matching long key, + * else returns NULL + * +*/ const char* lookup_short_key(const char* long_key) { for (size_t i = 0; i < ARRAY_LENGTH(key_map); i++) @@ -34,14 +53,32 @@ const char* lookup_short_key(const char* long_key) return NULL; } -size_t lookup_group_field_key(const char* long_key) +/* + * find the group key index in the range [0,2] that corresponds to the given long key + * + * If the given key matches a key in group_field_keys, returns the 0-based index + * If not match is found, returns FAIL (-1) + * +*/ +int lookup_group_field_key(const char* long_key) { for (size_t i = 0; i < ARRAY_LENGTH(group_field_keys); i++) if (0 == strcmp(long_key, group_field_keys[i])) - return i + 1; - return 0; + return i; + return FAIL; } +/* + * Create a short key from a long key that begins the with custom field prefix. This will be + * everything in the source key that comes after the end of the prefix. + * + * The new key is written into the given buffer. If the buffer is not long enough, the + * copied value is truncated, so the destination buffer will always end up with a + * null terminated string in it + * + * If the given key does not match the custom field prefix, then the entirety of of the + * given key is copied to the destination buffer. + */ void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_key) { size_t prefix_len = ARRAY_LENGTH(CUSTOM_FIELD_PREFIX) - 1; @@ -52,7 +89,11 @@ void extract_custom_short_key(char* buffer, size_t buf_size, const char* long_ke safe_strcpy(buffer, long_key, buf_size); } -void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) +/* + * Initialize an array of TAGBLOCK_FIELD to have empty, null terminated strings for the key + * field and NULL pointers for the value field + */ +void init_fields(struct TAGBLOCK_FIELD* fields, size_t num_fields) { for (size_t i = 0; i < num_fields; i++) { @@ -61,12 +102,25 @@ void init_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields) } } -// split a tagblock string with structure -// k:value,k:value -// k:value,k:value*cc -// \\k:value,k:value*cc\\other_stuff -int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fields) +/* + * Split a tagblok string into key/value pairs + * + * expects a tagblock_str with structure like + * k:value,k:value + * k:value,k:value*cc + * \\k:value,k:value*cc\\other_stuff + * + * NB THIS FUNCTION WILL MODIFY tagblock_str + * + * Uses strtok to cut up the source string into small strings. The resulting TAGBLOCK_FIELD + * objects will contain pointers to the resulting substrings stored in the original string + * So you should not use or modify the tagblock_str after calling this function + * + * Returns the number of elements written to the array of TAGBLOCK_FIELD + * If there are too many fields to fit in the array, returns FAIL (-1) + */ +int split_fields(struct TAGBLOCK_FIELD* fields, char* tagblock_str, int max_fields) { int idx = 0; char * ptr; @@ -108,8 +162,16 @@ int split_fields(char* tagblock_str, struct TAGBLOCK_FIELD* fields, int max_fiel return idx; } - -int join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* tagblock_str, size_t buf_size) +/* + * Join an array of fields into a tagblock string + * + * Writes a formatted tagblock string into the provided buffer using all the key/value pairs + * in the given array of TAGBLOCK_FIELD. + * + * Returns the length of the resulting tagblock string, excluding the NUL + * If the buffer is not big enough to contain the string, returns FAIL (-1) + */ +int join_fields(char* tagblock_str, size_t buf_size, const struct TAGBLOCK_FIELD* fields, size_t num_fields) { const char * end = tagblock_str + buf_size - 1; size_t last_field_idx = num_fields - 1; @@ -140,6 +202,16 @@ int join_fields(const struct TAGBLOCK_FIELD* fields, size_t num_fields, char* ta return ptr - tagblock_str; } +/* + * Merge one array of TAGBLOCK_FIELD into another + * + * Read fields in `update_fields` and overwrite or append to `fields` + * Overwrite if the keys matchm else append + * + * Return the new length of the destination array + * Returns FAIL (-1) if the destination array is not large enough to hold the combined set of fields + */ + int merge_fields( struct TAGBLOCK_FIELD* fields, size_t num_fields, size_t max_fields, struct TAGBLOCK_FIELD* update_fields, size_t num_update_fields) { diff --git a/ais_tools/core/join_tagblock.c b/ais_tools/core/tagblock_join.c similarity index 100% rename from ais_tools/core/join_tagblock.c rename to ais_tools/core/tagblock_join.c diff --git a/ais_tools/core/split_tagblock.c b/ais_tools/core/tagblock_split.c similarity index 100% rename from ais_tools/core/split_tagblock.c rename to ais_tools/core/tagblock_split.c diff --git a/ais_tools/core/tagblock_update.c b/ais_tools/core/tagblock_update.c index 9378753..3bce9cf 100644 --- a/ais_tools/core/tagblock_update.c +++ b/ais_tools/core/tagblock_update.c @@ -18,14 +18,14 @@ int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dic if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) return FAIL_STRING_TOO_LONG; - num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); + num_fields = split_fields(fields, tagblock_buffer, ARRAY_LENGTH(fields)); if (num_fields < 0) return FAIL_TOO_MANY_FIELDS; struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; int num_update_fields = 0; char value_buffer[MAX_VALUE_LEN]; - num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), + num_update_fields = encode_fields(update_fields, ARRAY_LENGTH(update_fields), dict, value_buffer, ARRAY_LENGTH(value_buffer)); if (num_update_fields < 0) return FAIL_TOO_MANY_FIELDS; @@ -35,7 +35,7 @@ int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dic return FAIL_TOO_MANY_FIELDS; char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; - join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); + join_fields(updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str), fields, num_fields); int msg_len = join_tagblock(dest, dest_size, updated_tagblock_str, nmea_str); if (msg_len < 0) @@ -44,78 +44,3 @@ int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dic return msg_len; } - -//// TODO: Move to methods.c -//static PyObject * -//method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -//{ -// const char* str; -// PyObject* dict; -// char message[MAX_SENTENCE_LENGTH]; -// char updated_message[MAX_SENTENCE_LENGTH]; -//// const char* tagblock_str; -//// const char* nmea_str; -// -// -// if (nargs != 2) -// return PyErr_Format(PyExc_TypeError, "update expects 2 arguments"); -// -// str = PyUnicode_AsUTF8(PyObject_Str(args[0])); -// dict = args[1]; -// -// if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); -// -// int message_len = update_tagblock(updated_message); -// -// if (message_len < 0) -// switch(message_len) -// { -// case FAIL_STRING_TOO_LONG: -// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); -// case FAIL_TOO_MANY_FIELDS: -// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); -// default: -// return PyErr_Format(PyExc_ValueError, ERR_UNKNOWN); -// } -// -// return PyUnicode_FromString(updated_message); -// -//} -// split_tagblock(message, &tagblock_str, &nmea_str); - - -// char tagblock_buffer[MAX_TAGBLOCK_STR_LEN]; -// if (safe_strcpy(tagblock_buffer, tagblock_str, ARRAY_LENGTH(tagblock_buffer)) >= ARRAY_LENGTH(tagblock_buffer)) -// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_TOO_LONG); - - -// struct TAGBLOCK_FIELD fields[MAX_TAGBLOCK_FIELDS]; -// int num_fields = 0; -// num_fields = split_fields(tagblock_buffer, fields, ARRAY_LENGTH(fields)); -// if (num_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TAGBLOCK_DECODE); - - -// struct TAGBLOCK_FIELD update_fields[MAX_TAGBLOCK_FIELDS]; -// int num_update_fields = 0; -// char value_buffer[MAX_VALUE_LEN]; -// num_update_fields = encode_fields(dict, update_fields, ARRAY_LENGTH(update_fields), -// value_buffer, ARRAY_LENGTH(value_buffer)); -// if (num_update_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - -// num_fields = merge_fields(fields, num_fields, ARRAY_LENGTH(fields), update_fields, num_update_fields); -// if (num_fields < 0) -// return PyErr_Format(PyExc_ValueError, ERR_TOO_MANY_FIELDS); - - -// char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; -// join_fields(fields, num_fields, updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str)); -// -// int msg_len = join_tagblock(updated_message, ARRAY_LENGTH(updated_message), updated_tagblock_str, nmea_str); -// if (msg_len < 0) -// return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); - - diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 9f13c45..9a0c6ee 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -2,9 +2,7 @@ from datetime import timezone from ais import DecodeError -# from ais_tools.core import checksum_str from ais_tools.core import is_checksum_valid -from ais_tools import _tagblock from ais_tools import core diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index d48cda3..8240f22 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -3,7 +3,6 @@ from ais_tools import tagblock from ais_tools.tagblock import DecodeError -from ais_tools import _tagblock from ais_tools import core @pytest.mark.parametrize("line,expected", [ From 1ec6e4d8e99016130a0606db6ece8213c3b1b9ae Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 28 Feb 2023 17:41:34 -0500 Subject: [PATCH 12/16] bugfix for segfault in split_tagblock --- ais_tools/core/tagblock_split.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ais_tools/core/tagblock_split.c b/ais_tools/core/tagblock_split.c index ffda255..3948d20 100644 --- a/ais_tools/core/tagblock_split.c +++ b/ais_tools/core/tagblock_split.c @@ -37,7 +37,7 @@ int split_tagblock(char* message, const char** tagblock, const char** nmea) else { *tagblock = ptr; - for (ptr = &message[1]; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); + for (; *ptr != '\0' && *ptr != *TAGBLOCK_SEPARATOR; ptr++); tagblock_len = ptr - *tagblock; if (*ptr) { From f55b662676a8c938c996bdb1a46bb8726d46f5c3 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 28 Feb 2023 20:00:06 -0500 Subject: [PATCH 13/16] additional unit tests for coverage in failure cases --- ais_tools/core/tagblock_update.c | 6 ++++-- ais_tools/tagblock.py | 2 +- tests/test_nmea.py | 7 +++++++ tests/test_tagblock.py | 27 ++++++++++++++++++++++----- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/ais_tools/core/tagblock_update.c b/ais_tools/core/tagblock_update.c index 3bce9cf..9cb0443 100644 --- a/ais_tools/core/tagblock_update.c +++ b/ais_tools/core/tagblock_update.c @@ -35,9 +35,11 @@ int update_tagblock(char * dest, size_t dest_size, char* message, PyObject * dic return FAIL_TOO_MANY_FIELDS; char updated_tagblock_str[MAX_TAGBLOCK_STR_LEN]; - join_fields(updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str), fields, num_fields); + int msg_len = join_fields(updated_tagblock_str, ARRAY_LENGTH(updated_tagblock_str), fields, num_fields); + if (msg_len < 0) + return FAIL_STRING_TOO_LONG; - int msg_len = join_tagblock(dest, dest_size, updated_tagblock_str, nmea_str); + msg_len = join_tagblock(dest, dest_size, updated_tagblock_str, nmea_str); if (msg_len < 0) return FAIL_STRING_TOO_LONG; diff --git a/ais_tools/tagblock.py b/ais_tools/tagblock.py index 9a0c6ee..44f51c7 100644 --- a/ais_tools/tagblock.py +++ b/ais_tools/tagblock.py @@ -103,6 +103,6 @@ def update_tagblock(nmea, **kwargs): def safe_update_tagblock(nmea, **kwargs): try: nmea = core.update_tagblock(nmea, kwargs) - except DecodeError: + except ValueError: pass return nmea diff --git a/tests/test_nmea.py b/tests/test_nmea.py index a1e5746..7abb14b 100644 --- a/tests/test_nmea.py +++ b/tests/test_nmea.py @@ -46,6 +46,13 @@ def test_expand_nmea_fail(nmea): with pytest.raises(DecodeError): tagblock, body, pad = expand_nmea(nmea, validate_checksum=False) +@pytest.mark.parametrize("nmea", [ + "\\!AIVDM,1,1,,A,BADCHECKSUM,0*00", +]) +def test_expand_nmea_validate_fail(nmea): + with pytest.raises(DecodeError): + tagblock, body, pad = expand_nmea(nmea, validate_checksum=True) + @pytest.mark.parametrize("nmea", [ (['!AIVDM,2,1,7,A,@*00']), diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index 8240f22..fb3d189 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -80,6 +80,14 @@ def test_encode_tagblock(fields, expected): assert expected == tagblock.encode_tagblock(**fields) +@pytest.mark.parametrize("fields", [ + ({'tagblock_text': '0' * 1024,}) +]) +def test_encode_tagblock_fail(fields): + with pytest.raises(DecodeError): + tagblock.encode_tagblock(**fields) + + @pytest.mark.parametrize("tagblock_str,expected", [ ('*00', {}), ('z:123*70', {'tagblock_z': '123'}), @@ -126,6 +134,7 @@ def test_decode_tagblock_invalid(tagblock_str): @pytest.mark.parametrize("tagblock_str,new_fields,expected", [ ('!AIVDM', {'q': 123}, '\\q:123*7B\\!AIVDM'), ('\\!AIVDM', {'q': 123}, '\\q:123*7B\\!AIVDM'), + ('\\s:00*00\\!AIVDM', {}, '\\s:00*49\\!AIVDM'), ('\\s:00*00\\!AIVDM', {'tagblock_station': 99}, '\\s:99*49\\!AIVDM'), ('\\s:00*00\\!AIVDM\\s:00*00\\!AIVDM', {'tagblock_station': 99}, '\\s:99*49\\!AIVDM\\s:00*00\\!AIVDM'), ('\\c:123456789*68\\!AIVDM', {}, '\\c:123456789*68\\!AIVDM'), @@ -136,6 +145,14 @@ def test_update_tagblock(tagblock_str, new_fields, expected): assert expected == tagblock.update_tagblock(tagblock_str, **new_fields) +@pytest.mark.parametrize("tagblock_str,new_fields,expected", [ + ('!AIVDM', {'q': 123}, '\\q:123*7B\\!AIVDM'), + ('\\s:00*49\\!AIVDM', {}, '\\s:00*49\\!AIVDM'), + ('\\s:00*49\\!AIVDM', {'tagblock_text' : '0' * 1024}, '\\s:00*49\\!AIVDM'), +]) +def test_safe_update_tagblock(tagblock_str, new_fields, expected): + assert expected == tagblock.safe_update_tagblock(tagblock_str, **new_fields) + @pytest.mark.parametrize("tagblock_str,expected", [ ('', {}), @@ -183,8 +200,8 @@ def test_encode_decode(fields): assert core.decode_tagblock(core.encode_tagblock(fields)) == fields -def test_update(): - tagblock_str="\\z:1*71\\" - fields = {'tagblock_text': 'ABC'} - expected = "z:1,t:ABC*53" - assert core.update_tagblock(tagblock_str, fields) == expected +# def test_update(): +# tagblock_str="\\z:1*71\\" +# fields = {'tagblock_text': 'ABC'} +# expected = "z:1,t:ABC*53" +# assert core.update_tagblock(tagblock_str, fields) == expected From a113f9cdc6d7907125eaa0c85c6bb841ba86967f Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Tue, 28 Feb 2023 20:21:40 -0500 Subject: [PATCH 14/16] one more unit test --- tests/test_nmea.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_nmea.py b/tests/test_nmea.py index 7abb14b..c3eebf6 100644 --- a/tests/test_nmea.py +++ b/tests/test_nmea.py @@ -48,6 +48,7 @@ def test_expand_nmea_fail(nmea): @pytest.mark.parametrize("nmea", [ "\\!AIVDM,1,1,,A,BADCHECKSUM,0*00", + "\\s:00*49\\!AIVDM,1,1,,A,BADCHECKSUM,0*00", ]) def test_expand_nmea_validate_fail(nmea): with pytest.raises(DecodeError): From 90378f4bbc984fd81bf862600f4cac79f5c71bc5 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Fri, 3 Mar 2023 16:29:19 -0500 Subject: [PATCH 15/16] refactor method args --- ais_tools/core/methods.c | 39 +++++++++++++---------- ais_tools/core/methods.h | 17 +++++----- ais_tools/core/module.c | 67 ++++++++++++++++++++++++++++++---------- tests/test_tagblock.py | 1 - 4 files changed, 81 insertions(+), 43 deletions(-) diff --git a/ais_tools/core/methods.c b/ais_tools/core/methods.c index 7c58b22..01829bd 100644 --- a/ais_tools/core/methods.c +++ b/ais_tools/core/methods.c @@ -6,43 +6,44 @@ #include "tagblock.h" PyObject * -method_compute_checksum(PyObject *module, PyObject *args) +method_compute_checksum(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { const char *str; - int c; - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - c = checksum(str); - return PyLong_FromLong(c); + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "compute_checksum expects 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); + return PyLong_FromLong(checksum(str)); } PyObject * -method_compute_checksum_str(PyObject *module, PyObject *args) +method_compute_checksum_str(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { const char *str; char c_str[3]; - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "checksum_str expects 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); checksum_str(c_str, str, ARRAY_LENGTH(c_str)); return PyUnicode_FromString(c_str); } PyObject * -method_is_checksum_valid(PyObject *module, PyObject *args) +method_is_checksum_valid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { - char *str; + const char *str; char buffer[MAX_SENTENCE_LENGTH]; - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; + if (nargs != 1) + return PyErr_Format(PyExc_TypeError, "checksum_str expects 1 argument"); + + str = PyUnicode_AsUTF8(PyObject_Str(args[0])); if (safe_strcpy(buffer, str, ARRAY_LENGTH(buffer)) >= ARRAY_LENGTH(buffer)) - { - PyErr_SetString(PyExc_ValueError, "String too long"); - return NULL; - } + return PyErr_Format(PyExc_ValueError, "String too long"); return is_checksum_valid(buffer) ? Py_True: Py_False; } @@ -121,6 +122,8 @@ method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs return PyErr_Format(PyExc_TypeError, "encode expects only 1 argument"); dict = args[0]; + if (!PyDict_Check(dict)) + return PyErr_Format(PyExc_ValueError, "encode requires a dict object as the argument"); result = encode_tagblock(tagblock_str, dict, ARRAY_LENGTH(tagblock_str)); @@ -149,6 +152,8 @@ method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t narg str = PyUnicode_AsUTF8(PyObject_Str(args[0])); dict = args[1]; + if (!PyDict_Check(dict)) + return PyErr_Format(PyExc_ValueError, "The second argument to update must be a dict"); if (safe_strcpy(message, str, ARRAY_LENGTH(message)) >= ARRAY_LENGTH(message)) return PyErr_Format(PyExc_ValueError, ERR_NMEA_TOO_LONG); diff --git a/ais_tools/core/methods.h b/ais_tools/core/methods.h index 0894c82..982c447 100644 --- a/ais_tools/core/methods.h +++ b/ais_tools/core/methods.h @@ -1,9 +1,10 @@ // Module methods -PyObject * method_compute_checksum(PyObject *module, PyObject *args); -PyObject * method_compute_checksum_str(PyObject *module, PyObject *args); -PyObject * method_is_checksum_valid(PyObject *module, PyObject *args); -PyObject * method_join_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_split_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_decode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_encode_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); -PyObject * method_update_tagblock(PyObject *module, PyObject *const *args, Py_ssize_t nargs); \ No newline at end of file + +PyObject * method_compute_checksum (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_compute_checksum_str(PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_is_checksum_valid (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_join_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_split_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_decode_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_encode_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); +PyObject * method_update_tagblock (PyObject *module, PyObject *const *args, Py_ssize_t nargs); \ No newline at end of file diff --git a/ais_tools/core/module.c b/ais_tools/core/module.c index c97e293..54d19f7 100644 --- a/ais_tools/core/module.c +++ b/ais_tools/core/module.c @@ -4,29 +4,62 @@ #include "methods.h" static PyMethodDef core_methods[] = { - {"checksum", (PyCFunction)(void(*)(void))method_compute_checksum, METH_VARARGS, - "Compute checksum of a string. returns an integer value"}, - {"checksum_str", (PyCFunction)(void(*)(void))method_compute_checksum_str, METH_VARARGS, - "Compute checksum of a string. returns a 2-character hex string"}, - {"is_checksum_valid", (PyCFunction)(void(*)(void))method_is_checksum_valid, METH_VARARGS, - "Returns True if the given string is terminated with a valid checksum, else False"}, - {"decode_tagblock", (PyCFunction)(void(*)(void))method_decode_tagblock, METH_FASTCALL, - "decode a tagblock string. Returns a dict"}, - {"encode_tagblock", (PyCFunction)(void(*)(void))method_encode_tagblock, METH_FASTCALL, - "encode a tagblock string from a dict. Returns a string"}, - {"update_tagblock", (PyCFunction)(void(*)(void))method_update_tagblock, METH_FASTCALL, - "update a tagblock string from a dict. Returns a string"}, - {"split_tagblock", (PyCFunction)(void(*)(void))method_split_tagblock, METH_FASTCALL, - "Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings"}, - {"join_tagblock", (PyCFunction)(void(*)(void))method_join_tagblock, METH_FASTCALL, - "Join a tagblock to an AIVDM message. Returns a string."}, + { + "checksum", + (PyCFunction)(void(*)(void))method_compute_checksum, + METH_FASTCALL, + PyDoc_STR("Compute checksum of a string. Returns an integer value. The checksum for an empty string is 0") + }, + { + "checksum_str", + (PyCFunction)(void(*)(void))method_compute_checksum_str, + METH_FASTCALL, + PyDoc_STR("Compute checksum of a string. Returns a 2-character hex string") + }, + { + "is_checksum_valid", + (PyCFunction)(void(*)(void))method_is_checksum_valid, + METH_FASTCALL, + PyDoc_STR("Returns True if the given string is terminated with a valid checksum, else False") + }, + { + "decode_tagblock", + (PyCFunction)(void(*)(void))method_decode_tagblock, + METH_FASTCALL, + PyDoc_STR("Decode a tagblock string. Returns a dict") + }, + { + "encode_tagblock", + (PyCFunction)(void(*)(void))method_encode_tagblock, + METH_FASTCALL, + PyDoc_STR("Encode a tagblock string from a dict. Takes a dict and returns a string") + }, + { + "update_tagblock", + (PyCFunction)(void(*)(void))method_update_tagblock, + METH_FASTCALL, + PyDoc_STR("Update a tagblock string from a dict. Takes a string and a dict and returns a string") + }, + { + "split_tagblock", + (PyCFunction)(void(*)(void))method_split_tagblock, + METH_FASTCALL, + PyDoc_STR("Split off the tagblock portion of a longer nmea string. Returns a tuple containing two strings " + "(tagblock, nmea)") + }, + { + "join_tagblock", + (PyCFunction)(void(*)(void))method_join_tagblock, + METH_FASTCALL, + PyDoc_STR("Join a tagblock to an AIVDM message. Takes two strings and returns a string.") + }, {NULL, NULL, 0, NULL} /* sentinel */ }; static struct PyModuleDef core_module = { PyModuleDef_HEAD_INIT, "core", - NULL, + PyDoc_STR("AIS Tools core methods implemented in C. Supports tagblock manipulation and computing checksums"), -1, core_methods }; diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index fb3d189..c8bfe6e 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -168,7 +168,6 @@ def test_tagblock_decode(tagblock_str, expected): @pytest.mark.parametrize("fields,expected", [ - (None,'*00'), ({},'*00'), ({'a': 1}, 'a:1*6A'), ({'tagblock_a': 1}, 'a:1*6A'), From 209d09a2a425529f358e32b5d0b3070314db3502 Mon Sep 17 00:00:00 2001 From: pwoods25443 Date: Fri, 2 Jun 2023 10:12:20 -0400 Subject: [PATCH 16/16] Extra unit test for new edge case --- tests/test_tagblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_tagblock.py b/tests/test_tagblock.py index c8bfe6e..001fd8f 100644 --- a/tests/test_tagblock.py +++ b/tests/test_tagblock.py @@ -161,7 +161,8 @@ def test_safe_update_tagblock(tagblock_str, new_fields, expected): ('d:dest*00', {'tagblock_destination': 'dest'}), ('n:42', {'tagblock_line_count': 42}), ("c:123456789", {'tagblock_timestamp': 123456789}), - ('g:1-2-3', {'tagblock_sentence': 1, 'tagblock_groupsize': 2, 'tagblock_id': 3}) + ('g:1-2-3', {'tagblock_sentence': 1, 'tagblock_groupsize': 2, 'tagblock_id': 3}), + ('s:rMT5858,*0E', {'tagblock_station': 'rMT5858'}) ]) def test_tagblock_decode(tagblock_str, expected): assert core.decode_tagblock(tagblock_str) == expected