Its a good exercise though, to implement a transctional protocol over a "best effort" one like UDP. TCP is a good reference of course as that is basically what it's for.
Its the same kind of exercise as:
Writing a synchronous protocol over an async layer.... or vice versa.
Mutating loops from while to for to "do..until" to "do..while".
Writing a filesystem in a database, writing a database in a filesystem.
They are all "69" problems. The exercises work because they force you to turn the table around and look at it from a different direction. Noticing they are just different "perspectives" on the same thing. It make you correctly "hedge" on certain otherwise "blockers". "Oh we can't do that we are async/sync" brittle boundardies etc.
EDIT: "Mutating loops from while to for to "do..until" to "do..while"."
This one bit me recently, pretty hard and nasty too. I was introduced the 68k "Decrement and loop" DBRA and friends. It's not even funny, stop laughing. It's just not an intuitive way to write loops. Its not bad when you accept it, but until you do it's a mind,. "Booork!"
DBRA (and peers) test if the condition is false first. (defaults to "N" flag). If it IS they decrement and then jump. So it's test-postdecrement-jump. What where they sniffing at the time. However... as long as you remember to -1 your length, it works out fine.
I think if you simply it, it's just this:
while( index-- >= 0) ) {}
... wait...
{} do while( index-- >=0);
I think.
{} do until( index-- < 0);
Final answer.