.. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. include:: ../common-defs.rst .. highlight:: cpp .. default-domain:: cpp .. |BW| replace:: :code:`BufferWriter` .. _BufferWriter: ************ BufferWriter ************ Synopsis ******** :code:`#include "swoc/BufferWriter.h"` .. class:: BufferWriter :libswoc:`Reference documentation `. .. class:: FixedBufferWriter :libswoc:`Reference documentation `. .. class:: template < uintmax_t N > LocalBufferWriter :libswoc:`Reference documentation `. |BW| is designed for use in the common circumstance of generating formatted output strings in fixed buffers. The goal is to replace usage that is a mixture of :code:`snprintf`, :code:`strcpy`, and :code:`memcpy`. |BW| automates buffer size checking and clipping for better reliability. |BW| itself is an abstract class to describe the base interface to wrappers for various types of output buffers. As a common example, :libswoc:`FixedBufferWriter` is a subclass that wraps a fixed size buffer. An instance is constructed by passing it a buffer and a size, which it then tracks as data is written. Writing past the end of the buffer is automatically clipped to prevent overruns. |BW| tracks two sizes - the actual amount of space used in the buffer and the amount of data written. The former is bounded by the buffer size to prevent overruns. The latter provides a mechanism both to detect the clipping done to prevent overruns, and the amount of space needed to avoid it. Usage ***** Consider a common case of code like :: char buff[1024]; char * ptr = buff; size_t len = sizeof(buff); //... if (len > 0) { auto n = std::min(len, thing1_len); memcpy(ptr, thing1, n); len -= n; } if (len > 0) { auto n = std::min(len, thing2_len); memcpy(ptr, thing2, n); len -= n; } if (len > 0) { auto n = std::min(len, thing3_len); memcpy(ptr, thing3, n); len -= n; } This is changed to:: char buff[1024]; swoc::FixedBufferWriter bw(buff, sizeof(buff)); //... bw.write(thing1, thing1_len). bw.write(thing2, thing2_len); bw.write(thing3, thing3_len); Or even more compactly with :libswoc:`LocalBufferWriter` :: swoc::LocalBufferWriter<1024> bw; // ... bw.write(thing1, thing1_len).write(thing2, thing2_len).write(thing3, thing3_len); For every call to :libswoc:`BufferWriter::write` the remaining length is updated and checked, discarding any overrun. This replaces the need to write the checks explictly on every :code:`memcpy`. Usually, however, |BW| will do the needed space checks that were not done at all previously. A similar mechanism works for :code:`snprintf`:: if (count < sizeof(buff)) { count += snprintf(buff + count, sizeof(buff) - count, "blah blah", arg1, ...); } vs. :: bw.commit(snprintf(bw.aux_data(), bw.remaining(), "blah blah", arg1, ...)); As before, the buffer limits are updated and checked, discarding as needed. Although :code:`snprintf` can be used in this way, |BW| provides its own `print formatting `_ which is more flexible and powerful than C style printing. |BW| itself is an abstract class and can't be constructed. Use is provided through concrete subclasses. :class:`FixedBufferWriter` This operates on an externally defined buffer of a fixed size. The constructor requires providing the start and size of the buffer. Output is limited to that buffer. :class:`LocalBufferWriter` This is a template class which takes a single size argument. An internal buffer of that size is made part of the instance and used as the output buffer. :class:`FixedBufferWriter` is used where the buffer is pre-existing or externally supplied. If the buffer is only accessed by the output generation then :class:`LocalBufferWriter` is more convenient, eliminating the need to separately declare the buffer. It also makes :class:`LocalBufferWriter` usable in line, such as :: std::cout << swoc::LocalBufferWriter<1024>{}.write(...).write(...).view(); which writes output to the |BW| instance, then gets a view of the content which is written to :code:`std::cout`. Writing ======= The primary method for sending output to a |BW| is :libswoc:`BufferWriter::write`. This is an overloaded method with overloads for a character (:code:`char`), a buffer (:code:`void *, size_t`), or a string view (:code:`std::string_view`). This covers literal strings and C-style strings because both of those implicitly convert to :code:`std::string_view`. For :code:`snprintf` style support, see `buffer writer formatting `_. Reading ======= Data in the buffer can be extracted using :libswoc:`BufferWriter::data`, along with :libswoc:`BufferWriter::size`. Together these return a pointer to the start of the buffer and the amount of data written to the buffer. The same result can be obtained with :libswoc:`FixedBufferWriter::view` which returns a :code:`std::string_view` which covers the output data. Calling :libswoc:`BufferWriter::error` will indicate if more data than space available was written (i.e. the buffer would have been overrun). :libswoc:`BufferWriter::extent` returns the amount of data written to the |BW|. This can be used in a two pass style with a null / size 0 buffer to determine the buffer size required for the full output. Advanced ======== The :libswoc:`BufferWriter::restrict` and :libswoc:`BufferWriter::restore` methods can be used to require space in the buffer. A common use case for this is to guarantee matching delimiters in output if buffer space is exhausted. :libswoc:`BufferWriter::restrict` can be used to temporarily reduce the buffer capacity by an amount large enough to hold the terminal delimiter. After writing the contained output, :libswoc:`BufferWriter::restore` can be used to restore the capacity and then output the terminal delimiter. E.g. :: w.restrict(1); w.write('['); /// other output to w. w.restore(1).write(']'); // always works, even if buffer was overrrun. .. warning:: :libswoc::`BufferWriter::restore` can only restore capacity that was removed by :libswoc:`BufferWriter::restrict`. It can **not** make the capacity larger than it was originally. As something of an alternative it is easy to do "speculative" output. :libswoc:`BufferWriter::aux_data` returns a pointer to the first byte of the buffer not yet used, and :libswoc:`BufferWriter::remaining` returns the amount of buffer space not yet consumed. These can be easily used to create a new :libswoc:`FixedBufferWriter` on the unused space :: ts::FixedBufferWriter subw(w.aux_data(), w.remaining()); Output can be written to :arg:`subw`. If successful :libswoc:`BufferWriter::commit` can be used to add that output to the original buffer :arg:`w` :: w.commit(subw.size()); If there is an error :arg:`subw` can be discarded and some suitable error output written to :arg:`w` instead. A common use case is to verify there is sufficient space in the buffer and create a "not enough space" message if not. E.g. :: ts::FixedBufferWriter subw{w.aux_data(), w.remaining()}; write_some_output(subw); if (!subw.error()) w.commit(subw.size()); else w.write("[...]"); While this can be used in a manner similar to using :libswoc:`BufferWriter::restrict` and :libswoc:`BufferWriter::restore` by subtracting from :libswoc:`BufferWriter::remaining`, this can be a bit risky because the return value is unsigned and underflow would be problematic. Examples ======== For example, error prone code that looks like .. code-block:: cpp char new_via_string[1024]; // 512-bytes for hostname+via string, 512-bytes for the debug info char * via_string = new_via_string; char * via_limit = via_string + sizeof(new_via_string); // ... * via_string++ = ' '; * via_string++ = '['; // incoming_via can be max MAX_VIA_INDICES+1 long (i.e. around 25 or so) if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity via_string += nstrcpy(via_string, incoming_via); } else { memcpy(via_string, incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT); via_string += VIA_SERVER - VIA_CLIENT; } *via_string++ = ']'; becomes .. code-block:: cpp ts::LocalBufferWriter<1024> w; // 1K internal buffer. // ... w.write(" ["); if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity w.write(incoming_via); } else { w.write(std::string_view{incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT}); } w.write(']'); There will be no overrun on the memory buffer in :arg:`w`, in strong contrast to the original code. This can be done better, as :: if (w.remaining() >= 3) { w.restrict(1).write(" ["); if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity w.write(incoming_via); } else { w.write(std::string_view{incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT}); } w.restore(1).write(']'); } This has the result that the terminal bracket will always be present which is very much appreciated by code that parses the resulting log file.