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 wrapper::detail {
28 : : struct ReceiveFramerAccess;
29 : : }
30 : : namespace framer {
31 : :
32 : : /**
33 : : * @brief Framer for binary protocols that prefix each message with its length.
34 : : *
35 : : * The layout most binary protocols use, and the one PacketFramer cannot carry:
36 : : * because the length says exactly how many bytes to collect, no byte value is
37 : : * special and the payload may contain anything at all.
38 : : *
39 : : * ```cpp
40 : : * auto client = wirestead::tcp_client("127.0.0.1", 9000)
41 : : * .use_length_prefix_framer(4) // 4-byte big-endian length
42 : : * .on_message(...)
43 : : * .build();
44 : : * ```
45 : : *
46 : : * @warning **The stream must start on a frame boundary.** This framer has no
47 : : * sync word and cannot resynchronise: joining mid-message, or one corrupt
48 : : * length, misreads the following bytes as a header and stays wrong. That is
49 : : * the normal case for TCP and UDS, where the connection begins at a boundary.
50 : : * A serial line you can attach to mid-stream, or any protocol carrying a sync
51 : : * word, needs framing that hunts for that word - implement IFramer and pass it
52 : : * to `framer()`.
53 : : *
54 : : * A declared length above `max_length` is treated as corruption: the buffer is
55 : : * dropped and framing restarts, rather than allocating whatever a bad or
56 : : * hostile header asked for.
57 : : */
58 : : class WIRESTEAD_API LengthPrefixFramer : public IFramer {
59 : : public:
60 : : enum class Endian { Big, Little };
61 : :
62 : : /**
63 : : * @brief Construct a new Length Prefix Framer
64 : : *
65 : : * @param prefix_bytes Width of the length field: 1, 2 or 4.
66 : : * @param endian Byte order of the length field. Big by default, which is
67 : : * network order and what most wire protocols specify.
68 : : * @param max_length Largest payload accepted; a larger declared length is
69 : : * treated as corruption.
70 : : * @param length_includes_prefix Whether the declared length counts the
71 : : * prefix itself. Both conventions exist; getting it wrong shifts
72 : : * every frame by the prefix width.
73 : : * @throws std::invalid_argument if prefix_bytes is not 1, 2 or 4, or
74 : : * max_length is 0.
75 : : */
76 : : explicit LengthPrefixFramer(size_t prefix_bytes = 2, Endian endian = Endian::Big, size_t max_length = 65536,
77 : : bool length_includes_prefix = false);
78 : :
79 : 19 : ~LengthPrefixFramer() override = default;
80 : :
81 : : void push_bytes(memory::ConstByteSpan data) override;
82 : : void on_message(MessageCallback cb) override;
83 : : void reset() override;
84 : :
85 : : private:
86 : : friend struct wrapper::detail::ReceiveFramerAccess;
87 : : // Returns false when the buffer does not yet hold a whole header.
88 : : bool read_length(size_t offset, size_t& out) const;
89 : :
90 : : size_t prefix_bytes_;
91 : : Endian endian_;
92 : : size_t max_length_;
93 : : bool length_includes_prefix_;
94 : :
95 : : std::vector<uint8_t> buffer_;
96 : : MessageCallback on_message_;
97 : : };
98 : :
99 : : } // namespace framer
100 : : } // namespace wirestead
|