summaryrefslogtreecommitdiff
path: root/src/chunk.hh
blob: a19522ec113744cbcb35a7d3e7aa720f64ab853e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
 * Copyright © 2018, 2020 Christian Persch
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once

#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <list>
#include <memory>
#include <stack>
#include <sys/types.h>

namespace vte {

namespace base {

class Chunk {
        // A Chunk contains the raw data read from PTY.
        //
        // Data will be read in blocks and accumulated into chunks.
        // Chunks will be processed in (potentially) multiple
        // parts (by potentially multiple (sub)parsers).
        //
private:
        class Recycler {
        public:
                void operator()(Chunk* chunk) {
                        if (chunk == nullptr)
                                return;
                        chunk->recycle();
                }
        };

        void recycle() noexcept;

        static constexpr const unsigned k_max_free_chunks = 16u;
        static constexpr const unsigned k_chunk_size = 0x2000u - 2 * sizeof(void*);
        static constexpr const unsigned k_overlap_size = 1u;

        enum class Flags : uint8_t {
                eSEALED  = 1u << 0,
                eEOS     = 1u << 1,
                eCHAINED = 1u << 2,
        };

        uint8_t* m_data{nullptr};
        size_t m_capacity{0};
        size_t m_start{k_overlap_size};
        size_t m_size{k_overlap_size};
        uint8_t m_flags{0};

public:

        // Returns: pointer to the raw data storage (includes space for pre-begin data)
        inline constexpr uint8_t* data() const noexcept { return m_data; }

        // Returns: the storage capacity of data()
        inline constexpr auto capacity() const noexcept { return m_capacity; }

        // Returns: pointer to where to start reading available data (inside data())
        inline constexpr uint8_t const* begin_reading() const noexcept { assert(m_start <= m_size); return data() + m_start; }

        // Returns: pointer to the end of available data()
        inline constexpr uint8_t const* end_reading() const noexcept { return data() + m_size; };

        // Returns: how much data there is to read between begin_reading() and end_reading()
        inline constexpr auto size_reading() const noexcept { return m_size - m_start; }

        // Returns: whether there is any data to read
        inline constexpr bool has_reading() const noexcept { return begin_reading() < end_reading(); }

        // Sets the value returned by begin_reading(). To be used after
        // processing some data, so that the next round knows where to start.
        void set_begin_reading(uint8_t const* ptr) noexcept
        {
                assert(ptr >= data() &&
                       (!chained() || ptr > data()) &&
                       ptr <= data() + capacity());
                m_start = unsigned(ptr - data());
        }

        // Returns: pointer to buffer to write data into
        // Note that there is *always* at least one byte writable at begin_writing()-1
        // to be used when reading from a PTY in CPKT mode.
        inline constexpr uint8_t* begin_writing() const noexcept { assert(m_size > 0); return data() + m_size; }

        // Returns: size of begin_writing() buffer
        inline constexpr auto capacity_writing() const noexcept { return m_capacity - m_size; }

        // Add to chunk size. To be called after writing data to begin_writing().
        inline void add_size(ssize_t len)
        {
                assert(len >= 0 && size_t(len) <= capacity_writing());
                m_size += len;
        }

        // Chain this Chunk to some other Chunk.
        // If the other chunk isn't EOS, we
        // copy the last k_overlap_size byte(s) from it to the start of
        // the new chunk, and set the new chunk as chained.
        // This will allow rewinding the stream during processing,
        // without keeping the preceding chunk around.
        void chain(Chunk const* previous)
        {
                assert(m_size == k_overlap_size && m_start == m_size); // or call reset() here?

                if (!previous->eos()) {
                        std::memcpy(m_data,
                                    previous->m_data + previous->m_size - k_overlap_size,
                                    k_overlap_size);

                        set_chained();
                }
        }

        // Special-case operator new, so that we can allocate
        // the chunk data together with the instance.
        void* operator new(std::size_t count)
        {
                assert(count < k_chunk_size);
                return std::malloc(k_chunk_size);
        }

        // Special-case operator delete for pairing with operator new.
        void operator delete(void* ptr)
        {
                std::free(ptr);
        }

        // Type to use when storing a Chunk, so that chunks can be recycled.
        using unique_type = std::unique_ptr<Chunk, Recycler>;

        Chunk()
                : m_data{reinterpret_cast<uint8_t*>(this) + sizeof(*this)},
                  m_capacity{k_chunk_size - sizeof(*this)}
        {
                std::memset(m_data, 0, k_overlap_size);
        }

        Chunk(Chunk const&) = delete;
        Chunk(Chunk&&) = delete;
        ~Chunk() = default;

        Chunk& operator= (Chunk const&) = delete;
        Chunk& operator= (Chunk&&) = delete;

        // Resets the chunk. Reset chunks will not be rewindable!
        void reset() noexcept
        {
                std::memset(m_data, 0, k_overlap_size);
                m_start = m_size = k_overlap_size;
                m_flags = 0;
        }

        // Returns: a new or recycled Chunk
        static unique_type get(Chunk const* chain_to) noexcept;

        // Prune recycled chunks
        static void prune(unsigned int max_size = k_max_free_chunks) noexcept;

        // Returns: whether the chunk is sealed, i.e. must not be used
        // to write more data into
        inline constexpr bool sealed() const noexcept { return m_flags & (uint8_t)Flags::eSEALED; }

        // Seal the chunk
        inline void set_sealed() noexcept { m_flags |= (uint8_t)Flags::eSEALED; }

        // Returns: whether the chunk is an EOS (end-of-stream)
        inline constexpr bool eos() const noexcept { return m_flags & (uint8_t)Flags::eEOS; }

        // Set the chunk EOS
        inline void set_eos() noexcept { m_flags |= (uint8_t)Flags::eEOS; }

        // Returns: whether the chunk was chained to some other chunk
        // and thus m_start may be set to < k_overlap_size.
        inline constexpr bool chained() const noexcept { return m_flags & (uint8_t)Flags::eCHAINED; }

        // Set the chunk as chained
        inline void set_chained() noexcept { m_flags |= (uint8_t)Flags::eCHAINED; }

private:

        /* Note that this is using the standard deleter, not Recycler */
        static std::stack<std::unique_ptr<Chunk>, std::list<std::unique_ptr<Chunk>>> g_free_chunks;
};

} // namespace base

} // namespace vte