Skip to content

Conversation

@denniszollo
Copy link
Contributor

This PR adds a new type of callback to the c SBP Library to handle the entire SBP frame. It leaves the external API unchanged except for these additions.

This new frame callback feature will allow for easier raw logging in applications including Piksi Multi's standalone logger. It also allows adding a frame callback of type SBP_MSG_ALL to fire a callback for any msg_type. A convenient helper function is added to the API for registering a callback for every msg_type. Implementation details were discussed internally quite a bit and I think this represents the consensus.

I've added unit tests for the new functionality and some missing unit tests like testing the max_size of SBP messages.

Copy link
Contributor

@benjaminaltieri benjaminaltieri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking great so far, really like the registration updates. lots of idea to explore here, I'm mostly looking to reduce the doubling up I'm seeing between frame and message buffers during sbp_process and the double list traversal going on in sbp_process_payload/sbp_process_frame

u16 frame_len;
u8 n_read;
u8 msg_buff[256];
u8 frame_buff[256 + 8];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we define SBP_MAX_FRAME_LEN with this value and use it? also, the above line could use SBP_MAX_PAYLOAD_LEN

* This enum is stored on each sbp_msg_callback_node struct to identify how
* to cast the callback function pointer stored within.
*/
enum sbp_cb_type{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space

c/src/sbp.c Outdated
if (s->msg_len - s->n_read <= 0) {
s->n_read = 0;
s->state = GET_CRC;
for (int i = 0; i < s->msg_len; i++)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at the code now, it seems as though msg_buff and frame_buff are redundant. reading directly into msg_type, sender_id, etc. seems like an optimization used since we weren't trying to record the whole frame. since it's only 8 extra bytes and we could just as easily extract those values from frame_buff at the appropriate time, I would propose simply removing msg_buff altogether and returning the usual msg[] pointer as an offset from the beginning of frame_buff

c/src/sbp.c Outdated
s->state = GET_MSG;
s->frame_buff[5] = s->msg_len;
/* length of frame is msg_len + header_len + 2 for CRC */
s->frame_len = s->msg_len + SBP_HEADER_LEN + SBP_CRC_LEN;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frame_len should be incremented on each read (assuming implementation of previous comment) and validated against this formula. this will allow early exits to reflect the actual bytes held in frame_buff when we add frame error handlers

c/src/sbp.c Outdated
* In future, it may be wise to add new return codes for the truth
* table of both process function return codes. For now, we return only
* sbp_process_payload's return code for backwards compatibility. */
sbp_process_frame(s, s->sender_id, s->msg_type, s->msg_len, s->msg_buff,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose we allow sbp_process_frame to take an argument such that it calls all or a subset of callback types. this would allow sbp_process_payload to be a specialized call to sbp_process_frame where only SBP_TYPE_CALLBACK nodes are executed (similar to how you are extending the callback register functions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would reduce code duplication between the two methods as well as avoid having to traverse the list twice.

void logging_reset(void)
{
n_callbacks_logged = 0;
n_frame_callbacks_logged = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the last_* variables get reset too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that would be better. updated.

tcase_add_test(tc_core, test_callbacks);
tcase_add_test(tc_core, test_sbp_send_message);
tcase_add_test(tc_core, test_sbp_process);
tcase_add_test(tc_core, test_sbp_frame);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding tests too! this is great, much good karma to you 🕉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ ❤️ ❤️ ❤️ ❤️ ❤️

*/
enum sbp_cb_type{
SBP_TYPE_CALLBACK,
SBP_FRAME_CALLBACK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in true C style, we could turn this into a bit field to allow the sbp_process_frame function to be super flexible, just throw in a bitmask to select the callback styles you'd like to service. as easy as:

    if ((node->cb_type & callback_select_mask) &&
        ((node->msg_type == msg_type) || (node->msg_type == SBP_MSG_ALL))) {

c/src/sbp.c Outdated
s->n_read = 0;
/* set frame_buff to 0 after each preamble.
* Note, library functions are not used to avoid more dependencies. */
for (int i = 0; i < 255; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

255 doesn't seem right here. since it's a static buffer we can use sizeof(s->frame_buff)

@benjaminaltieri
Copy link
Contributor

Put these changes together to help illustrate the ideas: https://github.com/swift-nav/libsbp/tree/benaltieri/frame_callback_ideas

@denniszollo
Copy link
Contributor Author

Ready for review.

Copy link
Contributor

@silverjam silverjam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly doc suggestions and pontificating about big vs little endian. Looking much better though.

c/src/sbp.c Outdated
if (expected_frame_len != s->frame_len) {
return SBP_READ_TOO_MANY_ERROR;
}
/* Swap bytes to little endian. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR Leaving the rest of this here for posterity, but I think we should remove these comments, since it's not correct. To fully handle endianness issues we need to rewrite some of the generated code to handle it too, so not in the purview of this PR.

---8<---

Is this comment correct? The value is stored in little endian (if the machine it's generated on is little endian), we're not really swapping anything.

Using *((u16) &s->frame_buff[SBP_FRAME_OFFSET_CRC(s->msg_len)]) will work the same. E.g. https://godbolt.org/z/MZ1MCX -- Not saying this shouldn't be here, but we need to do the byte swap when storing the value on big endian for this to make sense.

That is, when we build an SBP packet, we should have something like:

#define BUF_TO_U16(buf, offset) *((u16*)&buf[0])

#ifdef __BIG_ENDIAN__
#define HTON16(crc) ((crc << 8) | crc)
#else // __LITTLE_ENDIAN__
#define HTON16(crc) (crc)
#endif

Then when generating the CRC for an outgoging SBP packet:

s->crc = HTON16(crc);

(Note our "network" byte order is little endian, which is opposite of the usual meaning of "network byte order").

--->8---

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per this good feedback I removed the bogus comment and created a helper function to cast to u8s to a u16. I would love to generate real parsing functions that are platform independent for each field for SBP with the generator and hope to accomplish that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big endian seems pretty uncommon lately, so I think it's a "nice to have", we have definitely seen (1 or 2) customers with this issue, but it shouldn't be high priority.

tcase_add_test(tc_core, test_callbacks);
tcase_add_test(tc_core, test_sbp_send_message);
tcase_add_test(tc_core, test_sbp_process);
tcase_add_test(tc_core, test_sbp_frame);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ ❤️ ❤️ ❤️ ❤️ ❤️

@denniszollo
Copy link
Contributor Author

Doxygen looks pretty good:
image
image

* to cast the callback function pointer stored within.
*/
enum sbp_cb_type {
SBP_TYPE_CALLBACK = 0,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am starting to think this should be SBP_PAYLOAD_CALLBACK instead of SBP_TYPE_CALLBACK. I had named it type callback when it was impossible to register a frame callback on a particular message type.

@denniszollo
Copy link
Contributor Author

I've made a few changes. I added a helper function to go from u8[] to u16 and remove confusing endianness comments, renamed SBP_TYPE_CALLBACK to SBP_PAYLOAD_CB, and updated the docs a lot per Jason's suggestions and thoughts. If approved I will plan to rebase and merge as is with the relatively fine-grained commit history.

If requested, I am willing and able to squash into three logical commits.

  1. code changes for frame_callback
  2. comment and doc updates for frame callback
  3. unit test additions for frame_callback

@silverjam
Copy link
Contributor

@denniszollo I think some squashing is in order, whatever seems appropriate to you is fine.

@denniszollo
Copy link
Contributor Author

After working with this new API quite a bit on Friday, it became clear to me that the new return code (SBP_READ_TOO_MANY_ERROR) could result in a change in behavior depending on how the *read function pointer was implemented, as such Ben and I decided to remove it. I am going to merge the newly squashed version of this with only this one change.

@benjaminaltieri
Copy link
Contributor

@denniszollo squashing helped, my only nit would be that the macro commit should be before the commit that uses the macros implemented there. one example is SBP_MAX_FRAME_LEN use in sbp_state_t my suggestion would be to squash that or just reorder. The reason being is that if someone is looking at the full code in that single functional commit, they will not see the definition of those macros

        * classify callbacks into SBP_FRAME_CALLBACK(new) and SBP_PAYLOAD_CALLBACK (original)
        * add API for an ALL message cb to c lib
        * add macro for frame length calculation
        * c_frame_callback: add sbp_u8_array_to_u16 for sbp_process.
   * c_frame_callback: Add unit tests for all_msg callback api
   * c_frame_callback: Add unit test for max length (255) to c lib
@denniszollo denniszollo merged commit 39c6625 into swift-nav:master Sep 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants