Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <cstddef>
20 : : #include <cstdint>
21 : : #include <vector>
22 : :
23 : : #include "wirestead/base/visibility.hpp"
24 : : #include "wirestead/framer/iframer.hpp"
25 : :
26 : : namespace wirestead {
27 : : namespace framer {
28 : :
29 : : /**
30 : : * @brief Framer for binary protocols that prefix each message with its length.
31 : : *
32 : : * The layout most binary protocols use, and the one PacketFramer cannot carry:
33 : : * because the length says exactly how many bytes to collect, no byte value is
34 : : * special and the payload may contain anything at all.
35 : : *
36 : : * ```cpp
37 : : * auto client = wirestead::tcp_client("127.0.0.1", 9000)
38 : : * .use_length_prefix_framer(4) // 4-byte big-endian length
39 : : * .on_message(...)
40 : : * .build();
41 : : * ```
42 : : *
43 : : * @warning **The stream must start on a frame boundary.** This framer has no
44 : : * sync word and cannot resynchronise: joining mid-message, or one corrupt
45 : : * length, misreads the following bytes as a header and stays wrong. That is
46 : : * the normal case for TCP and UDS, where the connection begins at a boundary.
47 : : * A serial line you can attach to mid-stream, or any protocol carrying a sync
48 : : * word, needs framing that hunts for that word - implement IFramer and pass it
49 : : * to `framer()`.
50 : : *
51 : : * A declared length above `max_length` is treated as corruption: the buffer is
52 : : * dropped and framing restarts, rather than allocating whatever a bad or
53 : : * hostile header asked for.
54 : : */
55 : : class WIRESTEAD_API LengthPrefixFramer : public IFramer {
56 : : public:
57 : : enum class Endian { Big, Little };
58 : :
59 : : /**
60 : : * @brief Construct a new Length Prefix Framer
61 : : *
62 : : * @param prefix_bytes Width of the length field: 1, 2 or 4.
63 : : * @param endian Byte order of the length field. Big by default, which is
64 : : * network order and what most wire protocols specify.
65 : : * @param max_length Largest payload accepted; a larger declared length is
66 : : * treated as corruption.
67 : : * @param length_includes_prefix Whether the declared length counts the
68 : : * prefix itself. Both conventions exist; getting it wrong shifts
69 : : * every frame by the prefix width.
70 : : * @throws std::invalid_argument if prefix_bytes is not 1, 2 or 4, or
71 : : * max_length is 0.
72 : : */
73 : : explicit LengthPrefixFramer(size_t prefix_bytes = 2, Endian endian = Endian::Big, size_t max_length = 65536,
74 : : bool length_includes_prefix = false);
75 : :
76 : 14 : ~LengthPrefixFramer() override = default;
77 : :
78 : : void push_bytes(memory::ConstByteSpan data) override;
79 : : void on_message(MessageCallback cb) override;
80 : : void reset() override;
81 : :
82 : : private:
83 : : // Returns false when the buffer does not yet hold a whole header.
84 : : bool read_length(size_t offset, size_t& out) const;
85 : :
86 : : size_t prefix_bytes_;
87 : : Endian endian_;
88 : : size_t max_length_;
89 : : bool length_includes_prefix_;
90 : :
91 : : std::vector<uint8_t> buffer_;
92 : : MessageCallback on_message_;
93 : : };
94 : :
95 : : } // namespace framer
96 : : } // namespace wirestead
|