From: Mike Stepanek (mstepane) Date: Wed, 29 Sep 2021 14:09:32 +0000 (+0000) Subject: Merge pull request #3075 in SNORT/snort3 from ~OSHUMEIK/snort3:streambuf to master X-Git-Tag: 3.1.14.0~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f44367e87edf6485a92a43f6d107ac51aaab13b9;p=thirdparty%2Fsnort3.git Merge pull request #3075 in SNORT/snort3 from ~OSHUMEIK/snort3:streambuf to master Squashed commit of the following: commit 27e4c24b75d3b134656501765fad26a35c125fac Author: Oleksii Shumeiko Date: Fri Sep 17 13:10:27 2021 +0300 utils: add custom i/o stream buffers to JS normalizer The input stream buffer is a buffer over separated regions, which presents a continuous sequence to the caller. The output stream buffer is like std:stringstream. It has an ability to dynamically extend the buffer and to give away ownership over its memory to someone else. Some trace messages were removed (intermediate result are encapsulated in streambuf object now). Temporal buffer (for script detection mechanism) is prepended immediately to the output as soon as normalizer context created. --- diff --git a/doc/user/http_inspect.txt b/doc/user/http_inspect.txt index 994541056..82b264d96 100755 --- a/doc/user/http_inspect.txt +++ b/doc/user/http_inspect.txt @@ -360,7 +360,7 @@ JavaScript data dump and verbosity levels: 1. js_data buffer as it is passed to detection. -2. Current script in normalized form. +2. (no messages available currently) 3. Current script as it is passed to Normalizer. diff --git a/src/service_inspectors/http_inspect/dev_notes.txt b/src/service_inspectors/http_inspect/dev_notes.txt index b21548bb4..707835373 100755 --- a/src/service_inspectors/http_inspect/dev_notes.txt +++ b/src/service_inspectors/http_inspect/dev_notes.txt @@ -432,7 +432,7 @@ Verbosity levels: Verbosity levels: + 1. js_data buffer as it is being passed to detection (available in release build) -2. Current normalized script (available in release build) +2. (no messages available currently) 3. Payload passed to Normalizer (available in release build) 4. Temporary buffer (debug build only) 5. Matched token (debug build only) diff --git a/src/service_inspectors/http_inspect/http_flow_data.cc b/src/service_inspectors/http_inspect/http_flow_data.cc index 977d79675..8cd3ea751 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.cc +++ b/src/service_inspectors/http_inspect/http_flow_data.cc @@ -272,6 +272,10 @@ snort::JSNormalizer& HttpFlowData::acquire_js_ctx(int32_t ident_depth, size_t no js_normalizer = new JSNormalizer(*js_ident_ctx, norm_depth, max_template_nesting); update_allocations(JSNormalizer::size()); + auto ptr = js_detect_buffer[HttpCommon::SRC_SERVER]; + auto len = js_detect_length[HttpCommon::SRC_SERVER]; + js_normalizer->prepend_script(ptr, len); + debug_logf(4, http_trace, TRACE_JS_PROC, nullptr, "js_normalizer created (norm_depth %zd, max_template_nesting %d)\n", norm_depth, max_template_nesting); diff --git a/src/service_inspectors/http_inspect/http_js_norm.cc b/src/service_inspectors/http_inspect/http_js_norm.cc index b46465670..453fe7203 100644 --- a/src/service_inspectors/http_inspect/http_js_norm.cc +++ b/src/service_inspectors/http_inspect/http_js_norm.cc @@ -54,28 +54,23 @@ static const char* ret2str(JSTokenizer::JSRet ret) } static inline JSTokenizer::JSRet js_normalize(JSNormalizer& ctx, const char* const end, - const char* dst_end, const char*& ptr, char*& dst) + const char*& ptr) { trace_logf(3, http_trace, TRACE_JS_DUMP, nullptr, "original[%zu]: %.*s\n", end - ptr, static_cast(end - ptr), ptr); - auto ret = ctx.normalize(ptr, end - ptr, dst, dst_end - dst); + auto ret = ctx.normalize(ptr, end - ptr); auto src_next = ctx.get_src_next(); - auto dst_next = ctx.get_dst_next(); trace_logf(3, http_trace, TRACE_JS_PROC, nullptr, "normalizer returned with %d '%s'\n", ret, ret2str(ret)); - trace_logf(2, http_trace, TRACE_JS_DUMP, nullptr, - "normalized[%zu]: %.*s\n", dst_next - dst, static_cast(dst_next - dst), dst); - if (src_next > ptr) HttpModule::increment_peg_counts(PEG_JS_BYTES, src_next - ptr); else src_next = end; // Normalizer has failed, thus aborting the remaining input ptr = src_next; - dst = dst_next; return ret; } @@ -144,31 +139,25 @@ void HttpJsNorm::enhanced_external_normalize(const Field& input, Field& output, HttpEventGen* events = ssn->events[HttpCommon::SRC_SERVER]; - char* buffer = nullptr; - char* dst = nullptr; - const char* dst_end = nullptr; - if (!alive_ctx(ssn)) + { HttpModule::increment_peg_counts(PEG_JS_EXTERNAL); + trace_logf(2, http_trace, TRACE_JS_PROC, nullptr, + "script starts\n"); + } + else + trace_logf(2, http_trace, TRACE_JS_PROC, nullptr, + "script continues\n"); + + + auto& js_ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth, max_template_nesting); while (ptr < end) { trace_logf(1, http_trace, TRACE_JS_PROC, nullptr, "external script at %zd offset\n", ptr - (const char*)input.start()); - trace_logf(2, http_trace, TRACE_JS_PROC, nullptr, - "script %s\n", alive_ctx(ssn) ? "continues" : "starts"); - - if (!buffer) - { - auto len = end - ptr; // not more than the remaining raw data - buffer = new char[len]; - dst = buffer; - dst_end = buffer + len; - } - - auto& ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth, max_template_nesting); - auto ret = js_normalize(ctx, end, dst_end, ptr, dst); + auto ret = js_normalize(js_ctx, end, ptr); switch (ret) { @@ -211,12 +200,16 @@ void HttpJsNorm::enhanced_external_normalize(const Field& input, Field& output, break; } - if (buffer) + auto result = js_ctx.get_script(); + auto script_ptr = result.first; + + if (script_ptr) { - output.set(dst - buffer, (const uint8_t*)buffer, true); + auto script_len = result.second; + output.set(script_len, reinterpret_cast(script_ptr), true); trace_logf(1, http_trace, TRACE_JS_DUMP, nullptr, - "js_data[%zu]: %.*s\n", dst - buffer, static_cast(dst - buffer), buffer); + "js_data[%zu]: %.*s\n", script_len, static_cast(script_len), script_ptr); } } @@ -228,10 +221,6 @@ void HttpJsNorm::enhanced_inline_normalize(const Field& input, Field& output, HttpEventGen* events = ssn->events[HttpCommon::SRC_SERVER]; - char* buffer = nullptr; - char* dst = nullptr; - const char* dst_end = nullptr; - bool script_continue = alive_ctx(ssn); bool script_external = false; @@ -281,84 +270,73 @@ void HttpJsNorm::enhanced_inline_normalize(const Field& input, Field& output, HttpModule::increment_peg_counts(PEG_JS_INLINE); } - if (!buffer) - { - uint8_t* nbuf = ssn->js_detect_buffer[HttpCommon::SRC_SERVER]; - uint32_t nlen = ssn->js_detect_length[HttpCommon::SRC_SERVER]; - - auto len = nlen + (end - ptr); // not more than the remaining raw data - buffer = new char[len]; - if (nbuf) - memcpy(buffer, nbuf, nlen); - dst = buffer + nlen; - dst_end = buffer + len; - } + auto& js_ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth, max_template_nesting); + auto output_size_before = js_ctx.peek_script_size(); - auto& ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth, max_template_nesting); - auto dst_before = dst; - auto ret = js_normalize(ctx, end, dst_end, ptr, dst); + auto ret = js_normalize(js_ctx, end, ptr); switch (ret) { case JSTokenizer::EOS: - ctx.reset_depth(); - script_continue = false; + js_ctx.reset_depth(); break; case JSTokenizer::SCRIPT_ENDED: - script_continue = false; break; case JSTokenizer::SCRIPT_CONTINUE: - script_continue = true; break; case JSTokenizer::OPENING_TAG: *infractions += INF_JS_OPENING_TAG; events->create_event(EVENT_JS_OPENING_TAG); - script_continue = false; break; case JSTokenizer::CLOSING_TAG: *infractions += INF_JS_CLOSING_TAG; events->create_event(EVENT_JS_CLOSING_TAG); - script_continue = false; break; case JSTokenizer::BAD_TOKEN: *infractions += INF_JS_BAD_TOKEN; events->create_event(EVENT_JS_BAD_TOKEN); - script_continue = false; break; case JSTokenizer::IDENTIFIER_OVERFLOW: HttpModule::increment_peg_counts(PEG_JS_IDENTIFIER_OVERFLOW); *infractions += INF_JS_IDENTIFIER_OVERFLOW; events->create_event(EVENT_JS_IDENTIFIER_OVERFLOW); - script_continue = false; break; case JSTokenizer::TEMPLATE_NESTING_OVERFLOW: *infractions += INF_JS_TMPL_NEST_OVFLOW; events->create_event(EVENT_JS_TMPL_NEST_OVFLOW); - script_continue = false; break; default: assert(false); - script_continue = false; break; } - if (script_external && dst_before != dst) + if (script_external && output_size_before != js_ctx.peek_script_size()) { *infractions += INF_JS_CODE_IN_EXTERNAL; events->create_event(EVENT_JS_CODE_IN_EXTERNAL); } + + script_continue = ret == JSTokenizer::SCRIPT_CONTINUE; } - if (!script_continue) - ssn->release_js_ctx(); + if (!alive_ctx(ssn)) + return; + + auto js_ctx = ssn->js_normalizer; + auto result = js_ctx->get_script(); + auto script_ptr = result.first; - if (buffer) + if (script_ptr) { - output.set(dst - buffer, (const uint8_t*)buffer, true); + auto script_len = result.second; + output.set(script_len, (const uint8_t*)script_ptr, true); trace_logf(1, http_trace, TRACE_JS_DUMP, nullptr, - "js_data[%zu]: %.*s\n", dst - buffer, static_cast(dst - buffer), buffer); + "js_data[%zu]: %.*s\n", script_len, static_cast(script_len), script_ptr); } + + if (!script_continue) + ssn->release_js_ctx(); } void HttpJsNorm::legacy_normalize(const Field& input, Field& output, HttpInfractions* infractions, diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 632a5f5b4..8910de6b5 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -42,6 +42,8 @@ add_library ( utils OBJECT sflsq.cc snort_bounds.h stats.cc + streambuf.cc + streambuf.h util.cc util_ber.cc util_cstring.cc diff --git a/src/utils/dev_notes.txt b/src/utils/dev_notes.txt index 1dcc7d957..7ecc6c546 100644 --- a/src/utils/dev_notes.txt +++ b/src/utils/dev_notes.txt @@ -1,3 +1,48 @@ This unit contains a mixed bag of legacy utilities that haven't found a home in any other directory. In many cases, the STL provides better options. + +On stream buffer, there are two classes inherited from std::streambuf: + +* istreambuf_glue class for reading operations +* ostreambuf_infl class for writing operations + +The input stream buffer presents a continuous sequence of bytes to the client, +gathered from different sources. For example: + + char* s1 = "world"; + char* s2 = "!"; + char* s3 = "Hello "; + +These sources being fed to the stream buffer as s3, s1, s2 will form +"Hello world!" sequence. + +In order to do that, istreambuf_glue class represents each source as a chunk of +data, which has its own position in the resulting sequence. +The chunk structure contains a pointer to the source, source size, and +the chunk's offset in the resulting sequence. + +Reading is done sequentially within the current chunk. When the end of chunk +reached, the buffer switches to the next one, setting std::streambuf pointers. + +Positioning the cursor is done in two steps: + +1. Calculate the final cursor position (absolute or by offset). + +2. Find the right chunk and local offset in it to set cursor there. + +Currently, no intermediate buffering done between chunks (like alignment, +prepending/appending the next chunk). The buffer doesn't take ownership over +the source's memory. + +The output stream buffer is mostly like std::stringbuf. The main purpose of it +is having an extensible dynamic array, where clients could write their data, +not worrying about resizing and memory management. + +Aside from that, ostreambuf_infl can give away ownership over its memory, +which could be useful for final consumer. + +From performance perspective, ostreambuf_infl can reserve an amount of memory +before actual operations. Also, memory extending is done by predefined +portions of 2^8^, 2^9^, 2^10^, 2^13^, 2^16^, 2^16^, 2^16^... +This tries to minimize the number of memory reallocation. diff --git a/src/utils/js_normalizer.cc b/src/utils/js_normalizer.cc index 04db5661c..eff85e64e 100644 --- a/src/utils/js_normalizer.cc +++ b/src/utils/js_normalizer.cc @@ -32,7 +32,6 @@ JSNormalizer::JSNormalizer(JSIdentifierCtxBase& js_ident_ctx, size_t norm_depth, rem_bytes(norm_depth), unlim(norm_depth == static_cast(-1)), src_next(nullptr), - dst_next(nullptr), tmp_buf(nullptr), tmp_buf_size(0), in(&in_buf), @@ -48,7 +47,7 @@ JSNormalizer::~JSNormalizer() tmp_buf_size = 0; } -JSTokenizer::JSRet JSNormalizer::normalize(const char* src, size_t src_len, char* dst, size_t dst_len) +JSTokenizer::JSRet JSNormalizer::normalize(const char* src, size_t src_len) { if (rem_bytes == 0 && !unlim) { @@ -56,7 +55,6 @@ JSTokenizer::JSRet JSNormalizer::normalize(const char* src, size_t src_len, char "depth limit reached\n"); src_next = src + src_len; - dst_next = dst; return JSTokenizer::EOS; } @@ -66,29 +64,49 @@ JSTokenizer::JSRet JSNormalizer::normalize(const char* src, size_t src_len, char debug_logf(4, http_trace, TRACE_JS_DUMP, nullptr, "tmp buffer[%zu]: %.*s\n", tmp_buf_size, static_cast(tmp_buf_size), tmp_buf); - in_buf.str(tmp_buf, tmp_buf_size, const_cast(src), len); - size_t w_bytes = out.tellp(); + in_buf.pubsetbuf(nullptr, 0) + ->pubsetbuf(tmp_buf, tmp_buf_size) + ->pubsetbuf(const_cast(src), len); + out_buf.reserve(src_len); JSTokenizer::JSRet ret = static_cast(tokenizer.yylex()); - - out_buf.sgetn(dst, dst_len); - in.clear(); out.clear(); - size_t r_bytes = in_buf.glued() ? static_cast(in.tellg()) : 0; - w_bytes = (size_t)out.tellp() - w_bytes; + size_t r_bytes = in_buf.last_chunk_offset(); if (!unlim) rem_bytes -= r_bytes; src_next = src + r_bytes; - // avoid heap overflow if number of written bytes bigger than accepted dst_len - dst_next = (w_bytes <= dst_len) ? dst + w_bytes : dst + dst_len; - return rem_bytes ? ret : JSTokenizer::EOS; } +std::pair JSNormalizer::get_script() +{ + streamsize len = 0; + char* dst = out_buf.release_data(len); + return {dst, len}; +} + +size_t JSNormalizer::peek_script_size() +{ + return out.tellp(); +} + +void JSNormalizer::prepend_script(const void* p , size_t n) +{ + if (p) + out_buf.sputn(reinterpret_cast(p), n); +} + size_t JSNormalizer::size() { return sizeof(JSNormalizer) + 16834; // the default YY_BUF_SIZE } + +#ifdef BENCHMARK_TEST +void JSNormalizer::rewind_output() +{ + out_buf.pubseekoff(0, ios_base::beg, ios_base::out); +} +#endif diff --git a/src/utils/js_normalizer.h b/src/utils/js_normalizer.h index a58a45d22..c4f30d0e7 100644 --- a/src/utils/js_normalizer.h +++ b/src/utils/js_normalizer.h @@ -25,95 +25,11 @@ #include #include "js_tokenizer.h" +#include "streambuf.h" namespace snort { -class gluebuf : public std::stringbuf -{ -public: - gluebuf() : - std::stringbuf(), once(true), - src1(nullptr), len1(0), src2(nullptr), len2(0) - { } - - std::streambuf* str(char* buf1, std::streamsize buf1_len, - char* buf2, std::streamsize buf2_len) - { - once = !(buf1 && buf1_len); - - if (once) - { - std::stringbuf::str(std::string(buf2, buf2_len)); - current_src_len = buf2_len; - } - else - { - std::stringbuf::str(std::string(buf1, buf1_len)); - current_src_len = buf1_len; - } - src1 = buf1; - len1 = buf1_len; - src2 = buf2; - len2 = buf2_len; - return this; - } - - bool glued() const - { - return once; - } - -protected: - virtual std::streampos seekoff(std::streamoff off, - std::ios_base::seekdir way, std::ios_base::openmode which) override - { - if (way != std::ios_base::end) - return std::stringbuf::seekoff(off, way, which); - - if (current_src_len + off < 0 and once) - { - debug_logf(6, http_trace, TRACE_JS_PROC, nullptr, - "seek offset %ld, %p:%zu => %p:%zu\n", - off, src2, len2, src1, len1); - - off += current_src_len; - once = false; - std::stringbuf::str(std::string(src1, len1)); - current_src_len = len1; - } - - return std::stringbuf::seekoff(off, way, which); - } - - virtual int underflow() override - { - if (once) - { - debug_log(6, http_trace, TRACE_JS_PROC, nullptr, - "underflow, no buffer to switch to\n"); - return EOF; - } - - debug_logf(6, http_trace, TRACE_JS_PROC, nullptr, - "underflow, %p:%zu => %p:%zu\n", - src1, len1, src2, len2); - - once = true; - std::stringbuf::str(std::string(src2, len2)); - current_src_len = len2; - return sgetc(); - } - -private: - bool once; - std::streamsize current_src_len; - char* src1; - std::streamsize len1; - char* src2; - std::streamsize len2; -}; - class JSNormalizer { public: @@ -124,28 +40,31 @@ public: const char* get_src_next() const { return src_next; } - char* get_dst_next() const // this can go beyond dst length, but no writing happens outside of dst - { return dst_next; } - void reset_depth() { rem_bytes = depth; } - JSTokenizer::JSRet normalize(const char* src, size_t src_len, char* dst, size_t dst_len); + JSTokenizer::JSRet normalize(const char* src, size_t src_len); + std::pair get_script(); + size_t peek_script_size(); + void prepend_script(const void*, size_t); static size_t size(); +#ifdef BENCHMARK_TEST + void rewind_output(); +#endif + private: size_t depth; size_t rem_bytes; bool unlim; const char* src_next; - char* dst_next; char* tmp_buf; size_t tmp_buf_size; - gluebuf in_buf; - std::stringbuf out_buf; + istreambuf_glue in_buf; + ostreambuf_infl out_buf; std::istream in; std::ostream out; JSTokenizer tokenizer; diff --git a/src/utils/streambuf.cc b/src/utils/streambuf.cc new file mode 100644 index 000000000..dee1b939a --- /dev/null +++ b/src/utils/streambuf.cc @@ -0,0 +1,394 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "streambuf.h" + +#include +#include + +using namespace snort; +using namespace std; + +#define max(x, y) std::max((streamsize)(x), (streamsize)(y)) +#define min(x, y) std::min((streamsize)(x), (streamsize)(y)) + +istreambuf_glue::istreambuf_glue() : + streambuf(), + chunks(), + cur_idx(0), + size(0) +{ + +} + +streamsize istreambuf_glue::last_chunk_offset() const +{ + auto c = gptr(); + auto b = eback(); + return last_chunk() ? c - b : 0; +} + +streambuf* istreambuf_glue::setbuf(char* s, streamsize n) +{ + n = max(0, n); + + if (!s || !n) + { + chunks.clear(); + cur_idx = 0; + size = 0; + return this; + } + + if (!size) + setg(s, s, s + n); + + chunks.emplace_back(Chunk(s, n, size)); + size += n; + + return this; +} + +streampos istreambuf_glue::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which) +{ + if (!(which & ios_base::in)) + return -1; + + if (chunks.empty()) + return -1; + + auto& c_chunk = chunks[cur_idx]; + auto c_chunk_off = get<2>(c_chunk); + auto c_off = c_chunk_off + gptr() - eback(); + + streampos pos; + + switch (way) + { + case ios_base::beg: pos = 0; break; + case ios_base::cur: pos = c_off; break; + case ios_base::end: pos = size; break; + default: + return -1; + } + + pos += off; + pos = max(0, pos); + pos = min(pos, size); + + size_t i = 0; + for (auto chunk : chunks) + { + auto ptr = get<0>(chunk); + auto len = get<1>(chunk); + auto b_off = get<2>(chunk); + auto e_off = b_off + len; + + if (b_off <= pos && + (pos < e_off || (pos == e_off && last_chunk(i)))) + { + setg(ptr, ptr - b_off + pos, ptr + len); + cur_idx = i; + break; + } + + i += 1; + } + + return pos; +} + +streampos istreambuf_glue::seekpos(streampos pos, ios_base::openmode which) +{ + if (!(which & ios_base::in)) + return -1; + + if (chunks.empty()) + return -1; + + pos = max(0, pos); + pos = min(pos, size); + + int i = 0; + for (auto chunk : chunks) + { + auto ptr = get<0>(chunk); + auto len = get<1>(chunk); + auto b_off = get<2>(chunk); + auto e_off = b_off + len; + + if (b_off <= pos && pos < e_off) + { + auto off = pos - b_off; + setg(ptr, ptr + off, ptr + len); + cur_idx = i; + break; + } + + i += 1; + } + + return pos; +} + +int istreambuf_glue::sync() +{ + return -1; +} + +streamsize istreambuf_glue::showmanyc() +{ + if (chunks.empty()) + return -1; + + auto& chunk = chunks[cur_idx]; + auto chunk_off = get<2>(chunk); + auto off = chunk_off + gptr() - eback(); + + return size - off; +} + +streamsize istreambuf_glue::xsgetn(char* s, streamsize n) +{ + assert(n >= 0); + + if (chunks.empty()) + return -1; + + streamsize r = 0; + + while (true) + { + streamsize l = min(egptr() - gptr(), n); + memcpy(s, gptr(), l); + gbump(l); + + s += l; + r += l; + n -= l; + + if (n <= 0 || last_chunk()) + break; + + cur_idx += 1; + + auto& chunk = chunks[cur_idx]; + auto ptr = get<0>(chunk); + auto len = get<1>(chunk); + + setg(ptr, ptr, ptr + len); + } + + return r; +} + +int istreambuf_glue::underflow() +{ + if (chunks.empty()) + return traits_type::eof(); + + if (last_chunk()) + return traits_type::eof(); + + cur_idx += 1; + + auto& chunk = chunks[cur_idx]; + auto ptr = get<0>(chunk); + auto len = get<1>(chunk); + + setg(ptr, ptr, ptr + len); + + return sgetc(); +} + +const ostreambuf_infl::State ostreambuf_infl::states[] = +{ + {states + 1, 1 << 8}, + {states + 2, 1 << 9}, + {states + 3, 1 << 10}, + {states + 4, 1 << 13}, + {states + 4, 1 << 16} +}; + +ostreambuf_infl::ostreambuf_infl() : + streambuf(), + gen(states[0]) +{ + +} + +ostreambuf_infl::~ostreambuf_infl() +{ + delete[] pbase(); +} + +void ostreambuf_infl::reserve(streamsize n) +{ + auto base = pbase(); + auto eptr = epptr(); + auto size = eptr - base; + + if (n > size) + enlarge(n - size); +} + +char* ostreambuf_infl::release_data(streamsize& n) +{ + auto data = pbase(); + + n = pptr() - data; + setp(nullptr, nullptr); + + gen.s = states[0].s; + gen.n = states[0].n; + + return data; +} + +streambuf* ostreambuf_infl::setbuf(char* s, streamsize n) +{ + n = min(n, size_limit); + + delete[] pbase(); + setp(s, s + max(0, n)); + return this; +} + +streampos ostreambuf_infl::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which) +{ + if (!(which & ios_base::out)) + return -1; + + auto base = pbase(); + auto ptr = pptr(); + auto eptr = epptr(); + auto size = eptr - base; + + streampos cpos = ptr - base; + streampos npos; + + switch (way) + { + case ios_base::beg: npos = 0; break; + case ios_base::cur: npos = ptr - base; break; + case ios_base::end: npos = eptr - base; break; + default: + return -1; + } + + npos += off; + npos = max(0, npos); + npos = min(npos, size); + + pbump(npos - cpos); + + return npos; +} + +streampos ostreambuf_infl::seekpos(streampos pos, ios_base::openmode which) +{ + if (!(which & ios_base::out)) + return -1; + + auto base = pbase(); + auto ptr = pptr(); + auto eptr = epptr(); + auto size = eptr - base; + + pos = max(0, pos); + pos = min(pos, size); + + streampos cpos = ptr - base; + pbump(pos - cpos); + + return pos; +} + +int ostreambuf_infl::sync() +{ + return -1; +} + +streamsize ostreambuf_infl::xsputn(const char* s, streamsize n) +{ + assert(n >= 0); + n = max(0, n); + + auto c_avail = epptr() - pptr(); + if (n > c_avail) + enlarge(n - c_avail); + + auto n_avail = epptr() - pptr(); + n = min(n, n_avail); + + memcpy(pptr(), s, n); + pbump(n); + + return n; +} + +int ostreambuf_infl::overflow(int c) +{ + if (traits_type::eof() == c) + return traits_type::eof(); + + if (!enlarge()) + return traits_type::eof(); + + *pptr() = c; + pbump(1); + + return c; +} + +bool ostreambuf_infl::enlarge() +{ + return enlarge(gen.get_next_size()); +} + +bool ostreambuf_infl::enlarge(streamsize extra_len) +{ + assert(extra_len > 0); + + auto c_pbase = pbase(); + auto c_pptr = pptr(); + auto c_epptr = epptr(); + auto c_size = c_epptr - c_pbase; + + auto n_size = c_size + extra_len; + n_size = min(n_size, size_limit); + + auto n_off = c_pptr - c_pbase; + auto n_pbase = new char[n_size]; + auto n_epptr = n_pbase ? n_pbase + n_size : nullptr; + + assert(c_pptr >= c_pbase); + + if (c_pbase && n_pbase) + memcpy(n_pbase, c_pbase, c_size); + + delete[] c_pbase; + setp(n_pbase, n_epptr); + pbump(n_off); + + return n_pbase != nullptr && n_epptr > n_pbase; +} diff --git a/src/utils/streambuf.h b/src/utils/streambuf.h new file mode 100644 index 000000000..674a5b498 --- /dev/null +++ b/src/utils/streambuf.h @@ -0,0 +1,111 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +#ifndef STREAMBUF +#define STREAMBUF + +#include +#include +#include +#include + +namespace snort +{ + +// an input stream over set of buffers, +// the buffer doesn't take ownership over the memory, +// no intermediate buffering between chunks +class istreambuf_glue : public std::streambuf +{ +public: + istreambuf_glue(); + virtual ~istreambuf_glue() override = default; + + std::streamsize last_chunk_offset() const; + +protected: + // a valid s/n pair continues the chain, nullptr or zero size starts new one + virtual std::streambuf* setbuf(char* s, std::streamsize n) override; + virtual std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override; + virtual std::streampos seekpos(std::streampos sp, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override; + virtual int sync() override; + virtual std::streamsize showmanyc() override; + virtual std::streamsize xsgetn(char* s, std::streamsize n) override; + virtual int underflow() override; + + bool last_chunk() const + { return cur_idx + 1 >= chunks.size(); } + + bool last_chunk(size_t idx) const + { return idx + 1 >= chunks.size(); } + + typedef std::tuple Chunk; + typedef std::vector Chunks; + + Chunks chunks; + size_t cur_idx; + std::streamsize size; +}; + +// an output stream over extensible array +class ostreambuf_infl : public std::streambuf +{ +public: + static constexpr size_t size_limit = 1 << 20; + + ostreambuf_infl(); + virtual ~ostreambuf_infl() override; + + // reserve more memory for the current buffer, keeping data + void reserve(std::streamsize n); + + // releases the current buffer, + // the caller takes ownership over the buffer + char* release_data(std::streamsize& n); + +protected: + virtual std::streambuf* setbuf(char* s, std::streamsize n) override; + virtual std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override; + virtual std::streampos seekpos(std::streampos sp, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override; + virtual int sync() override; + virtual std::streamsize xsputn(const char* s, std::streamsize n) override; + virtual int overflow(int c = EOF) override; + + bool enlarge(); + bool enlarge(std::streamsize extra_len); + + struct State + { + const State* s; + std::streamsize n; + + std::streamsize get_next_size() + { auto r = n; n = s->n; s = s->s; return r; } + }; + + static const State states[]; + State gen; +}; + +} + +#endif diff --git a/src/utils/test/CMakeLists.txt b/src/utils/test/CMakeLists.txt index 2a092f323..cff4cd399 100644 --- a/src/utils/test/CMakeLists.txt +++ b/src/utils/test/CMakeLists.txt @@ -15,6 +15,7 @@ add_catch_test( js_normalizer_test ${FLEX_js_tokenizer_OUTPUTS} ../js_identifier_ctx.cc ../js_normalizer.cc + ../streambuf.cc ../util_cstring.cc ) @@ -23,3 +24,7 @@ add_catch_test( js_identifier_ctx_test ../js_identifier_ctx.cc ) +add_catch_test( streambuf_test + SOURCES + ../streambuf.cc +) diff --git a/src/utils/test/js_normalizer_test.cc b/src/utils/test/js_normalizer_test.cc index e393fe7d0..ae750328a 100644 --- a/src/utils/test/js_normalizer_test.cc +++ b/src/utils/test/js_normalizer_test.cc @@ -65,58 +65,74 @@ using namespace snort; #define DST_SIZE 512 -#define NORMALIZE(src, expected) \ - char dst[sizeof(expected)]; \ +#define NORMALIZE(src) \ JSIdentifierCtxTest ident_ctx; \ JSNormalizer norm(ident_ctx, DEPTH, MAX_TEMPLATE_NESTNIG); \ - auto ret = norm.normalize(src, sizeof(src), dst, sizeof(dst)); \ + auto ret = norm.normalize(src, sizeof(src)); \ const char* ptr = norm.get_src_next(); \ - int act_len = norm.get_dst_next() - dst; + auto result = norm.get_script(); \ + char* dst = result.first; \ + int act_len = result.second; \ #define VALIDATE(src, expected) \ CHECK(ret == JSTokenizer::SCRIPT_CONTINUE); \ CHECK((ptr - src) == sizeof(src)); \ CHECK(act_len == sizeof(expected) - 1); \ - CHECK(!memcmp(dst, expected, act_len)); + CHECK(!memcmp(dst, expected, act_len)); \ + delete[] dst; #define VALIDATE_FAIL(src, expected, ret_code, ptr_offset) \ CHECK(ret == ret_code); \ CHECK((ptr - src) == ptr_offset); \ CHECK(act_len == sizeof(expected) - 1); \ - CHECK(!memcmp(dst, expected, act_len)); + CHECK(!memcmp(dst, expected, act_len)); \ + delete[] dst; + #define NORMALIZE_L(src, src_len, dst, dst_len, depth, ret, ptr, len) \ { \ JSIdentifierCtxTest ident_ctx; \ JSNormalizer norm(ident_ctx, depth, MAX_TEMPLATE_NESTNIG); \ - ret = norm.normalize(src, src_len, dst, dst_len); \ + ret = norm.normalize(src, src_len); \ ptr = norm.get_src_next(); \ - len = norm.get_dst_next() - dst; \ + auto result = norm.get_script(); \ + char* dptr = result.first; \ + len = result.second; \ + REQUIRE(len == dst_len); \ + memcpy(dst, dptr, dst_len); \ + delete[] dptr; \ } #define DO(src, slen, dst, dlen) \ { \ - auto ret = norm.normalize(src, slen, dst, dlen); \ + auto ret = norm.normalize(src, slen); \ CHECK(ret == JSTokenizer::SCRIPT_CONTINUE); \ auto nsrc = norm.get_src_next(); \ - auto ndst = norm.get_dst_next(); \ + auto result = norm.get_script(); \ + char* ptr = result.first; \ + int act_len = result.second; \ REQUIRE(nsrc - src == slen); \ - REQUIRE(ndst - dst == dlen); \ + REQUIRE(act_len == dlen); \ + memcpy(dst, ptr, dlen); \ + delete[] ptr; \ } #define TRY(src, slen, dst, dlen, rexp) \ { \ - auto ret = norm.normalize(src, slen, dst, dlen); \ + auto ret = norm.normalize(src, slen); \ CHECK(ret == rexp); \ - auto ndst = norm.get_dst_next(); \ - REQUIRE(ndst - dst == dlen); \ + auto result = norm.get_script(); \ + char* ptr = result.first; \ + int act_len = result.second; \ + REQUIRE(act_len == dlen); \ + memcpy(dst, ptr, dlen); \ + delete[] ptr; \ } #define CLOSE() \ { \ const char end[] = ""; \ - char dst[DST_SIZE]; \ - auto ret = norm.normalize(end, sizeof(end) - 1, dst, sizeof(dst) - 1); \ + auto ret = norm.normalize(end, sizeof(end) - 1); \ CHECK(ret == JSTokenizer::SCRIPT_ENDED); \ } @@ -331,78 +347,78 @@ TEST_CASE("clamav tests", "[JSNormalizer]") { SECTION("test_case_0 - mixed identifiers and comments") { - NORMALIZE(clamav_buf0, clamav_expected0); + NORMALIZE(clamav_buf0); VALIDATE(clamav_buf0, clamav_expected0); } SECTION("test_case_1 - escaped unicode in identifier") { - NORMALIZE(clamav_buf1, clamav_expected1); + NORMALIZE(clamav_buf1); VALIDATE(clamav_buf1, clamav_expected1); } SECTION("test_case_2 - accumulated string assignment") { - NORMALIZE(clamav_buf2, clamav_expected2); + NORMALIZE(clamav_buf2); VALIDATE(clamav_buf2, clamav_expected2); } SECTION("test_case_3 - percent-encoded string") { - NORMALIZE(clamav_buf3, clamav_expected3); + NORMALIZE(clamav_buf3); VALIDATE(clamav_buf3, clamav_expected3); } SECTION("test_case_4 - percent-encoded string") { - NORMALIZE(clamav_buf4, clamav_expected4); + NORMALIZE(clamav_buf4); VALIDATE(clamav_buf4, clamav_expected4); } SECTION("test_case_5 - obfuscated script") { - NORMALIZE(clamav_buf5, clamav_expected5); + NORMALIZE(clamav_buf5); VALIDATE(clamav_buf5, clamav_expected5); } SECTION("test_case_6 - obfuscated script") { - NORMALIZE(clamav_buf6, clamav_expected6); + NORMALIZE(clamav_buf6); VALIDATE(clamav_buf6, clamav_expected6); } SECTION("test_case_7 - single quotes string") { - NORMALIZE(clamav_buf7, clamav_expected7); + NORMALIZE(clamav_buf7); VALIDATE(clamav_buf7, clamav_expected7); } SECTION("test_case_8 - double quotes string") { - NORMALIZE(clamav_buf8, clamav_expected8); + NORMALIZE(clamav_buf8); VALIDATE(clamav_buf8, clamav_expected8); } SECTION("test_case_9 - obfuscated script") { - NORMALIZE(clamav_buf9, clamav_expected9); + NORMALIZE(clamav_buf9); VALIDATE(clamav_buf9, clamav_expected9); } SECTION("test_case_10 - obfuscated script") { - NORMALIZE(clamav_buf10, clamav_expected10); + NORMALIZE(clamav_buf10); VALIDATE(clamav_buf10, clamav_expected10); } SECTION("test_case_11 - integer literal") { - NORMALIZE(clamav_buf11, clamav_expected11); + NORMALIZE(clamav_buf11); VALIDATE(clamav_buf11, clamav_expected11); } SECTION("test_case_12 - escaped unicode in string literal") { - NORMALIZE(clamav_buf12, clamav_expected12); + NORMALIZE(clamav_buf12); VALIDATE(clamav_buf12, clamav_expected12); } // FIXIT-L this should be revisited SECTION("test_case_13 - invalid escape sequence") { - NORMALIZE(clamav_buf13, clamav_expected13); + NORMALIZE(clamav_buf13); VALIDATE(clamav_buf13, clamav_expected13); } SECTION("test_case_14 - EOF in the middle of string literal") { - NORMALIZE(clamav_buf14, clamav_expected14); + NORMALIZE(clamav_buf14); // trailing \0 is included as a part of the string // to utilize available macros we alter the read length act_len -= 1; @@ -479,12 +495,12 @@ TEST_CASE("all patterns", "[JSNormalizer]") { SECTION("whitespaces and special characters") { - NORMALIZE(all_patterns_buf0, all_patterns_expected0); + NORMALIZE(all_patterns_buf0); VALIDATE(all_patterns_buf0, all_patterns_expected0); } SECTION("comments") { - NORMALIZE(all_patterns_buf1, all_patterns_expected1); + NORMALIZE(all_patterns_buf1); VALIDATE(all_patterns_buf1, all_patterns_expected1); } SECTION("directives") @@ -499,11 +515,11 @@ TEST_CASE("all patterns", "[JSNormalizer]") const char expected1[] = "\"use strict\";var a=1;"; const char expected2[] = "var a=1 'use strict';"; - char dst0[sizeof(expected0)]; - char dst1[sizeof(expected1)]; - char dst2[sizeof(expected0)]; - char dst3[sizeof(expected1)]; - char dst4[sizeof(expected2)]; + char dst0[sizeof(expected0) - 1]; + char dst1[sizeof(expected1) - 1]; + char dst2[sizeof(expected0) - 1]; + char dst3[sizeof(expected1) - 1]; + char dst4[sizeof(expected2) - 1]; int ret0, ret1, ret2, ret3, ret4; const char *ptr0, *ptr1, *ptr2, *ptr3, *ptr4; @@ -542,27 +558,27 @@ TEST_CASE("all patterns", "[JSNormalizer]") } SECTION("punctuators") { - NORMALIZE(all_patterns_buf2, all_patterns_expected2); + NORMALIZE(all_patterns_buf2); VALIDATE(all_patterns_buf2, all_patterns_expected2); } SECTION("keywords") { - NORMALIZE(all_patterns_buf3, all_patterns_expected3); + NORMALIZE(all_patterns_buf3); VALIDATE(all_patterns_buf3, all_patterns_expected3); } SECTION("literals") { - NORMALIZE(all_patterns_buf4, all_patterns_expected4); + NORMALIZE(all_patterns_buf4); VALIDATE(all_patterns_buf4, all_patterns_expected4); } SECTION("identifiers") { - NORMALIZE(all_patterns_buf5, all_patterns_expected5); + NORMALIZE(all_patterns_buf5); VALIDATE(all_patterns_buf5, all_patterns_expected5); } SECTION("template literals") { - NORMALIZE(all_patterns_buf6, all_patterns_expected6); + NORMALIZE(all_patterns_buf6); VALIDATE(all_patterns_buf6, all_patterns_expected6); } } @@ -888,82 +904,82 @@ TEST_CASE("syntax cases", "[JSNormalizer]") { SECTION("variables") { - NORMALIZE(syntax_cases_buf0, syntax_cases_expected0); + NORMALIZE(syntax_cases_buf0); VALIDATE(syntax_cases_buf0, syntax_cases_expected0); } SECTION("operators") { - NORMALIZE(syntax_cases_buf1, syntax_cases_expected1); + NORMALIZE(syntax_cases_buf1); VALIDATE(syntax_cases_buf1, syntax_cases_expected1); } SECTION("arithmetic and logical operators") { - NORMALIZE(syntax_cases_buf2, syntax_cases_expected2); + NORMALIZE(syntax_cases_buf2); VALIDATE(syntax_cases_buf2, syntax_cases_expected2); } SECTION("complex object") { - NORMALIZE(syntax_cases_buf3, syntax_cases_expected3); + NORMALIZE(syntax_cases_buf3); VALIDATE(syntax_cases_buf3, syntax_cases_expected3); } SECTION("arrays") { - NORMALIZE(syntax_cases_buf4, syntax_cases_expected4); + NORMALIZE(syntax_cases_buf4); VALIDATE(syntax_cases_buf4, syntax_cases_expected4); } SECTION("loops") { - NORMALIZE(syntax_cases_buf5, syntax_cases_expected5); + NORMALIZE(syntax_cases_buf5); VALIDATE(syntax_cases_buf5, syntax_cases_expected5); } SECTION("if-else and switch statements") { - NORMALIZE(syntax_cases_buf6, syntax_cases_expected6); + NORMALIZE(syntax_cases_buf6); VALIDATE(syntax_cases_buf6, syntax_cases_expected6); } SECTION("try-catch statements") { - NORMALIZE(syntax_cases_buf7, syntax_cases_expected7); + NORMALIZE(syntax_cases_buf7); VALIDATE(syntax_cases_buf7, syntax_cases_expected7); } SECTION("functions and promises") { - NORMALIZE(syntax_cases_buf8, syntax_cases_expected8); + NORMALIZE(syntax_cases_buf8); VALIDATE(syntax_cases_buf8, syntax_cases_expected8); } SECTION("regex-division ambiguity") { - NORMALIZE(syntax_cases_buf9, syntax_cases_expected9); + NORMALIZE(syntax_cases_buf9); VALIDATE(syntax_cases_buf9, syntax_cases_expected9); } SECTION("regex on a new line") { - NORMALIZE(syntax_cases_buf10, syntax_cases_expected10); + NORMALIZE(syntax_cases_buf10); VALIDATE(syntax_cases_buf10, syntax_cases_expected10); } SECTION("string and regex literals ambiguity with escaped sentinel chars") { - NORMALIZE(syntax_cases_buf11, syntax_cases_expected11); + NORMALIZE(syntax_cases_buf11); VALIDATE(syntax_cases_buf11, syntax_cases_expected11); } SECTION("escaped LF and CR chars in literals") { - NORMALIZE(syntax_cases_buf12, syntax_cases_expected12); + NORMALIZE(syntax_cases_buf12); VALIDATE(syntax_cases_buf12, syntax_cases_expected12); } SECTION("regex after keyword") { - NORMALIZE(syntax_cases_buf13, syntax_cases_expected13); + NORMALIZE(syntax_cases_buf13); VALIDATE(syntax_cases_buf13, syntax_cases_expected13); } SECTION("white space between '+'<-->'++' and '-'<-->'--'") { - NORMALIZE(syntax_cases_buf14, syntax_cases_expected14); + NORMALIZE(syntax_cases_buf14); VALIDATE(syntax_cases_buf14, syntax_cases_expected14); } SECTION("template literals") { - NORMALIZE(syntax_cases_buf22, syntax_cases_expected22); + NORMALIZE(syntax_cases_buf22); VALIDATE(syntax_cases_buf22, syntax_cases_expected22); } } @@ -972,37 +988,37 @@ TEST_CASE("bad tokens", "[JSNormalizer]") { SECTION("LS chars within literal") { - NORMALIZE(syntax_cases_buf15, syntax_cases_expected15); + NORMALIZE(syntax_cases_buf15); VALIDATE_FAIL(syntax_cases_buf15, syntax_cases_expected15, JSTokenizer::BAD_TOKEN, 25); } SECTION("PS chars within literal") { - NORMALIZE(syntax_cases_buf21, syntax_cases_expected21); + NORMALIZE(syntax_cases_buf21); VALIDATE_FAIL(syntax_cases_buf21, syntax_cases_expected21, JSTokenizer::BAD_TOKEN, 25); } SECTION("explicit LF within literal") { - NORMALIZE(syntax_cases_buf16, syntax_cases_expected16); + NORMALIZE(syntax_cases_buf16); VALIDATE_FAIL(syntax_cases_buf16, syntax_cases_expected16, JSTokenizer::BAD_TOKEN, 23); } SECTION("explicit CR within literal") { - NORMALIZE(syntax_cases_buf17, syntax_cases_expected17); + NORMALIZE(syntax_cases_buf17); VALIDATE_FAIL(syntax_cases_buf17, syntax_cases_expected17, JSTokenizer::BAD_TOKEN, 23); } SECTION("escaped LF-CR sequence within literal") { - NORMALIZE(syntax_cases_buf18, syntax_cases_expected18); + NORMALIZE(syntax_cases_buf18); VALIDATE_FAIL(syntax_cases_buf18, syntax_cases_expected18, JSTokenizer::BAD_TOKEN, 25); } SECTION("escaped LF within regex literal") { - NORMALIZE(syntax_cases_buf19, syntax_cases_expected19); + NORMALIZE(syntax_cases_buf19); VALIDATE_FAIL(syntax_cases_buf19, syntax_cases_expected19, JSTokenizer::BAD_TOKEN, 23); } SECTION("escaped CR-LF within regex literal") { - NORMALIZE(syntax_cases_buf20, syntax_cases_expected20); + NORMALIZE(syntax_cases_buf20); VALIDATE_FAIL(syntax_cases_buf20, syntax_cases_expected20, JSTokenizer::BAD_TOKEN, 23); } } @@ -1011,7 +1027,7 @@ TEST_CASE("template literal overflow", "[JSNormalizer]") { SECTION("exceeding template literal limit") { - NORMALIZE(syntax_cases_buf23, syntax_cases_expected23); + NORMALIZE(syntax_cases_buf23); VALIDATE_FAIL(syntax_cases_buf23, syntax_cases_expected23, JSTokenizer::TEMPLATE_NESTING_OVERFLOW, 15); } @@ -1028,7 +1044,7 @@ TEST_CASE("endings", "[JSNormalizer]") "var c = 3 ;\n"; const int ptr_offset = 33; const char expected[] = "var a=1;var b=2;"; - char dst[sizeof(expected)]; + char dst[sizeof(expected) - 1]; int act_len; const char* ptr; int ret; @@ -1045,45 +1061,33 @@ TEST_CASE("endings", "[JSNormalizer]") const char src[] = "var abc = 123;\n\r"; const char src2[] = "var foo = 321;\n\r"; const char expected[] = "var abc"; - char dst[sizeof(src)]; - int act_len; const char* ptr; int ret; JSIdentifierCtxTest ident_ctx; JSNormalizer norm(ident_ctx, 7, MAX_TEMPLATE_NESTNIG); - ret = norm.normalize(src, sizeof(src), dst, sizeof(dst)); + ret = norm.normalize(src, sizeof(src)); ptr = norm.get_src_next(); - act_len = norm.get_dst_next() - dst; + auto res1 = norm.get_script(); + char* dst1 = res1.first; + int act_len1 = res1.second; CHECK(ret == JSTokenizer::EOS); CHECK(ptr == src + 7); - CHECK(act_len == sizeof(expected) - 1); - CHECK(!memcmp(dst, expected, act_len)); + CHECK(act_len1 == sizeof(expected) - 1); + CHECK(!memcmp(dst1, expected, act_len1)); + delete[] dst1; - ret = norm.normalize(src2, sizeof(src2), dst, sizeof(dst)); + ret = norm.normalize(src2, sizeof(src2)); ptr = norm.get_src_next(); - act_len = norm.get_dst_next() - dst; + auto res2 = norm.get_script(); + char* dst2 = res2.first; + int act_len2 = res2.second; CHECK(ret == JSTokenizer::EOS); CHECK(ptr == src2 + sizeof(src2)); - CHECK(act_len == 0); - } - SECTION("dst size is less then src size") - { - const char src[] = "var abc = 123;\n\r"; - const char expected[sizeof(src)] = "var abc"; - char dst[7]; - int act_len; - const char* ptr; - int ret; - - NORMALIZE_L(src, sizeof(src), dst, sizeof(dst), DEPTH, ret, ptr, act_len); - - CHECK(ret == JSTokenizer::SCRIPT_CONTINUE); - CHECK(ptr == src + sizeof(src)); - CHECK(act_len == 7); // size of normalized src - CHECK(!memcmp(dst, expected, sizeof(dst))); + CHECK(act_len2 == 0); + delete[] dst2; } } @@ -1295,127 +1299,127 @@ TEST_CASE("nested script tags", "[JSNormalizer]") { SECTION("explicit open tag - simple") { - NORMALIZE(unexpected_tag_buf0, unexpected_tag_expected0); + NORMALIZE(unexpected_tag_buf0); VALIDATE_FAIL(unexpected_tag_buf0, unexpected_tag_expected0, JSTokenizer::OPENING_TAG, 18); } SECTION("explicit open tag - complex") { - NORMALIZE(unexpected_tag_buf1, unexpected_tag_expected1); + NORMALIZE(unexpected_tag_buf1); VALIDATE_FAIL(unexpected_tag_buf1, unexpected_tag_expected1, JSTokenizer::OPENING_TAG, 18); } SECTION("open tag within literal - start") { - NORMALIZE(unexpected_tag_buf2, unexpected_tag_expected2); + NORMALIZE(unexpected_tag_buf2); VALIDATE_FAIL(unexpected_tag_buf2, unexpected_tag_expected2, JSTokenizer::OPENING_TAG, 29); } SECTION("open tag within literal - mid") { - NORMALIZE(unexpected_tag_buf3, unexpected_tag_expected3); + NORMALIZE(unexpected_tag_buf3); VALIDATE_FAIL(unexpected_tag_buf3, unexpected_tag_expected3, JSTokenizer::OPENING_TAG, 39); } SECTION("open tag within literal - end") { - NORMALIZE(unexpected_tag_buf4, unexpected_tag_expected4); + NORMALIZE(unexpected_tag_buf4); VALIDATE_FAIL(unexpected_tag_buf4, unexpected_tag_expected4, JSTokenizer::OPENING_TAG, 39); } SECTION("close tag within literal - start") { - NORMALIZE(unexpected_tag_buf5, unexpected_tag_expected5); + NORMALIZE(unexpected_tag_buf5); VALIDATE_FAIL(unexpected_tag_buf5, unexpected_tag_expected5, JSTokenizer::CLOSING_TAG, 31); } SECTION("close tag within literal - mid") { - NORMALIZE(unexpected_tag_buf6, unexpected_tag_expected6); + NORMALIZE(unexpected_tag_buf6); VALIDATE_FAIL(unexpected_tag_buf6, unexpected_tag_expected6, JSTokenizer::CLOSING_TAG, 41); } SECTION("close tag within literal - end") { - NORMALIZE(unexpected_tag_buf7, unexpected_tag_expected7); + NORMALIZE(unexpected_tag_buf7); VALIDATE_FAIL(unexpected_tag_buf7, unexpected_tag_expected7, JSTokenizer::CLOSING_TAG, 41); } SECTION("open tag within literal - escaped") { - NORMALIZE(unexpected_tag_buf8, unexpected_tag_expected8); + NORMALIZE(unexpected_tag_buf8); VALIDATE_FAIL(unexpected_tag_buf8, unexpected_tag_expected8, JSTokenizer::OPENING_TAG, 40); } SECTION("close tag within literal - escaped") { - NORMALIZE(unexpected_tag_buf9, unexpected_tag_expected9); + NORMALIZE(unexpected_tag_buf9); VALIDATE(unexpected_tag_buf9, unexpected_tag_expected9); } SECTION("open tag within single-line comment - start") { - NORMALIZE(unexpected_tag_buf10, unexpected_tag_expected10); + NORMALIZE(unexpected_tag_buf10); VALIDATE_FAIL(unexpected_tag_buf10, unexpected_tag_expected10, JSTokenizer::OPENING_TAG, 20); } SECTION("open tag within single-line comment - mid") { - NORMALIZE(unexpected_tag_buf11, unexpected_tag_expected11); + NORMALIZE(unexpected_tag_buf11); VALIDATE_FAIL(unexpected_tag_buf11, unexpected_tag_expected11, JSTokenizer::OPENING_TAG, 30); } SECTION("open tag within single-line comment - end") { - NORMALIZE(unexpected_tag_buf12, unexpected_tag_expected12); + NORMALIZE(unexpected_tag_buf12); VALIDATE_FAIL(unexpected_tag_buf12, unexpected_tag_expected12, JSTokenizer::OPENING_TAG, 30); } SECTION("open tag within multi-line comment - start") { - NORMALIZE(unexpected_tag_buf13, unexpected_tag_expected13); + NORMALIZE(unexpected_tag_buf13); VALIDATE_FAIL(unexpected_tag_buf13, unexpected_tag_expected13, JSTokenizer::OPENING_TAG, 20); } SECTION("open tag within multi-line comment - mid") { - NORMALIZE(unexpected_tag_buf14, unexpected_tag_expected14); + NORMALIZE(unexpected_tag_buf14); VALIDATE_FAIL(unexpected_tag_buf14, unexpected_tag_expected14, JSTokenizer::OPENING_TAG, 30); } SECTION("open tag within multi-line comment - end") { - NORMALIZE(unexpected_tag_buf15, unexpected_tag_expected15); + NORMALIZE(unexpected_tag_buf15); VALIDATE_FAIL(unexpected_tag_buf15, unexpected_tag_expected15, JSTokenizer::OPENING_TAG, 30); } SECTION("close tag within single-line comment - start") { - NORMALIZE(unexpected_tag_buf16, unexpected_tag_expected16); + NORMALIZE(unexpected_tag_buf16); VALIDATE_FAIL(unexpected_tag_buf16, unexpected_tag_expected16, JSTokenizer::CLOSING_TAG, 22); } SECTION("close tag within single-line comment - mid") { - NORMALIZE(unexpected_tag_buf17, unexpected_tag_expected17); + NORMALIZE(unexpected_tag_buf17); VALIDATE_FAIL(unexpected_tag_buf17, unexpected_tag_expected17, JSTokenizer::CLOSING_TAG, 34); } SECTION("close tag within single-line comment - end") { - NORMALIZE(unexpected_tag_buf18, unexpected_tag_expected18); + NORMALIZE(unexpected_tag_buf18); VALIDATE_FAIL(unexpected_tag_buf18, unexpected_tag_expected18, JSTokenizer::CLOSING_TAG, 32); } SECTION("close tag within multi-line comment - start") { - NORMALIZE(unexpected_tag_buf19, unexpected_tag_expected19); + NORMALIZE(unexpected_tag_buf19); VALIDATE_FAIL(unexpected_tag_buf19, unexpected_tag_expected19, JSTokenizer::CLOSING_TAG, 22); } SECTION("close tag within multi-line comment - mid") { - NORMALIZE(unexpected_tag_buf20, unexpected_tag_expected20); + NORMALIZE(unexpected_tag_buf20); VALIDATE_FAIL(unexpected_tag_buf20, unexpected_tag_expected20, JSTokenizer::CLOSING_TAG, 32); } SECTION("close tag within multi-line comment - end") { - NORMALIZE(unexpected_tag_buf21, unexpected_tag_expected21); + NORMALIZE(unexpected_tag_buf21); VALIDATE_FAIL(unexpected_tag_buf21, unexpected_tag_expected21, JSTokenizer::CLOSING_TAG, 32); } SECTION("multiple patterns - not matched") { - NORMALIZE(unexpected_tag_buf22, unexpected_tag_expected22); + NORMALIZE(unexpected_tag_buf22); VALIDATE(unexpected_tag_buf22, unexpected_tag_expected22); } SECTION("multiple patterns - matched") { - NORMALIZE(unexpected_tag_buf23, unexpected_tag_expected23); + NORMALIZE(unexpected_tag_buf23); VALIDATE_FAIL(unexpected_tag_buf23, unexpected_tag_expected23, JSTokenizer::OPENING_TAG, 65); } SECTION("mixed lower and upper case") { - NORMALIZE(unexpected_tag_buf24, unexpected_tag_expected24); + NORMALIZE(unexpected_tag_buf24); VALIDATE_FAIL(unexpected_tag_buf24, unexpected_tag_expected24, JSTokenizer::OPENING_TAG, 39); } } @@ -1909,15 +1913,18 @@ TEST_CASE("benchmarking - ::normalize() - literals", "[JSNormalizer]") }; BENCHMARK("whitespaces - 65535 bytes") { - return normalizer.normalize(src_ws, src_ws_len, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_ws, src_ws_len); }; BENCHMARK("block comment - 65535 bytes") { - return normalizer.normalize(src_bcomm, src_bcomm_len, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_bcomm, src_bcomm_len); }; BENCHMARK("double quotes string - 65535 bytes") { - return normalizer.normalize(src_dqstr, src_dqstr_len, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_dqstr, src_dqstr_len); }; constexpr size_t depth_8k = 8192; @@ -1932,15 +1939,18 @@ TEST_CASE("benchmarking - ::normalize() - literals", "[JSNormalizer]") }; BENCHMARK("whitespaces - 8192 bytes") { - return normalizer.normalize(src_ws_8k, src_ws_len_8k, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_ws_8k, src_ws_len_8k); }; BENCHMARK("block comment - 8192 bytes") { - return normalizer.normalize(src_bcomm_8k, src_bcomm_len_8k, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_bcomm_8k, src_bcomm_len_8k); }; BENCHMARK("double quotes string - 8192 bytes") { - return normalizer.normalize(src_dqstr_8k, src_dqstr_len_8k, dst, DEPTH); + normalizer.rewind_output(); + return normalizer.normalize(src_dqstr_8k, src_dqstr_len_8k); }; } @@ -1956,14 +1966,13 @@ TEST_CASE("benchmarking - ::normalize() - identifiers") const char* src = input.c_str(); size_t src_len = input.size(); - char dst[DEPTH]; - JSIdentifierCtxTest ident_ctx_mock; JSNormalizer normalizer_wo_ident(ident_ctx_mock, UNLIM_DEPTH, MAX_TEMPLATE_NESTNIG); BENCHMARK("without substitution") { - return normalizer_wo_ident.normalize(src, src_len, dst, DEPTH); + normalizer_wo_ident.rewind_output(); + return normalizer_wo_ident.normalize(src, src_len); }; JSIdentifierCtx ident_ctx(DEPTH); @@ -1971,9 +1980,9 @@ TEST_CASE("benchmarking - ::normalize() - identifiers") BENCHMARK("with substitution") { - return normalizer_w_ident.normalize(src, src_len, dst, DEPTH); + normalizer_w_ident.rewind_output(); + return normalizer_w_ident.normalize(src, src_len); }; } #endif // BENCHMARK_TEST - diff --git a/src/utils/test/streambuf_test.cc b/src/utils/test/streambuf_test.cc new file mode 100644 index 000000000..65778366e --- /dev/null +++ b/src/utils/test/streambuf_test.cc @@ -0,0 +1,2031 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "catch/catch.hpp" + +#include +#include +#include + +#include "utils/streambuf.h" + +using namespace snort; +using namespace std; + +#define ACT_SIZE 1024 +#define EXP_SIZE 4096 + +#define EXP_AVAIL1(b, exp, len, off) \ + { \ + auto avail = (b).in_avail(); \ + CHECK(avail == (len) - (off)); \ + \ + char act[ACT_SIZE]; \ + auto n = (b).sgetn(act, (len)); \ + REQUIRE(n == (len) - (off)); \ + CHECK(!memcmp((exp) + (off), act, n)); \ + } + +#define EXP_AVAIL2(b, exp, len, off, len_1c) \ + { \ + auto avail = (b).in_avail(); \ + if ((off) < (len_1c)) \ + CHECK(avail == (len_1c) - (off)); \ + else \ + CHECK(avail == (len) - (off)); \ + \ + char act[ACT_SIZE]; \ + auto n = (b).sgetn(act, (len)); \ + REQUIRE(n == (len) - (off)); \ + CHECK(!memcmp((exp) + (off), act, n)); \ + } + +#define EXP_AVAILn(b, exp, len, off) \ + { \ + char act[ACT_SIZE]; \ + auto n = (b).sgetn(act, (len)); \ + REQUIRE(n == (len) - (off)); \ + CHECK(!memcmp((exp) + (off), act, n)); \ + } + +#define EXP_RES(b, exp, exp_len, exp_mem_size) \ + { \ + streamsize act_len; \ + char* act = (b).release_data(act_len); \ + \ + CHECK((exp_mem_size) == act_len); \ + REQUIRE((exp_len) <= act_len); \ + CHECK(!memcmp((exp), act, (exp_len))); \ + delete[] act; \ + } + +#define EXP_IN(s, exp, act, len) \ + { \ + CHECK(true == (s).good()); \ + REQUIRE((len) == (s).tellg()); \ + CHECK(!memcmp((exp), (act), (len))); \ + } + +#define EOF_IN(s, exp, act, len) \ + { \ + CHECK(true == (s).eof()); \ + (s).clear(); \ + REQUIRE((len) == (s).tellg()); \ + CHECK(!memcmp((exp), (act), (len))); \ + } + +#define EXP_OUT(s, exp, exp_len) \ + { \ + CHECK(false == (s).fail()); \ + CHECK(false == (s).bad()); \ + (s).clear(); \ + CHECK((exp_len) == (s).tellp()); \ + \ + ostreambuf_infl* b = reinterpret_cast((s).rdbuf()); \ + streamsize act_len; \ + char* act = b->release_data(act_len); \ + \ + REQUIRE((exp_len) == act_len); \ + CHECK(!memcmp((exp), act, (exp_len))); \ + delete[] act; \ + } + +#define EOF_OUT(s, exp, exp_len) \ + { \ + CHECK(true == (s).fail()); \ + CHECK(true == (s).bad()); \ + (s).clear(); \ + CHECK((exp_len) == (s).tellp()); \ + \ + ostreambuf_infl* b = reinterpret_cast((s).rdbuf()); \ + streamsize act_len; \ + char* act = b->release_data(act_len); \ + \ + REQUIRE((exp_len) == act_len); \ + CHECK(!memcmp((exp), act, (exp_len))); \ + delete[] act; \ + } + +TEST_CASE("input buffer - basic one source", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + char dat[] = "Early bird gets a corn."; + + SECTION("no data") + { + istreambuf_glue b; + + int avail_1 = b.in_avail(); + int c = b.sgetc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == EOF); + CHECK(avail_1 == -1); + CHECK(avail_2 == -1); + CHECK(off_b == -1); + CHECK(off_c == -1); + CHECK(off_e == -1); + } + + SECTION("get char") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int avail_1 = b.in_avail(); + int c = b.sgetc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'E'); + CHECK(avail_1 == len); + CHECK(avail_2 == len); + CHECK(off_b == 0); + CHECK(off_c == 0); + CHECK(off_e == len); + } + + SECTION("get char and bump") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int avail_1 = b.in_avail(); + int c = b.sbumpc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'E'); + CHECK(avail_1 == len); + CHECK(avail_2 == len - 1); + CHECK(off_b == 0); + CHECK(off_c == 1); + CHECK(off_e == len); + } + + SECTION("advance and get char") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int avail_1 = b.in_avail(); + int c = b.snextc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'a'); + CHECK(avail_1 == len); + CHECK(avail_2 == len - 1); + CHECK(off_b == 0); + CHECK(off_c == 1); + CHECK(off_e == len); + } + + SECTION("get chars") + { + char act[ACT_SIZE]; + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int avail_1 = b.in_avail(); + int nread_1 = b.sgetn(act, 10); + int avail_2 = b.in_avail(); + int nread_2 = b.sgetn(act + 10, 10); + int avail_3 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(nread_1 == 10); + CHECK(nread_2 == 10); + CHECK(avail_1 == len); + CHECK(avail_2 == len - 10); + CHECK(avail_3 == len - 20); + CHECK(off_b == 0); + CHECK(off_c == 20); + CHECK(off_e == len); + CHECK(!memcmp(exp, act, 20)); + } + + SECTION("get chars EOF") + { + char act[ACT_SIZE]; + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int avail_1 = b.in_avail(); + int nread_1 = b.sgetn(act, 10); + int avail_2 = b.in_avail(); + int nread_2 = b.sgetn(act + 10, 10); + int avail_3 = b.in_avail(); + int nread_3 = b.sgetn(act + 20, 10); + int avail_4 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(nread_1 == 10); + CHECK(nread_2 == 10); + CHECK(nread_3 == 3); + CHECK(avail_1 == len); + CHECK(avail_2 == len - 10); + CHECK(avail_3 == len - 20); + CHECK(avail_4 == 0); + CHECK(off_b == 0); + CHECK(off_c == 23); + CHECK(off_e == len); + CHECK(!memcmp(exp, act, len)); + } + + SECTION("put char back") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int off_o = b.pubseekoff(5, ios_base::beg, ios_base::in); + int avail_1 = b.in_avail(); + int c = b.sungetc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'y'); + CHECK(avail_1 == len - 5); + CHECK(avail_2 == len - 4); + CHECK(off_o == 5); + CHECK(off_b == 0); + CHECK(off_c == 4); + CHECK(off_e == len); + } + + SECTION("put another char back") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int off_o = b.pubseekoff(5, ios_base::beg, ios_base::in); + int avail_1 = b.in_avail(); + int c1 = b.sputbackc(' '); + int c2 = b.sputbackc('y'); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c1 == -1); + CHECK(c2 == 'y'); + CHECK(avail_1 == len - 5); + CHECK(avail_2 == len - 4); + CHECK(off_o == 5); + CHECK(off_b == 0); + CHECK(off_c == 4); + CHECK(off_e == len); + } +} + +TEST_CASE("input buffer - basic two sources", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + char dat1[] = "Early bird"; + char dat2[] = " gets a corn."; + const int dat1_len = strlen(dat1); + const int dat2_len = strlen(dat2); + + SECTION("get char") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int avail_1 = b.in_avail(); + int c = b.sgetc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'E'); + CHECK(avail_1 == dat1_len); + CHECK(avail_2 == dat1_len); + CHECK(off_b == 0); + CHECK(off_c == 0); + CHECK(off_e == len); + } + + SECTION("get char and bump") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int avail_1 = b.in_avail(); + int c = b.sbumpc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'E'); + CHECK(avail_1 == dat1_len); + CHECK(avail_2 == dat1_len - 1); + CHECK(off_b == 0); + CHECK(off_c == 1); + CHECK(off_e == len); + } + + SECTION("advance and get char") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int avail_1 = b.in_avail(); + int c = b.snextc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'a'); + CHECK(avail_1 == dat1_len); + CHECK(avail_2 == dat1_len - 1); + CHECK(off_b == 0); + CHECK(off_c == 1); + CHECK(off_e == len); + } + + SECTION("get chars") + { + char act[ACT_SIZE]; + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int avail_1 = b.in_avail(); + int nread_1 = b.sgetn(act, 10); + int avail_2 = b.in_avail(); + int nread_2 = b.sgetn(act + 10, 10); + int avail_3 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(nread_1 == 10); + CHECK(nread_2 == 10); + CHECK(avail_1 == dat1_len); + CHECK(avail_2 == len - 10); + CHECK(avail_3 == len - 20); + CHECK(off_b == 0); + CHECK(off_c == 20); + CHECK(off_e == len); + CHECK(!memcmp(exp, act, 20)); + } + + SECTION("get chars EOF") + { + char act[ACT_SIZE]; + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int avail_1 = b.in_avail(); + int nread_1 = b.sgetn(act, 10); + int avail_2 = b.in_avail(); + int nread_2 = b.sgetn(act + 10, 10); + int avail_3 = b.in_avail(); + int nread_3 = b.sgetn(act + 20, 10); + int avail_4 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(nread_1 == 10); + CHECK(nread_2 == 10); + CHECK(nread_3 == 3); + CHECK(avail_1 == dat1_len); + CHECK(avail_2 == len - 10); + CHECK(avail_3 == len - 20); + CHECK(avail_4 == 0); + CHECK(off_b == 0); + CHECK(off_c == 23); + CHECK(off_e == len); + CHECK(!memcmp(exp, act, len)); + } + + SECTION("put char back") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int off_o = b.pubseekoff(5, ios_base::beg, ios_base::in); + int avail_1 = b.in_avail(); + int c = b.sungetc(); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c == 'y'); + CHECK(avail_1 == dat1_len - 5); + CHECK(avail_2 == dat1_len - 4); + CHECK(off_o == 5); + CHECK(off_b == 0); + CHECK(off_c == 4); + CHECK(off_e == len); + } + + SECTION("put another char back") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int off_o = b.pubseekoff(5, ios_base::beg, ios_base::in); + int avail_1 = b.in_avail(); + int c1 = b.sputbackc(' '); + int c2 = b.sputbackc('y'); + int avail_2 = b.in_avail(); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(c1 == -1); + CHECK(c2 == 'y'); + CHECK(avail_1 == dat1_len - 5); + CHECK(avail_2 == dat1_len - 4); + CHECK(off_o == 5); + CHECK(off_b == 0); + CHECK(off_c == 4); + CHECK(off_e == len); + } +} + +TEST_CASE("input buffer - buffer management", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + char dat[] = "Early bird gets a corn."; + char dat1[] = "Early "; + char dat2[] = "bird "; + char dat3[] = "gets "; + char dat4[] = "a corn."; + const int dat1_len = strlen(dat1); + const int dat2_len = strlen(dat2); + const int dat3_len = strlen(dat3); + const int dat4_len = strlen(dat4); + + SECTION("sync") + { + istreambuf_glue b1, b2; + + b1.pubsetbuf(dat, len); + b2.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int r1 = b1.pubsync(); + int r2 = b2.pubsync(); + + CHECK(r1 == -1); + CHECK(r2 == -1); + } + + SECTION("chain of buffers") + { + istreambuf_glue b; + + b.pubsetbuf(nullptr, 0); + + b.pubsetbuf(dat1, dat1_len); + EXP_AVAILn(b, exp, dat1_len, 0); + b.pubsetbuf(dat2, dat2_len); + EXP_AVAILn(b, exp + dat1_len, dat2_len, 0); + + b.pubsetbuf(nullptr, 0); + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + EXP_AVAILn(b, exp, dat1_len + dat2_len, 0); + b.pubsetbuf(dat3, dat3_len)->pubsetbuf(dat4, dat4_len); + EXP_AVAILn(b, exp + dat1_len + dat2_len, dat3_len + dat4_len, 0); + + b.pubsetbuf(nullptr, 0); + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len) + ->pubsetbuf(dat3, dat3_len)->pubsetbuf(dat4, dat4_len); + EXP_AVAILn(b, exp, len, 0); + } +} + +TEST_CASE("input buffer - offset one source", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + char dat[] = "Early bird gets a corn."; + + SECTION("no data") + { + istreambuf_glue b; + + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + + CHECK(off_b == -1); + CHECK(off_c == -1); + CHECK(off_e == -1); + } + + SECTION("wrong argument") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + int off_a = b.pubseekoff(0, static_cast(0x5a5a), ios_base::in); + + CHECK(off_b == -1); + CHECK(off_c == -1); + CHECK(off_e == -1); + CHECK(off_a == -1); + } + + SECTION("begin") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat, len); + + off = b.pubseekoff(0 - len - len, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len + len, ios_base::beg, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - 1, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + 1, ios_base::beg, ios_base::in); + CHECK(off == 1); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len1d3, ios_base::beg, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len2d3, ios_base::beg, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len - 1, ios_base::beg, ios_base::in); + CHECK(off == len - 1); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len, ios_base::beg, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + } + + SECTION("end") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat, len); + + off = b.pubseekoff(0 - len - len, ios_base::end, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + len + len, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - 1, ios_base::end, ios_base::in); + CHECK(off == len - 1); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 + 1, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - len1d3, ios_base::end, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - len2d3, ios_base::end, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - len + 1, ios_base::end, ios_base::in); + CHECK(off == 1); + EXP_AVAIL1(b, exp, len, off); + + off = b.pubseekoff(0 - len, ios_base::end, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + } + + SECTION("current 1/3") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat, len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - len - len, ios_base::cur, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0, ios_base::cur, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - 1, ios_base::cur, ios_base::in); + CHECK(off == len1d3 - 1); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + 1, ios_base::cur, ios_base::in); + CHECK(off == len1d3 + 1); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len1d3, ios_base::cur, ios_base::in); + CHECK(off == len1d3 + len1d3); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len2d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + } + + SECTION("current 2/3") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat, len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - len - len, ios_base::cur, ios_base::in); + CHECK(off == 0); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0, ios_base::cur, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - 1, ios_base::cur, ios_base::in); + CHECK(off == len2d3 - 1); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + 1, ios_base::cur, ios_base::in); + CHECK(off == len2d3 + 1); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len1d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len2d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL1(b, exp, len, off); + } +} + +TEST_CASE("input buffer - offset two sources", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + char dat1[] = "Early bird"; + char dat2[] = " gets a corn."; + const int dat1_len = strlen(dat1); + const int dat2_len = strlen(dat2); + + SECTION("wrong buffer") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(off_b == -1); + CHECK(off_c == -1); + CHECK(off_e == -1); + } + + SECTION("begin") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + off = b.pubseekoff(0 - len - len, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len + len, ios_base::beg, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - 1, ios_base::beg, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + 1, ios_base::beg, ios_base::in); + CHECK(off == 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len1d3, ios_base::beg, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len2d3, ios_base::beg, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len - 1, ios_base::beg, ios_base::in); + CHECK(off == len - 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len, ios_base::beg, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + } + + SECTION("end") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + off = b.pubseekoff(0 - len - len, ios_base::end, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + len + len, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - 1, ios_base::end, ios_base::in); + CHECK(off == len - 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 + 1, ios_base::end, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - len1d3, ios_base::end, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - len2d3, ios_base::end, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - len + 1, ios_base::end, ios_base::in); + CHECK(off == 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + off = b.pubseekoff(0 - len, ios_base::end, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + } + + SECTION("current 1/3") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - len - len, ios_base::cur, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0, ios_base::cur, ios_base::in); + CHECK(off == len1d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - 1, ios_base::cur, ios_base::in); + CHECK(off == len1d3 - 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + 1, ios_base::cur, ios_base::in); + CHECK(off == len1d3 + 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len1d3, ios_base::cur, ios_base::in); + CHECK(off == len1d3 + len1d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len2d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + } + + SECTION("current 2/3") + { + istreambuf_glue b; + int off; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - len - len, ios_base::cur, ios_base::in); + CHECK(off == 0); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0, ios_base::cur, ios_base::in); + CHECK(off == len2d3); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 - 1, ios_base::cur, ios_base::in); + CHECK(off == len2d3 - 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + 1, ios_base::cur, ios_base::in); + CHECK(off == len2d3 + 1); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len1d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len2d3, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + + b.pubseekoff(len2d3, ios_base::beg, ios_base::in); + off = b.pubseekoff(0 + len, ios_base::cur, ios_base::in); + CHECK(off == len); + EXP_AVAIL2(b, exp, len, off, dat1_len); + } +} + +TEST_CASE("input buffer - positioning one source", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + char dat[] = "Early bird gets a corn."; + + SECTION("no data") + { + istreambuf_glue b; + + int pos_c = b.pubseekpos(len / 2, ios_base::in); + int pos_b = b.pubseekpos(0, ios_base::in); + int pos_e = b.pubseekpos(len, ios_base::in); + + CHECK(pos_b == -1); + CHECK(pos_c == -1); + CHECK(pos_e == -1); + } + + SECTION("wrong buffer") + { + istreambuf_glue b; + + b.pubsetbuf(dat, len); + + int pos_c = b.pubseekpos(len / 2, ios_base::out); + int pos_b = b.pubseekpos(0, ios_base::out); + int pos_e = b.pubseekpos(len, ios_base::out); + + CHECK(pos_b == -1); + CHECK(pos_c == -1); + CHECK(pos_e == -1); + } + + SECTION("out of range") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat, len); + + pos = b.pubseekpos(0 - len, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(len + len, ios_base::in); + CHECK(pos == len); + EXP_AVAIL1(b, exp, len, pos); + } + + SECTION("on the edge") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat, len); + + pos = b.pubseekpos(0, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(0 - 1, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(0 + 1, ios_base::in); + CHECK(pos == 1); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(len, ios_base::in); + CHECK(pos == len); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(len - 1, ios_base::in); + CHECK(pos == len - 1); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(len + 1, ios_base::in); + CHECK(pos == len); + EXP_AVAIL1(b, exp, len, pos); + } + + SECTION("in range") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat, len); + + pos = b.pubseekpos(len1d3, ios_base::in); + CHECK(pos == len1d3); + EXP_AVAIL1(b, exp, len, pos); + + pos = b.pubseekpos(len2d3, ios_base::in); + CHECK(pos == len2d3); + EXP_AVAIL1(b, exp, len, pos); + } +} + +TEST_CASE("input buffer - positioning two sources", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + char dat1[] = "Early bird"; + char dat2[] = " gets a corn."; + const int dat1_len = strlen(dat1); + const int dat2_len = strlen(dat2); + + SECTION("wrong buffer") + { + istreambuf_glue b; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + int pos_c = b.pubseekpos(len / 2, ios_base::out); + int pos_b = b.pubseekpos(0, ios_base::out); + int pos_e = b.pubseekpos(len, ios_base::out); + + CHECK(pos_b == -1); + CHECK(pos_c == -1); + CHECK(pos_e == -1); + } + + SECTION("out of range") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + pos = b.pubseekpos(0 - len, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(len + len, ios_base::in); + CHECK(pos == len); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + } + + SECTION("on the edge") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + pos = b.pubseekpos(0, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(0 - 1, ios_base::in); + CHECK(pos == 0); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(0 + 1, ios_base::in); + CHECK(pos == 1); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(len, ios_base::in); + CHECK(pos == len); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(len - 1, ios_base::in); + CHECK(pos == len - 1); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(len + 1, ios_base::in); + CHECK(pos == len); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + } + + SECTION("in range") + { + istreambuf_glue b; + int pos; + + b.pubsetbuf(dat1, dat1_len)->pubsetbuf(dat2, dat2_len); + + pos = b.pubseekpos(len1d3, ios_base::in); + CHECK(pos == len1d3); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(len2d3, ios_base::in); + CHECK(pos == len2d3); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(dat1_len, ios_base::in); + CHECK(pos == dat1_len); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(dat1_len + 1, ios_base::in); + CHECK(pos == dat1_len + 1); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(dat1_len - 1, ios_base::in); + CHECK(pos == dat1_len - 1); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(dat1_len + 2, ios_base::in); + CHECK(pos == dat1_len + 2); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + + pos = b.pubseekpos(dat1_len - 2, ios_base::in); + CHECK(pos == dat1_len - 2); + EXP_AVAIL2(b, exp, len, pos, dat1_len); + } +} + +TEST_CASE("input stream - one source", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + char dat[] = "Early bird gets a corn."; + + SECTION("no data") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + s.read(act, sizeof(act)); + + CHECK(true == s.eof()); + CHECK(-1 == s.tellg()); + s.clear(); + CHECK(-1 == s.tellg()); + } + + SECTION("equal") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat, len); + s.read(act, len); + + EXP_IN(s, exp, act, len); + } + + SECTION("partial read") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat, len); + s.read(act + 0, 1); + s.read(act + 1, 2); + s.read(act + 3, 3); + s.read(act + 6, 4); + + EXP_IN(s, exp, act, 10); + + s.read(act + 10, len - 10); + + EXP_IN(s, exp, act, len); + } + + SECTION("EOF") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat, 10); + s.read(act, len); + + EOF_IN(s, exp, act, 10); + } +} + +TEST_CASE("input stream - two sources", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + char dat1[] = "Early bird"; + char dat2[] = " gets a corn."; + + SECTION("equal") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat1, strlen(dat1))->pubsetbuf(dat2, strlen(dat2)); + s.read(act, len); + + EXP_IN(s, exp, act, len); + } + + SECTION("partial read") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat1, strlen(dat1))->pubsetbuf(dat2, strlen(dat2)); + s.read(act + 0, 1); + s.read(act + 1, 2); + s.read(act + 3, 3); + s.read(act + 6, 4); + + EXP_IN(s, exp, act, 10); + + s.read(act + 10, len - 10); + + EXP_IN(s, exp, act, len); + } + + SECTION("EOF") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + b.pubsetbuf(dat1, strlen(dat1))->pubsetbuf(dat2, 1); + s.read(act, len); + + EOF_IN(s, exp, act, 11); + } +} + +TEST_CASE("input stream - last chunk offset", "[Stream buffers]") +{ + SECTION("no data") + { + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(0 == b.last_chunk_offset()); + } + + SECTION("single buffer") + { + char dat1[] = "01234567"; + + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + CHECK(0 == b.last_chunk_offset()); + + b.pubsetbuf(dat1, strlen(dat1)); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(1 == b.last_chunk_offset()); + + s.read(act, 2); + CHECK(3 == b.last_chunk_offset()); + + s.read(act, 5); + CHECK(8 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(8 == b.last_chunk_offset()); + } + + SECTION("two buffers") + { + char dat1[] = "0123"; + char dat2[] = "4567"; + + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + CHECK(0 == b.last_chunk_offset()); + + b.pubsetbuf(dat1, strlen(dat1))->pubsetbuf(dat2, strlen(dat2)); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 4); + CHECK(2 == b.last_chunk_offset()); + + s.read(act, 2); + CHECK(4 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(4 == b.last_chunk_offset()); + } + + SECTION("three buffers") + { + char dat1[] = "0123"; + char dat2[] = "4567"; + char dat3[] = "89+*"; + + char act[ACT_SIZE]; + istreambuf_glue b; + istream s(&b); + + CHECK(0 == b.last_chunk_offset()); + + b.pubsetbuf(dat1, strlen(dat1))->pubsetbuf(dat2, strlen(dat2))->pubsetbuf(dat3, strlen(dat3)); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 3); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 3); + CHECK(0 == b.last_chunk_offset()); + + s.read(act, 3); + CHECK(1 == b.last_chunk_offset()); + + s.read(act, 3); + CHECK(4 == b.last_chunk_offset()); + + s.read(act, 1); + CHECK(4 == b.last_chunk_offset()); + } +} + +TEST_CASE("output buffer - basic", "[Stream buffers]") +{ + const char exp[EXP_SIZE] = "ABC"; + + SECTION("no input") + { + const char* n = ""; + const int l = strlen(n); + + ostreambuf_infl b; + + EXP_RES(b, n, l, 0); + } + + SECTION("put char") + { + ostreambuf_infl b; + + int c = b.sputc('A'); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(c == 'A'); + CHECK(off_b == 0); + CHECK(off_c == 1); + CHECK(off_e == 256); + + EXP_RES(b, exp, 1, 256); + } + + SECTION("put two chars") + { + ostreambuf_infl b; + + int c1 = b.sputc('A'); + int off_1 = b.pubseekoff(0, ios_base::cur, ios_base::out); + int c2 = b.sputc('B'); + int off_2 = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(c1 == 'A'); + CHECK(c2 == 'B'); + CHECK(off_b == 0); + CHECK(off_1 == 1); + CHECK(off_2 == 2); + CHECK(off_e == 256); + + EXP_RES(b, exp, 2, 256); + } + + SECTION("extend buffer") + { + ostreambuf_infl b; + + int c1 = b.sputc('A'); + int off_1 = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + int c2 = b.sputc('Z'); + int off_2 = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_z = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(c1 == 'A'); + CHECK(c2 == 'Z'); + CHECK(off_b == 0); + CHECK(off_1 == 1); + CHECK(off_2 == 257); + CHECK(off_e == 256); + CHECK(off_z == 768); + } + + SECTION("put sequence of chars") + { + ostreambuf_infl b; + const int len = sizeof(exp); + + int n = b.sputn(exp, len); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(n == len); + CHECK(off_b == 0); + CHECK(off_c == len); + CHECK(off_e == 4096); + + EXP_RES(b, exp, len, 4096); + } + + SECTION("continue sequence") + { + ostreambuf_infl b; + const int len = sizeof(exp) - 1; + + int c1 = b.sputc('A'); + int n = b.sputn(exp, len); + int c2 = b.sputc('Z'); + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(n == len); + CHECK(c1 == 'A'); + CHECK(c2 == 'Z'); + CHECK(off_b == 0); + CHECK(off_c == len + 2); + CHECK(off_e == 4096 + 512); + } +} + +TEST_CASE("output buffer - buffer management", "[Stream buffers]") +{ + char dat1[] = "0123"; + char dat2[] = "4567"; + char dat3[] = "89"; + char dat4[] = "-+"; + const int dat1_len = strlen(dat1); + const int dat2_len = strlen(dat2); + const int dat3_len = strlen(dat3); + const int dat4_len = strlen(dat4); + + SECTION("sync") + { + ostreambuf_infl b; + + char* buf = new char[dat1_len]; + memcpy(buf, dat1, dat1_len); + b.pubsetbuf(buf, dat1_len); + + int r = b.pubsync(); + CHECK(r == -1); + } + + SECTION("changing buffer") + { + ostreambuf_infl b; + const int s1 = 128; + const int s2 = 256; + const int s3 = 32; + const int s4 = 64; + + b.pubsetbuf(new char[s1], s1); + b.sputn(dat1, dat1_len); + EXP_RES(b, dat1, dat1_len, dat1_len); + + b.pubsetbuf(new char[s2], s2); + b.sputn(dat2, dat2_len); + EXP_RES(b, dat2, dat2_len, dat2_len); + + b.pubsetbuf(new char[s3], s3); + b.sputn(dat3, dat3_len); + EXP_RES(b, dat3, dat3_len, dat3_len); + + b.pubsetbuf(new char[s4], s4); + b.sputn(dat4, dat4_len); + EXP_RES(b, dat4, dat4_len, dat4_len); + } +} + +TEST_CASE("output buffer - positioning", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + + SECTION("no data") + { + ostreambuf_infl b; + + int pos_c = b.pubseekpos(len / 2, ios_base::out); + int pos_b = b.pubseekpos(0, ios_base::out); + int pos_e = b.pubseekpos(len, ios_base::out); + + CHECK(pos_b == 0); + CHECK(pos_c == 0); + CHECK(pos_e == 0); + } + + SECTION("wrong buffer") + { + ostreambuf_infl b; + + b.pubsetbuf(new char[len], len); + + int pos_c = b.pubseekpos(len / 2, ios_base::in); + int pos_b = b.pubseekpos(0, ios_base::in); + int pos_e = b.pubseekpos(len, ios_base::in); + + CHECK(pos_b == -1); + CHECK(pos_c == -1); + CHECK(pos_e == -1); + } + + SECTION("out of range") + { + ostreambuf_infl b; + int pos; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + pos = b.pubseekpos(0 - len, ios_base::out); + CHECK(pos == 0); + EXP_RES(b, exp, 0, 0); + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + pos = b.pubseekpos(len + len, ios_base::out); + CHECK(pos == len); + EXP_RES(b, exp, len, len); + } + + SECTION("on the edge") + { + ostreambuf_infl b; + int pos; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + pos = b.pubseekpos(0 - 1, ios_base::out); + CHECK(pos == 0); + pos = b.pubseekpos(0 + 1, ios_base::out); + CHECK(pos == 1); + pos = b.pubseekpos(len, ios_base::out); + CHECK(pos == len); + pos = b.pubseekpos(len - 1, ios_base::out); + CHECK(pos == len - 1); + pos = b.pubseekpos(len + 1, ios_base::out); + CHECK(pos == len); + + pos = b.pubseekpos(0, ios_base::out); + CHECK(pos == 0); + EXP_RES(b, exp, 0, 0); + } + + SECTION("in range") + { + ostreambuf_infl b; + int pos; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + pos = b.pubseekpos(len1d3, ios_base::out); + CHECK(pos == len1d3); + pos = b.pubseekpos(len2d3, ios_base::out); + CHECK(pos == len2d3); + } +} + +TEST_CASE("output buffer - offset", "[Stream buffers]") +{ + const char* exp = "Early bird gets a corn."; + const int len = strlen(exp); + const int len1d3 = strlen(exp) * 1 / 3; + const int len2d3 = len - len1d3; + + SECTION("no data") + { + ostreambuf_infl b; + + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::out); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::out); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::out); + + CHECK(off_b == 0); + CHECK(off_c == 0); + CHECK(off_e == 0); + } + + SECTION("wrong buffer") + { + ostreambuf_infl b; + + b.pubsetbuf(new char[len], len); + + int off_c = b.pubseekoff(0, ios_base::cur, ios_base::in); + int off_b = b.pubseekoff(0, ios_base::beg, ios_base::in); + int off_e = b.pubseekoff(0, ios_base::end, ios_base::in); + int off_a = b.pubseekoff(0, static_cast(0x5a5a), ios_base::out); + + CHECK(off_b == -1); + CHECK(off_c == -1); + CHECK(off_e == -1); + CHECK(off_a == -1); + } + + SECTION("begin") + { + ostreambuf_infl b; + int off; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + off = b.pubseekoff(0 - len - len, ios_base::beg, ios_base::out); + CHECK(off == 0); + + off = b.pubseekoff(0 + len + len, ios_base::beg, ios_base::out); + CHECK(off == len); + + off = b.pubseekoff(0 - 1, ios_base::beg, ios_base::out); + CHECK(off == 0); + + off = b.pubseekoff(0 + 1, ios_base::beg, ios_base::out); + CHECK(off == 1); + + off = b.pubseekoff(0 + len1d3, ios_base::beg, ios_base::out); + CHECK(off == len1d3); + + off = b.pubseekoff(0 + len2d3, ios_base::beg, ios_base::out); + CHECK(off == len2d3); + + off = b.pubseekoff(0 + len - 1, ios_base::beg, ios_base::out); + CHECK(off == len - 1); + + off = b.pubseekoff(0 + len, ios_base::beg, ios_base::out); + CHECK(off == len); + + off = b.pubseekoff(0, ios_base::beg, ios_base::out); + CHECK(off == 0); + EXP_RES(b, exp, 0, 0); + b.sputn(exp, len); + EXP_RES(b, exp, len, len); + } + + SECTION("end") + { + const char* exp_alt = "Early bird gets a corn!"; + + ostreambuf_infl b; + int off; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + off = b.pubseekoff(0 - len - len, ios_base::end, ios_base::out); + CHECK(off == 0); + + off = b.pubseekoff(0 + len + len, ios_base::end, ios_base::out); + CHECK(off == len); + + off = b.pubseekoff(0, ios_base::end, ios_base::out); + CHECK(off == len); + + off = b.pubseekoff(0 - 1, ios_base::end, ios_base::out); + CHECK(off == len - 1); + + off = b.pubseekoff(0 + 1, ios_base::end, ios_base::out); + CHECK(off == len); + + off = b.pubseekoff(0 - len1d3, ios_base::end, ios_base::out); + CHECK(off == len2d3); + + off = b.pubseekoff(0 - len2d3, ios_base::end, ios_base::out); + CHECK(off == len1d3); + + off = b.pubseekoff(0 - len + 1, ios_base::end, ios_base::out); + CHECK(off == 1); + + off = b.pubseekoff(0 - len, ios_base::end, ios_base::out); + CHECK(off == 0); + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + off = b.pubseekoff(- 1, ios_base::end, ios_base::out); + CHECK(off == len - 1); + b.sputn("!", 1); + off = b.pubseekoff(0, ios_base::end, ios_base::out); + CHECK(off == len); + EXP_RES(b, exp_alt, len, len); + } + + SECTION("current") + { + const char* exp_alt = "Early birds get a worm."; + const char* ovr_alt = "s get a worm."; + int off_alt = strlen(ovr_alt); + + ostreambuf_infl b; + int off; + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 - len - len, ios_base::cur, ios_base::out); + CHECK(off == 0); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 + len + len, ios_base::cur, ios_base::out); + CHECK(off == len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0, ios_base::cur, ios_base::out); + CHECK(off == len1d3); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 - 1, ios_base::cur, ios_base::out); + CHECK(off == len1d3 - 1); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 + 1, ios_base::cur, ios_base::out); + CHECK(off == len1d3 + 1); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 + len1d3, ios_base::cur, ios_base::out); + CHECK(off == len1d3 + len1d3); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 + len2d3, ios_base::cur, ios_base::out); + CHECK(off == len); + + b.pubseekoff(len1d3, ios_base::beg, ios_base::out); + off = b.pubseekoff(0 + len, ios_base::cur, ios_base::out); + CHECK(off == len); + + b.pubsetbuf(new char[len], len); + b.sputn(exp, len); + b.pubseekoff(- off_alt, ios_base::end, ios_base::out); + off = b.pubseekoff(0, ios_base::cur, ios_base::out); + CHECK(off == len - off_alt); + b.sputn(ovr_alt, off_alt); + off = b.pubseekoff(0, ios_base::end, ios_base::out); + CHECK(off == len); + EXP_RES(b, exp_alt, len, len); + } +} + +TEST_CASE("output stream - basic", "[Stream buffers]") +{ + SECTION("no input") + { + const char* exp = ""; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("1 byte input") + { + const char* src = "A"; + const int src_len = strlen(src); + const char* exp = "A"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + s.write(src, src_len); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("8 bytes input") + { + const char* src = "12345678"; + const int src_len = strlen(src); + const char* exp = "12345678"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + s.write(src, src_len); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("partial write") + { + const char* src = "12345678"; + const int src_len = strlen(src); + const char* exp = "12345678"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + for (const char* c = src; c < src + src_len; ++c) + s.write(c, 1); + + EXP_OUT(s, exp, exp_len); + } +} + +TEST_CASE("output stream - reserved size", "[Stream buffers]") +{ + SECTION("no input") + { + const char* exp = ""; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(32); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("1 byte input") + { + const char* src = "A"; + const int src_len = strlen(src); + const char* exp = "A"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(32); + s.write(src, src_len); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("8 bytes input") + { + const char* src = "12345678"; + const int src_len = strlen(src); + const char* exp = "12345678"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(32); + s.write(src, src_len); + + EXP_OUT(s, exp, exp_len); + } + + SECTION("partial write") + { + const char* src = "12345678"; + const int src_len = strlen(src); + const char* exp = "12345678"; + const int exp_len = strlen(exp); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(32); + for (const char* c = src; c < src + src_len; ++c) + s.write(c, 1); + + EXP_OUT(s, exp, exp_len); + } +} + +TEST_CASE("output stream - large data", "[Stream buffers]") +{ + const int len = 1 << 21; + const int plen = 1 << 12; + vector chars; + + chars.reserve(len); + for (char& c : chars) + c = rand(); + + SECTION("0 bytes reserved") + { + const char* src = chars.data(); + const char* exp = chars.data(); + + ostreambuf_infl b; + ostream s(&b); + + for (int i = 0; i < len; i += plen, src += plen) + s.write(src, plen); + + EOF_OUT(s, exp, 1 << 20); + } + + SECTION("2^10 bytes reserved") + { + const char* src = chars.data(); + const char* exp = chars.data(); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(1 << 10); + for (int i = 0; i < len; i += plen, src += plen) + s.write(src, plen); + + EOF_OUT(s, exp, 1 << 20); + } + + SECTION("2^18 bytes reserved") + { + const char* src = chars.data(); + const char* exp = chars.data(); + + ostreambuf_infl b; + ostream s(&b); + + b.reserve(1 << 18); + for (int i = 0; i < len; i += plen, src += plen) + s.write(src, plen); + + EOF_OUT(s, exp, 1 << 20); + } +}