// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #pragma once #include #include #include #include #include "communication/bolt/v1/constants.hpp" namespace memgraph::communication::bolt { /** * @brief ChunkedEncoderBuffer * * Has methods for writing data into the chunk buffer. * * Writing data stores data in the internal buffer and chunking data sends * the currently stored data to the OutputStream. Flushing prepends the data * length to each chunk. * * | chunk header | --- chunk --- | another chunk | -- end marker -- | * | ------- whole chunk -------- | whole chunk | chunk of size 0 | * * | --------------------------- message --------------------------- | * | --------------------------- buffer --------------------------- | * * NOTE: To send a message end marker (chunk of size 0) it is necessary to * explicitly call the `Flush` method on an empty buffer. In that way the user * can control when the message is over and the whole message isn't * unnecessarily buffered in memory. * * The current implementation stores only a single chunk into memory and sends * it immediately to the output stream when new data arrives. * * @tparam TOutputStream the output stream that should be used */ template class ChunkedEncoderBuffer { public: ChunkedEncoderBuffer(TOutputStream &output_stream) : output_stream_(output_stream) {} /** * Writes n values into the buffer. If n is bigger than whole chunk size * values are automatically chunked and sent to the output buffer. * * @param values data array of bytes * @param n is the number of bytes */ void Write(const uint8_t *values, size_t n) { size_t written = 0; while (n > 0) { // Define the number of bytes which will be copied into the chunk because // the internal storage is a fixed length array. size_t size = n < kChunkMaxDataSize - have_ ? n : kChunkMaxDataSize - have_; // Copy `size` values to the chunk array. std::memcpy(chunk_.data() + kChunkHeaderSize + have_, values + written, size); // Update positions. The position pointer and incoming size have to be // updated because all incoming values have to be processed. written += size; have_ += size; n -= size; // If the chunk is full, send it to the output stream and clear the // internal storage to make space for other incoming values that are left // in the values array. if (have_ == kChunkMaxDataSize) Flush(true); } } /** * Wrap the data from the chunk array (append the size header) and send * the whole chunk into the output stream. * * @param have_more this parameter is passed to the underlying output stream * `Write` method to indicate wether we have more data * waiting to be sent (in order to optimize network packets) */ bool Flush(bool have_more = false) { // Write the size of the chunk. chunk_[0] = have_ >> 8; chunk_[1] = have_ & 0xFF; // Write the data to the stream. auto ret = output_stream_.Write(chunk_.data(), kChunkHeaderSize + have_, have_more); // Cleanup. Clear(); return ret; } /** Clears the internal buffers. */ void Clear() { have_ = 0; } /** * Returns a boolean indicating whether there is data in the buffer. * @returns true if there is data in the buffer, * false otherwise */ bool HasData() { return have_ > 0; } private: // The output stream used. TOutputStream &output_stream_; // Buffer for a single chunk. std::array chunk_; // Amount of data in chunk array. size_t have_{0}; }; } // namespace memgraph::communication::bolt