Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: 0-length strings are valid on the protocol #1

Closed
wants to merge 2 commits into from

Conversation

depili
Copy link

@depili depili commented Jan 26, 2022

OSC v1.0 spesification doesn't demand that the strings aren't empty, thus a 0-lenght string is completely valid argument and commonly used in real world.

OSC v1.0 spesification doesn't demand that the strings aren't empty,
thus a 0-lenght string is completely valid argument and commonly used
in real world.
@depili
Copy link
Author

depili commented Jan 26, 2022

Upon further testing this still seems to have problems on stopping the decoding on the first encountered empty string

The padding calculation is bit iffy...
@depili
Copy link
Author

depili commented Jan 26, 2022

The padding calculation seems to cause some errors with 0 length strings. For now I have dealt with this as a special case but some care should be applied to see if this is a real error in the padding calculations that applies to all strings

@chabad360
Copy link
Owner

chabad360 commented Jan 27, 2022

Regarding the OSC Spec (emphasis mine):

OSC-string
A sequence of non-null ASCII characters followed by a null, followed by 0-3 additional null characters to make the
total number of bits a multiple of 32. (OSC-string examples) In this document, example OSC-strings will be written
without the null characters, surrounded by double quotes

It would seem from this that an OSC String must not be null. But then again, there isn't really a good way to otherwise express an empty string.

Regarding the fix, the second change shouldn't be required (I don't think). The padding calculation should give 3 bytes of padding on a null string (i.e. 0 + 000). If that's causing a problem, I believe your client isn't compliant.

@chabad360
Copy link
Owner

Upon further testing this still seems to have problems on stopping the decoding on the first encountered empty string

It should return an error then.

@depili
Copy link
Author

depili commented Jan 28, 2022 via email

@depili
Copy link
Author

depili commented Jan 28, 2022 via email

@depili
Copy link
Author

depili commented Jan 28, 2022

Upon further investigation it seems that the key difference is that the messages are sent as part of a osc bundle, without the length altering code non-bundle empty strings are still decoded properly, but bundles fail.

This is output from a debug print in my osc-dispatching code, without the mangling of padding length on 0-lenght strings:

2022/01/28 12:17:59 Dispatching osc message /clock/state [0     0]
2022/01/28 12:17:59 OSC-listen error: *fmt.wrapError UnmarshalBinary: readArguments: not enough bits to read

And with the mangling.

2022/01/28 12:20:14 Dispatching osc message /clock/state [0     0]
2022/01/28 12:20:14 Dispatching osc message /clock/source/1/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false  0]
2022/01/28 12:20:14 Dispatching osc message /clock/source/2/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false  0]
2022/01/28 12:20:14 Dispatching osc message /clock/source/3/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false 12:20:14   0 false false  0]
2022/01/28 12:20:14 Dispatching osc message /clock/source/4/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false 12:20:14   0 false false  0]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/0/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/1/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/2/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/3/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/4/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/5/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/6/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/7/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/8/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]
2022/01/28 12:20:14 Dispatching osc message /clock/timer/9/state [3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5 false    0 false false]

The /clock/state is a legacy message send alone, the others are the new state api inside a osc bundle. All messages are generated and decoded on the same version of go-osc. Decoding of the bundles by npm osc package and the protokol osc monitor for osx match the case with the mangling in place.

@depili
Copy link
Author

depili commented Jan 28, 2022

And this is the output of the messages from Protokol:

12:32:31.353 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/state) INT32(0) STRING() STRING() STRING() STRING() INT32(0)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/source/1/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE) STRING(Timer 1) INT32(0)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/source/2/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE) STRING(Timer 2) INT32(0)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/source/3/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING(12:32:31) STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE) STRING(one) INT32(0)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/source/4/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING(12:32:31) STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE) STRING() INT32(0)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/0/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/1/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/2/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.369 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/3/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/4/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/5/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/6/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/7/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/8/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)
12:32:31.370 | RECEIVE    | ENDPOINT([::ffff:192.168.2.84]:41549) ADDRESS(/clock/timer/9/state) STRING(3e4ee3ca38f32fea6dba1a13bf2657ddb3917a38cffecbdca302f3014b4d68c5) BOOL(FALSE) STRING() STRING() STRING() FLOAT(0) BOOL(FALSE) BOOL(FALSE)

