ProtoAssembly.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef PROTO_ASSEMBLY_H | ||
| 2 | #define PROTO_ASSEMBLY_H | ||
| 3 | #include <stddef.h> | ||
| 4 | #include <stdint.h> | ||
| 5 | |||
| 6 | // Putting a protocol 2 message back together from the pieces the web server | ||
| 7 | // hands over: a frame larger than a TCP segment, or a message sent as several | ||
| 8 | // frames (docs/PROTOCOL.md). One message at a time across every client, in | ||
| 9 | // one buffer taken when its first piece arrives and given up when it is whole, | ||
| 10 | // refused or stalled, so nothing is held while no long message is on its way. | ||
| 11 | // Not thread safe: the caller locks it. Kept free of the web server so it can | ||
| 12 | // be unit tested on the PC (pio test -e native). | ||
| 13 | |||
| 14 | // How long a message may take to arrive in full. | ||
| 15 | #define PROTO_ASSEMBLY_TIMEOUT_MS 10000 | ||
| 16 | |||
| 17 | // One piece of a message. | ||
| 18 | struct proto_piece_t { | ||
| 19 | uint32_t clientId = 0; | ||
| 20 | bool first = false; // its first piece: the start of its first frame | ||
| 21 | bool last = false; // its last piece: the end of its final frame | ||
| 22 | bool text = true; // a text message rather than binary | ||
| 23 | const uint8_t *data = nullptr; | ||
| 24 | size_t len = 0; | ||
| 25 | }; | ||
| 26 | enum class assembly_t : uint8_t { | ||
| 27 | more, // wait for the next piece | ||
| 28 | complete, // the message is whole: take() it | ||
| 29 | ignored, // a piece of a message that was already answered | ||
| 30 | busy, // another client's message holds the buffer | ||
| 31 | tooLarge, // over the limit: the rest of it is ignored | ||
| 32 | notText, // a binary message: the rest of it is ignored | ||
| 33 | noMemory // the buffer couldn't be had: the rest of it is ignored | ||
| 34 | }; | ||
| 35 | class MessageAssembler { | ||
| 36 | public: | ||
| 37 | 9 | explicit MessageAssembler(size_t limit) : limit(limit) {} | |
| 38 | 9 | ~MessageAssembler() { this->release(); } | |
| 39 | // Each refusal (busy, tooLarge, notText, noMemory) comes once per message, | ||
| 40 | // from the piece that caused it, so the client gets one reply. | ||
| 41 | assembly_t add(const proto_piece_t &piece, uint32_t now); | ||
| 42 | // The complete message, terminated, for the caller to free(): the | ||
| 43 | // assembler holds nothing after it. | ||
| 44 | char *take(size_t &len); | ||
| 45 | // The client has gone: its part of a message goes with it. | ||
| 46 | void drop(uint32_t clientId); | ||
| 47 | // Gives up a message that has taken too long, returning its client, which | ||
| 48 | // is owed a reply; 0 when none has. The rest of it is ignored. | ||
| 49 | uint32_t expire(uint32_t now); | ||
| 50 | 7 | bool holding() const { return this->owner != 0; } | |
| 51 | private: | ||
| 52 | size_t limit; | ||
| 53 | uint32_t owner = 0; | ||
| 54 | uint32_t startedAt = 0; | ||
| 55 | char *buf = nullptr; | ||
| 56 | size_t len = 0; | ||
| 57 | void release(); | ||
| 58 | }; | ||
| 59 | #endif | ||
| 60 |