@chabad360
Copy link
Owner

interesting. Can you send me a that whole packet raw? I would like to test against it.

Regarding issues with ListenAndServe and error hygiene, next week (or the week after, not sure yet) I'm planning a rather large rewrite (probably not the core decoding, but almost everything else). If you've got a wishlist, let me know.

@chabad360
Copy link
Owner

I pushed a few small fixes (from a few weeks ago, that I just committed now), let me know if those help at all.

@depili
Copy link
Author

depili commented Jan 28, 2022

https://kissa.depili.fi/tmp/osc_packet_captures.pcap

The sending code is from https://gitlab.com/Depili/clock-8001 a open source stage timer

@chabad360
Copy link
Owner

One thing I'm noticing from your packet captures, the bundle seems to include more data than your MTU allows for (which appears to be about 1500), cause the last few messages to get dropped consistently.

In other news, when I remove those bytes, every thing works fine in my v1 rewrite branch (which hasn't been pushed yet), so that's a good thing.

@depili
Copy link
Author

depili commented Feb 2, 2022

Hmm, the tcpdump actually chops the packets up due to their length being larger than the reported MTU

09:25:02.509668 IP 192.168.2.67.40967 > 192.168.2.255.isbconference2: UDP, bad length 1808 > 1472

But the OSC libraries, including go-osc, as seen from above, actually receive all of that data and are happy to parse it all (given the patch to go-osc).

Nothing in the OSC spec seems to indicate that a single message must fit into one udp packet on the transport layer.

@depili
Copy link
Author

depili commented Feb 2, 2022

https://opensoundcontrol.stanford.edu/files/osc-best-practices-final.pdf actually just plainly says that fragmentation is ok, but this also leads to the question why the bundle packets (send by go-osc) don't seem to get fragmentated even when exceeding the MTU

@depili
Copy link
Author

depili commented Feb 2, 2022

https://kissa.depili.fi/osc_all.pcap error was in the tcpdump filters for not capturing also the fragment pieces. The fragments get reassembled by the kernel automatically for the application.

@chabad360
Copy link
Owner

But the OSC libraries, including go-osc, as seen from above, actually receive all of that data and are happy to parse it all (given the patch to go-osc).

Nothing in the OSC spec seems to indicate that a single message must fit into one udp packet on the transport layer.

That I know, it's just hard to test when I'm exporting raw data from the pcap.

but this also leads to the question why the bundle packets (send by go-osc) don't seem to get fragmentated even when exceeding the MTU

That is interesting, it's possible Go explicitly doesn't care (which I should probably look into, that doesn't seem like a good thing).

In other news, I've decided to do my rewrite this week, I'm just wrapping up the actual encoding and decoding bits (fixed a number of bugs, I believe). I'm going to get started on the whole server bit either tomorrow or the next day. If there is anything you'd like added or changed in the API (the coming v1 release is gonna be almost entirely breaking changes), let me know, maybe I can work it in.

@depili
Copy link
Author

depili commented Feb 2, 2022

The issue with the packets was completely on wrong tcpdump filters, that weren't catching the additional fragments sent.

For the server; It would help if the errors returned would be custom ones for decoding so they can be easily processed. Also if the error processing flow wouldn't close the port (and returning to ListenAndServe would reuse the already open port) it would prevent potential packet loss and errors on the client side while the port is closed.

Would also like to have a way to shut the server down if needed for graceful shutdown (and in my case reloading of the configuration with potential osc port changes)

@chabad360
Copy link
Owner

chabad360 commented Feb 10, 2022

I've just pushed a bunch of changes to the v1-server branch (eventually it's all gonna move into a v1-rewrite branch that hasn't been pushed yet). Can you try it and let me know if it works?

$ go get github.com/chabad360/go-osc@v1-server

There is a good number of API changes and there are a lot more to come.

EDIT: I should note, the dispatcher API has a rather large bug in the matching code, I've fixed it locally, but I don't currently have an internet connection.

@chabad360
Copy link
Owner

Just pushed the fix to the dispatcher.

@depili
Copy link
Author

depili commented Feb 16, 2022 via email

@chabad360
Copy link
Owner

All right, at this point the rewrite should be ready to use (checkout the v1-rewrite branch).

I'm going to close this PR and the other one in favor of #3.

@chabad360 chabad360 closed this Feb 16, 2022
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.

2 participants