From 500ddae318f026b818ca481255ff3bc5565884c8 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Sat, 20 Oct 2018 15:30:56 -0500 Subject: [PATCH 1/7] YAML: LibSWOC prep - update BufferWriter logic in HttpTransactHeaders.cc --- proxy/http/HttpTransactHeaders.cc | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/proxy/http/HttpTransactHeaders.cc b/proxy/http/HttpTransactHeaders.cc index 95f662626c7..5f18843b4d8 100644 --- a/proxy/http/HttpTransactHeaders.cc +++ b/proxy/http/HttpTransactHeaders.cc @@ -1155,25 +1155,19 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (n_proto > 0) { auto Conn = [&](HttpForwarded::Option opt, HttpTransactHeaders::ProtocolStackDetail detail) -> void { - if (optSet[opt]) { - int revert = hdr.size(); + if (optSet[opt] && hdr.remaining() > 0) { + ts::FixedBufferWriter lw{hdr.auxBuffer(), hdr.remaining()}; if (hdr.size()) { - hdr << ';'; + lw << ';'; } - hdr << "connection="; + lw << "connection="; int numChars = - HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), detail, protoBuf.data(), n_proto, '-'); - if (numChars > 0) { - hdr.fill(size_t(numChars)); - } - - if ((numChars <= 0) or (hdr.size() >= hdr.capacity())) { - // Remove parameter with potentially incomplete value. - // - hdr.reduce(revert); + HttpTransactHeaders::write_hdr_protocol_stack(lw.auxBuffer(), lw.remaining(), detail, protoBuf.data(), n_proto, '-'); + if (numChars > 0 && !lw.fill(size_t(numChars)).error()) { + hdr.fill(lw.size()); } } }; From c745780f85fbdddaeecc434952b9dc41f2951844 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Fri, 19 Oct 2018 13:10:35 -0500 Subject: [PATCH 2/7] YAML: LibSWOC++ update for MemArena. Move to tscpp/util for plugins. --- include/tscpp/util/Makefile.am | 2 + include/{tscore => tscpp/util}/MemArena.h | 102 +++++++++------ src/tscore/Makefile.am | 4 - src/tscpp/util/Makefile.am | 4 + src/{tscore => tscpp/util}/MemArena.cc | 118 +++++++++--------- .../util}/unit_tests/test_MemArena.cc | 54 +++++--- 6 files changed, 166 insertions(+), 118 deletions(-) rename include/{tscore => tscpp/util}/MemArena.h (77%) rename src/{tscore => tscpp/util}/MemArena.cc (58%) rename src/{tscore => tscpp/util}/unit_tests/test_MemArena.cc (80%) diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index 5fea12cd2d1..e24e1eed1fd 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -20,5 +20,7 @@ library_includedir=$(includedir)/tscpp/util library_include_HEADERS = \ IntrusiveDList.h \ + MemArena.h \ + MemSpan.h \ PostScript.h \ TextView.h diff --git a/include/tscore/MemArena.h b/include/tscpp/util/MemArena.h similarity index 77% rename from include/tscore/MemArena.h rename to include/tscpp/util/MemArena.h index 3a3519a0b2e..4e24cf26f7a 100644 --- a/include/tscore/MemArena.h +++ b/include/tscpp/util/MemArena.h @@ -4,20 +4,17 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -27,11 +24,11 @@ #include #include #include + #include "tscpp/util/MemSpan.h" #include "tscore/Scalar.h" -#include +#include "tscpp/util/IntrusiveDList.h" -/// Apache Traffic Server commons. namespace ts { /** A memory arena. @@ -46,19 +43,18 @@ class MemArena { using self_type = MemArena; ///< Self reference type. protected: - struct Block; // Forward declare - using BlockPtr = ts::IntrusivePtr; - friend struct IntrusivePtrPolicy; - /** Simple internal arena block of memory. Maintains the underlying memory. - * - * Intrusive pointer is used to keep all of the memory in this single block. This struct is just - * the header on the full memory block allowing the raw memory and the meta data to be obtained - * in a single memory allocation. - */ - struct Block : public ts::IntrusivePtrCounter { + /// Simple internal arena block of memory. Maintains the underlying memory. + struct Block { size_t size; ///< Actual block size. size_t allocated{0}; ///< Current allocated (in use) bytes. - BlockPtr next; ///< List of previous blocks. + struct Linkage { + Block *_next{nullptr}; + Block *_prev{nullptr}; + + static Block *&next_ptr(Block *); + + static Block *&prev_ptr(Block *); + } _link; /** Construct to have @a n bytes of available storage. * @@ -104,6 +100,8 @@ class MemArena static void operator delete(void *ptr); }; + using BlockList = IntrusiveDList; + public: /** Construct with reservation hint. * @@ -120,6 +118,16 @@ class MemArena */ explicit MemArena(size_t n = DEFAULT_BLOCK_SIZE); + /// no copying + MemArena(self_type const &that) = delete; + MemArena(self_type &&that) = default; + + /// Destructor. + ~MemArena(); + + self_type &operator=(self_type const &that) = delete; + self_type &operator=(self_type &&that) = default; + /** Allocate @a n bytes of storage. Returns a span of memory within the arena. alloc() is self expanding but DOES NOT self @@ -183,7 +191,7 @@ class MemArena size_t remaining() const; /// @returns the remaining contiguous space in the active generation. - MemSpan remnant() const; + MemSpan remnant(); /// @returns the total number of bytes allocated within the arena. size_t allocated_size() const; @@ -206,10 +214,14 @@ class MemArena * @param n Size of block to allocate. * @return */ - BlockPtr make_block(size_t n); + Block *make_block(size_t n); + /// Clean up the frozen list. + void destroy_frozen(); + /// Clean up the active list + void destroy_active(); - using Page = ts::Scalar<4096>; ///< Size for rounding block sizes. - using Paragraph = ts::Scalar<16>; ///< Minimum unit of memory allocation. + using Page = Scalar<4096>; ///< Size for rounding block sizes. + using Paragraph = Scalar<16>; ///< Minimum unit of memory allocation. static constexpr size_t ALLOC_HEADER_SIZE = 16; ///< Guess of overhead of @c malloc /// Initial block size to allocate if not specified via API. @@ -218,20 +230,32 @@ class MemArena size_t _active_allocated = 0; ///< Total allocations in the active generation. size_t _active_reserved = 0; ///< Total current reserved memory. /// Total allocations in the previous generation. This is only non-zero while the arena is frozen. - size_t _prev_allocated = 0; + size_t _frozen_allocated = 0; /// Total frozen reserved memory. - size_t _prev_reserved = 0; + size_t _frozen_reserved = 0; /// Minimum free space needed in the next allocated block. /// This is not zero iff @c reserve was called. size_t _reserve_hint = 0; - BlockPtr _prev; ///< Previous generation, frozen memory. - BlockPtr _active; ///< Current generation. Allocate here. + BlockList _frozen; ///< Previous generation, frozen memory. + BlockList _active; ///< Current generation. Allocate here. }; // Implementation +inline auto +MemArena::Block::Linkage::next_ptr(Block *b) -> Block *& +{ + return b->_link._next; +} + +inline auto +MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *& +{ + return b->_link._prev; +} + inline MemArena::Block::Block(size_t n) : size(n) {} inline char * @@ -262,7 +286,9 @@ MemArena::Block::remaining() const inline MemSpan MemArena::Block::alloc(size_t n) { - ink_assert(n <= this->remaining()); + if (n > this->remaining()) { + throw(std::invalid_argument{"MemArena::Block::alloc size is more than remaining."}); + } MemSpan zret = this->remnant().prefix(n); allocated += n; return zret; @@ -292,25 +318,25 @@ MemArena::size() const inline size_t MemArena::allocated_size() const { - return _prev_allocated + _active_allocated; + return _frozen_allocated + _active_allocated; } inline size_t MemArena::remaining() const { - return _active ? _active->remaining() : 0; + return _active.empty() ? 0 : _active.head()->remaining(); } inline MemSpan -MemArena::remnant() const +MemArena::remnant() { - return _active ? _active->remnant() : MemSpan{}; + return _active.empty() ? MemSpan() : _active.head()->remnant(); } inline size_t MemArena::reserved_size() const { - return _active_reserved + _prev_reserved; + return _active_reserved + _frozen_reserved; } } // namespace ts diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index 367cf8e900f..2cf4709def8 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -174,9 +174,6 @@ libtscore_la_SOURCES = \ Map.h \ MatcherUtils.cc \ MatcherUtils.h \ - MemSpan.h \ - MemArena.cc \ - MemArena.h \ MMH.cc \ MMH.h \ MT_hashtable.h \ @@ -262,7 +259,6 @@ test_tscore_SOURCES = \ unit_tests/test_layout.cc \ unit_tests/test_Map.cc \ unit_tests/test_List.cc \ - unit_tests/test_MemArena.cc \ unit_tests/test_MT_hashtable.cc \ unit_tests/test_PriorityQueue.cc \ unit_tests/test_Ptr.cc \ diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index 8f0f4428aa8..5d7bd31ed77 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -28,6 +28,9 @@ libtscpputil_la_LDFLAGS = -no-undefined -version-info @TS_LIBTOOL_VERSION@ libtscpputil_la_SOURCES = \ IntrusiveDList.h \ + MemArena.h \ + MemArena.cc \ + MemSpan.h \ PostScript.h \ TextView.h TextView.cc @@ -38,6 +41,7 @@ test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS) test_tscpputil_LDADD = libtscpputil.la test_tscpputil_SOURCES = \ unit_tests/unit_test_main.cc \ + unit_tests/test_MemArena.cc \ unit_tests/test_MemSpan.cc \ unit_tests/test_PostScript.cc \ unit_tests/test_TextView.cc \ diff --git a/src/tscore/MemArena.cc b/src/tscpp/util/MemArena.cc similarity index 58% rename from src/tscore/MemArena.cc rename to src/tscpp/util/MemArena.cc index 3dfcb0cfed5..e156c2c28ec 100644 --- a/src/tscore/MemArena.cc +++ b/src/tscpp/util/MemArena.cc @@ -23,10 +23,7 @@ */ #include - -#include "tscore/MemArena.h" -#include "tscore/ink_memory.h" -#include "tscore/ink_assert.h" +#include "tscpp/util/MemArena.h" using namespace ts; @@ -36,15 +33,15 @@ MemArena::Block::operator delete(void *ptr) ::free(ptr); } -MemArena::BlockPtr +MemArena::Block * MemArena::make_block(size_t n) { // If there's no reservation hint, use the extent. This is transient because the hint is cleared. if (_reserve_hint == 0) { if (_active_reserved) { _reserve_hint = _active_reserved; - } else if (_prev_allocated) { - _reserve_hint = _prev_allocated; + } else if (_frozen_allocated) { + _reserve_hint = _frozen_allocated; } } @@ -63,53 +60,40 @@ MemArena::make_block(size_t n) // Easier to use malloc and override @c delete. auto free_space = n - sizeof(Block); _active_reserved += free_space; - return BlockPtr(new (::malloc(n)) Block(free_space)); + return new (::malloc(n)) Block(free_space); } MemSpan MemArena::alloc(size_t n) { - MemSpan zret; - _active_allocated += n; - - if (!_active) { - _active = this->make_block(n); - zret = _active->alloc(n); - } else if (n > _active->remaining()) { // too big, need another block - BlockPtr block = this->make_block(n); - // For the new @a _active, pick the block which will have the most free space after taking - // the request space out of the new block. - zret = block->alloc(n); - if (block->remaining() > _active->remaining()) { - block->next = _active; - _active = block; -#if 0 - // Defeat another clang analyzer false positive. Unit tests validate the code is correct. - ink_assert(_active.use_count() > 1); -#endif + Block *block = _active.head(); + + if (nullptr == block) { + block = this->make_block(n); + _active.prepend(block); + } else if (n > block->remaining()) { // too big, need another block + block = this->make_block(n); + // For the resulting active allocation block, pick the block which will have the most free space + // after taking the request space out of the new block. + if (block->remaining() - n > _active.head()->remaining()) { + _active.prepend(block); } else { - block->next = _active->next; - _active->next = block; -#if 0 - // Defeat another clang analyzer false positive. Unit tests validate the code is correct. - ink_assert(block.use_count() > 1); -#endif + _active.insert_after(_active.head(), block); } - } else { - zret = _active->alloc(n); } - return zret; + _active_allocated += n; + return block->alloc(n); } MemArena & MemArena::freeze(size_t n) { - _prev = _active; - _active.reset(); // it's in _prev now, start fresh. + this->destroy_frozen(); + _frozen = std::move(_active); // Update the meta data. - _prev_allocated = _active_allocated; + _frozen_allocated = _active_allocated; _active_allocated = 0; - _prev_reserved = _active_reserved; + _frozen_reserved = _active_reserved; _active_reserved = 0; _reserve_hint = n; @@ -120,36 +104,58 @@ MemArena::freeze(size_t n) MemArena & MemArena::thaw() { - _prev.reset(); - _prev_reserved = _prev_allocated = 0; + this->destroy_frozen(); + _frozen_reserved = _frozen_allocated = 0; return *this; } bool MemArena::contains(const void *ptr) const { - for (Block *b = _active.get(); b; b = b->next.get()) { - if (b->contains(ptr)) { - return true; - } - } - for (Block *b = _prev.get(); b; b = b->next.get()) { - if (b->contains(ptr)) { - return true; - } - } + auto pred = [ptr](const Block &b) -> bool { return b.contains(ptr); }; - return false; + return std::any_of(_active.begin(), _active.end(), pred) || std::any_of(_frozen.begin(), _frozen.end(), pred); +} + +void +MemArena::destroy_active() +{ + _active.apply([](Block *b) { delete b; }).clear(); +} + +void +MemArena::destroy_frozen() +{ + _frozen.apply([](Block *b) { delete b; }).clear(); } MemArena & MemArena::clear(size_t n) { - _reserve_hint = n ? n : _prev_allocated + _active_allocated; - _prev.reset(); - _prev_reserved = _prev_allocated = 0; - _active.reset(); + _reserve_hint = n ? n : _frozen_allocated + _active_allocated; + _frozen_reserved = _frozen_allocated = 0; _active_reserved = _active_allocated = 0; + this->destroy_frozen(); + this->destroy_active(); return *this; } + +MemArena::~MemArena() +{ + // Destruct in a way that makes it safe for the instance to be in one of its own memory blocks. + Block *ba = _active.head(); + Block *bf = _frozen.head(); + _active.clear(); + _frozen.clear(); + while (bf) { + Block *b = bf; + bf = bf->_link._next; + delete b; + } + while (ba) { + Block *b = ba; + ba = ba->_link._next; + delete b; + } +} diff --git a/src/tscore/unit_tests/test_MemArena.cc b/src/tscpp/util/unit_tests/test_MemArena.cc similarity index 80% rename from src/tscore/unit_tests/test_MemArena.cc rename to src/tscpp/util/unit_tests/test_MemArena.cc index 3492c9964d8..b93993e6e52 100644 --- a/src/tscore/unit_tests/test_MemArena.cc +++ b/src/tscpp/util/unit_tests/test_MemArena.cc @@ -4,32 +4,29 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include - #include -#include "tscore/MemArena.h" +#include "tscpp/util/MemArena.h" +#include "catch.hpp" + using ts::MemSpan; using ts::MemArena; using namespace std::literals; -TEST_CASE("MemArena generic", "[libts][MemArena]") +TEST_CASE("MemArena generic", "[[libtscpputil]][MemArena]") { ts::MemArena arena{64}; REQUIRE(arena.size() == 0); @@ -37,9 +34,11 @@ TEST_CASE("MemArena generic", "[libts][MemArena]") arena.alloc(0); REQUIRE(arena.size() == 0); REQUIRE(arena.reserved_size() >= 64); + REQUIRE(arena.remaining() >= 64); ts::MemSpan span1 = arena.alloc(32); REQUIRE(span1.size() == 32); + REQUIRE(arena.remaining() >= 32); ts::MemSpan span2 = arena.alloc(32); REQUIRE(span2.size() == 32); @@ -52,7 +51,7 @@ TEST_CASE("MemArena generic", "[libts][MemArena]") REQUIRE(extent < arena.reserved_size()); } -TEST_CASE("MemArena freeze and thaw", "[libts][MemArena]") +TEST_CASE("MemArena freeze and thaw", "[[libtscpputil]][MemArena]") { MemArena arena; MemSpan span1{arena.alloc(1024)}; @@ -114,7 +113,7 @@ TEST_CASE("MemArena freeze and thaw", "[libts][MemArena]") REQUIRE(arena.reserved_size() < 2 * 32000); } -TEST_CASE("MemArena helper", "[libts][MemArena]") +TEST_CASE("MemArena helper", "[[libtscpputil]][MemArena]") { struct Thing { int ten{10}; @@ -131,6 +130,7 @@ TEST_CASE("MemArena helper", "[libts][MemArena]") REQUIRE(arena.size() == 0); ts::MemSpan s = arena.alloc(56); REQUIRE(arena.size() == 56); + REQUIRE(arena.remaining() >= 200); void *ptr = s.begin(); REQUIRE(arena.contains((char *)ptr)); @@ -177,7 +177,7 @@ TEST_CASE("MemArena helper", "[libts][MemArena]") REQUIRE(thing_one->name == "Persia"); } -TEST_CASE("MemArena large alloc", "[libts][MemArena]") +TEST_CASE("MemArena large alloc", "[[libtscpputil]][MemArena]") { ts::MemArena arena; ts::MemSpan s = arena.alloc(4000); @@ -204,7 +204,7 @@ TEST_CASE("MemArena large alloc", "[libts][MemArena]") } } -TEST_CASE("MemArena block allocation", "[libts][MemArena]") +TEST_CASE("MemArena block allocation", "[[libtscpputil]][MemArena]") { ts::MemArena arena{64}; ts::MemSpan s = arena.alloc(32); @@ -227,7 +227,7 @@ TEST_CASE("MemArena block allocation", "[libts][MemArena]") REQUIRE((char *)s.begin() + 64 == s3.end()); } -TEST_CASE("MemArena full blocks", "[libts][MemArena]") +TEST_CASE("MemArena full blocks", "[[libtscpputil]][MemArena]") { // couple of large allocations - should be exactly sized in the generation. size_t init_size = 32000; @@ -250,3 +250,17 @@ TEST_CASE("MemArena full blocks", "[libts][MemArena]") REQUIRE(std::all_of(m2.begin(), m2.end(), [](uint8_t c) { return 0xc2 == c; })); REQUIRE(std::all_of(m3.begin(), m3.end(), [](uint8_t c) { return 0x56 == c; })); } + +TEST_CASE("MemArena esoterica", "[[libtscpputil]][MemArena]") +{ + MemArena a1; + MemSpan span; + { + MemArena a2{512}; + span = a2.alloc(128); + REQUIRE(a2.contains(span.data())); + a1 = std::move(a2); + } + REQUIRE(a1.contains(span.data())); + REQUIRE(a1.remaining() >= 384); +} From 593bea1123b3eedc025be711fba511ed622f31c4 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Sat, 20 Oct 2018 19:23:05 -0500 Subject: [PATCH 3/7] CacheTool: Cleanup TS_INLINE. --- src/traffic_cache_tool/CacheDefs.cc | 4 ++-- src/traffic_cache_tool/CacheDefs.h | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/traffic_cache_tool/CacheDefs.cc b/src/traffic_cache_tool/CacheDefs.cc index 642f7c2f773..820c5d10b89 100644 --- a/src/traffic_cache_tool/CacheDefs.cc +++ b/src/traffic_cache_tool/CacheDefs.cc @@ -415,13 +415,13 @@ dir_compare_tag(const CacheDirEntry *e, const CryptoHash *key) return (dir_tag(e) == DIR_MASK_TAG(key->slice32(2))); } -TS_INLINE int +int vol_in_phase_valid(Stripe *d, CacheDirEntry *e) { return (dir_offset(e) - 1 < ((d->_meta[0][0].write_pos + d->agg_buf_pos - d->_start) / CACHE_BLOCK_SIZE)); } -TS_INLINE int +int vol_out_of_phase_valid(Stripe *d, CacheDirEntry *e) { return (dir_offset(e) - 1 >= ((d->_meta[0][0].agg_pos - d->_start) / CACHE_BLOCK_SIZE)); diff --git a/src/traffic_cache_tool/CacheDefs.h b/src/traffic_cache_tool/CacheDefs.h index 40695b11261..065db639a74 100644 --- a/src/traffic_cache_tool/CacheDefs.h +++ b/src/traffic_cache_tool/CacheDefs.h @@ -400,7 +400,7 @@ namespace ct #define dir_in_seg(_s, _i) ((CacheDirEntry *)(((char *)(_s)) + (SIZEOF_DIR * (_i)))) -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_from_offset(int64_t i, CacheDirEntry *seg) { #if DIR_DEPTH < 5 @@ -413,26 +413,26 @@ dir_from_offset(int64_t i, CacheDirEntry *seg) #endif } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_bucket(int64_t b, CacheDirEntry *seg) { return dir_in_seg(seg, b * DIR_DEPTH); } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * next_dir(CacheDirEntry *d, CacheDirEntry *seg) { int i = dir_next(d); return dir_from_offset(i, seg); } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_bucket_row(CacheDirEntry *b, int64_t i) { return dir_in_seg(b, i); } -TS_INLINE int64_t +inline int64_t dir_to_offset(const CacheDirEntry *d, const CacheDirEntry *seg) { #if DIR_DEPTH < 5 @@ -555,12 +555,12 @@ struct Stripe { // This is because the freelist is not being copied to _metap[2][2] correctly. // need to do something about it .. hmmm :-? int dir_freelist_length(int s); - TS_INLINE CacheDirEntry * + inline CacheDirEntry * vol_dir_segment(int s) { return (CacheDirEntry *)(((char *)this->dir) + (s * this->_buckets) * DIR_DEPTH * SIZEOF_DIR); } - TS_INLINE CacheDirEntry * + inline CacheDirEntry * dir_segment(int s) { return vol_dir_segment(s); @@ -568,7 +568,7 @@ struct Stripe { Bytes stripe_offset(CacheDirEntry *e); // offset w.r.t the stripe content size_t vol_dirlen(); - TS_INLINE int + inline int vol_headerlen() { return ROUND_TO_STORE_BLOCK(sizeof(StripeMeta) + sizeof(uint16_t) * (this->_segments - 1)); From c4b8f626c9be36bd29112832882326ac29f22888 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Sun, 21 Oct 2018 13:47:20 -0500 Subject: [PATCH 4/7] YAML: LibSWOC IntrusiveHashMap refresh. --- .../{tscore => tscpp/util}/IntrusiveHashMap.h | 80 +++++++++---------- include/tscpp/util/Makefile.am | 1 + proxy/http/HttpConnectionCount.h | 4 +- proxy/http/HttpSessionManager.h | 6 +- src/tscore/Makefile.am | 1 - src/tscpp/util/Makefile.am | 1 + .../util}/unit_tests/test_IntrusiveHashMap.cc | 28 +++---- 7 files changed, 61 insertions(+), 60 deletions(-) rename include/{tscore => tscpp/util}/IntrusiveHashMap.h (90%) rename src/{tscore => tscpp/util}/unit_tests/test_IntrusiveHashMap.cc (87%) diff --git a/include/tscore/IntrusiveHashMap.h b/include/tscpp/util/IntrusiveHashMap.h similarity index 90% rename from include/tscore/IntrusiveHashMap.h rename to include/tscpp/util/IntrusiveHashMap.h index c787e4b358b..87d3500f6d5 100644 --- a/include/tscore/IntrusiveHashMap.h +++ b/include/tscpp/util/IntrusiveHashMap.h @@ -4,21 +4,18 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. + See the NOTICE file distributed with this work for additional information regarding copyright + ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance with the License. You may obtain a + copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Unless required by applicable law or agreed to in writing, software distributed under the License + is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing permissions and limitations under + the License. */ #pragma once @@ -28,6 +25,8 @@ #include #include "tscpp/util/IntrusiveDList.h" +namespace ts +{ /** Intrusive Hash Table. Values stored in this container are not destroyed when the container is destroyed or removed from the container. @@ -105,11 +104,11 @@ template class IntrusiveHashMap * All table elements are in this list. The buckets reference their starting element in the list, or nothing if * no elements are in that bucket. */ - using List = ts::IntrusiveDList; + using List = IntrusiveDList; /// A bucket for the hash map. struct Bucket { - /// Support for ts::IntrusiveDList, definitions and link storage. + /// Support for IntrusiveDList, definitions and link storage. struct Linkage { static Bucket *&next_ptr(Bucket *b); ///< Access next pointer. static Bucket *&prev_ptr(Bucket *b); ///< Access prev pointer. @@ -294,7 +293,7 @@ template class IntrusiveHashMap Table _table; ///< Array of buckets. /// List of non-empty buckets. - ts::IntrusiveDList _active_buckets; + IntrusiveDList _active_buckets; Bucket *bucket_for(key_type key); @@ -626,32 +625,32 @@ IntrusiveHashMap::erase(range const &r) -> iterator namespace detail { -// Make @c apply more convenient by allowing the function to take a reference type or pointer type to the container -// elements. The pointer type is the base, plus a shim to convert from a reference type functor to a pointer pointer -// type. The complex return type definition forces only one, but not both, to be valid for a particular functor. This -// also must be done via free functions and not method overloads because the compiler forces a match up of method -// definitions and declarations before any template instantiation. - -template -auto -IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) - -> decltype(f(*static_cast::value_type *>(nullptr)), map) -{ - return map.apply([&f](typename IntrusiveHashMap::value_type *v) { return f(*v); }); -} + // Make @c apply more convenient by allowing the function to take a reference type or pointer type to the container + // elements. The pointer type is the base, plus a shim to convert from a reference type functor to a pointer pointer + // type. The complex return type definition forces only one, but not both, to be valid for a particular functor. This + // also must be done via free functions and not method overloads because the compiler forces a match up of method + // definitions and declarations before any template instantiation. + + template + auto + IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) + -> decltype(f(*static_cast::value_type *>(nullptr)), map) + { + return map.apply([&f](typename IntrusiveHashMap::value_type *v) { return f(*v); }); + } -template -auto -IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) - -> decltype(f(static_cast::value_type *>(nullptr)), map) -{ - auto spot{map.begin()}; - auto limit{map.end()}; - while (spot != limit) { - f(spot++); // post increment means @a spot is updated before @a f is applied. + template + auto + IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) + -> decltype(f(static_cast::value_type *>(nullptr)), map) + { + auto spot{map.begin()}; + auto limit{map.end()}; + while (spot != limit) { + f(spot++); // post increment means @a spot is updated before @a f is applied. + } + return map; } - return map; -} } // namespace detail template @@ -727,4 +726,5 @@ IntrusiveHashMap::get_expansion_limit() const { return _expansion_limit; } -/* ---------------------------------------------------------------------------------------------- */ + +} // namespace ts diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index e24e1eed1fd..0c13e17fd64 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -20,6 +20,7 @@ library_includedir=$(includedir)/tscpp/util library_include_HEADERS = \ IntrusiveDList.h \ + IntrusiveHashMap.h \ MemArena.h \ MemSpan.h \ PostScript.h \ diff --git a/proxy/http/HttpConnectionCount.h b/proxy/http/HttpConnectionCount.h index 2d4efddfd6d..7ff0a6eecfe 100644 --- a/proxy/http/HttpConnectionCount.h +++ b/proxy/http/HttpConnectionCount.h @@ -33,7 +33,7 @@ #include "tscore/ink_config.h" #include "tscore/ink_mutex.h" #include "tscore/ink_inet.h" -#include "tscore/IntrusiveHashMap.h" +#include "tscpp/util/IntrusiveHashMap.h" #include "tscore/Diags.h" #include "tscore/CryptoHash.h" #include "tscore/BufferWriterForward.h" @@ -270,7 +270,7 @@ class OutboundConnTrack /// Internal implementation class instance. struct Imp { - IntrusiveHashMap _table; ///< Hash table of upstream groups. + ts::IntrusiveHashMap _table; ///< Hash table of upstream groups. std::mutex _mutex; ///< Lock for insert & find. }; static Imp _imp; diff --git a/proxy/http/HttpSessionManager.h b/proxy/http/HttpSessionManager.h index f83196aad0e..594ca2051f2 100644 --- a/proxy/http/HttpSessionManager.h +++ b/proxy/http/HttpSessionManager.h @@ -34,7 +34,7 @@ #include "P_EventSystem.h" #include "HttpServerSession.h" -#include "tscore/IntrusiveHashMap.h" +#include "tscpp/util/IntrusiveHashMap.h" class ProxyClientTransaction; class HttpSM; @@ -67,8 +67,8 @@ class ServerSessionPool : public Continuation static bool validate_sni(HttpSM *sm, NetVConnection *netvc); protected: - using IPTable = IntrusiveHashMap; - using FQDNTable = IntrusiveHashMap; + using IPTable = ts::IntrusiveHashMap; + using FQDNTable = ts::IntrusiveHashMap; public: /** Check if a session matches address and host name. diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index 2cf4709def8..c0ff6b7f549 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -253,7 +253,6 @@ test_tscore_SOURCES = \ unit_tests/test_Extendible.cc \ unit_tests/test_History.cc \ unit_tests/test_ink_inet.cc \ - unit_tests/test_IntrusiveHashMap.cc \ unit_tests/test_IntrusivePtr.cc \ unit_tests/test_IpMap.cc \ unit_tests/test_layout.cc \ diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index 5d7bd31ed77..cd126013020 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -28,6 +28,7 @@ libtscpputil_la_LDFLAGS = -no-undefined -version-info @TS_LIBTOOL_VERSION@ libtscpputil_la_SOURCES = \ IntrusiveDList.h \ + IntrusiveHashMap.h \ MemArena.h \ MemArena.cc \ MemSpan.h \ diff --git a/src/tscore/unit_tests/test_IntrusiveHashMap.cc b/src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc similarity index 87% rename from src/tscore/unit_tests/test_IntrusiveHashMap.cc rename to src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc index a0521a27a21..9cdbeb45365 100644 --- a/src/tscore/unit_tests/test_IntrusiveHashMap.cc +++ b/src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc @@ -4,20 +4,17 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -26,9 +23,12 @@ #include #include #include -#include -#include -#include "../../../tests/include/catch.hpp" + +#include "tscpp/util/IntrusiveHashMap.h" +#include "tscpp/util/bwf_base.h" +#include "catch.hpp" + +using ts::IntrusiveHashMap; // ------------- // --- TESTS --- From 230fe1d9ca8cafbe78fc024cc933075f0b17e3fd Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Sun, 21 Oct 2018 14:13:19 -0500 Subject: [PATCH 5/7] YAML: LibSWOC++ BufferWriter format update. --- include/tscore/BufferWriter.h | 897 -------------- include/tscore/BufferWriterForward.h | 150 --- include/tscore/CryptoHash.h | 4 +- include/tscore/SourceLocation.h | 6 +- include/tscore/ink_inet.h | 8 +- include/tscore/ts_file.h | 2 +- include/tscpp/util/BufferWriter.h | 556 +++++++++ include/tscpp/util/Makefile.am | 5 + include/tscpp/util/MemArena.h | 2 +- include/{tscore => tscpp/util}/Scalar.h | 89 +- include/tscpp/util/bwf_base.h | 963 ++++++++++++++ .../bwf_std_format.h => tscpp/util/bwf_ex.h} | 27 +- include/tscpp/util/bwf_printf.h | 79 ++ include/tscpp/util/bwf_std.h | 37 + iocore/eventsystem/IOBuffer.cc | 22 - iocore/eventsystem/I_MIOBufferWriter.h | 11 +- iocore/eventsystem/Makefile.am | 7 +- mgmt/LocalManager.cc | 8 +- proxy/IPAllow.cc | 6 +- proxy/http/ForwardedConfig.cc | 12 +- proxy/http/HttpConfig.h | 2 +- proxy/http/HttpConnectionCount.cc | 20 +- proxy/http/HttpConnectionCount.h | 10 +- proxy/http/HttpDebugNames.cc | 9 +- proxy/http/HttpDebugNames.h | 10 +- proxy/http/HttpProxyServerMain.cc | 17 +- proxy/http/HttpSM.cc | 2 +- proxy/http/HttpServerSession.cc | 4 +- proxy/http/HttpTransactHeaders.cc | 70 +- proxy/http/unit_tests/test_ForwardedConfig.cc | 2 +- src/traffic_cache_tool/CacheDefs.cc | 2 + src/traffic_cache_tool/CacheDefs.h | 2 +- src/traffic_cache_tool/CacheScan.cc | 1 + src/traffic_cache_tool/CacheTool.cc | 2 +- src/traffic_cache_tool/Makefile.inc | 3 +- src/traffic_manager/traffic_manager.cc | 2 +- src/tscore/BufferWriterFormat.cc | 1023 --------------- src/tscore/CryptoHash.cc | 5 +- src/tscore/Diags.cc | 10 +- src/tscore/Makefile.am | 4 - src/tscore/SourceLocation.cc | 6 +- src/tscore/ink_inet.cc | 23 +- src/tscore/unit_tests/test_History.cc | 8 +- src/tscore/unit_tests/test_ink_inet.cc | 72 +- src/tscpp/util/Makefile.am | 5 + src/tscpp/util/bw_format.cc | 1104 +++++++++++++++++ .../util}/unit_tests/test_BufferWriter.cc | 2 +- .../util}/unit_tests/test_Scalar.cc | 82 +- .../util/unit_tests/test_bw_format.cc} | 478 ++++--- 49 files changed, 3278 insertions(+), 2593 deletions(-) delete mode 100644 include/tscore/BufferWriter.h delete mode 100644 include/tscore/BufferWriterForward.h create mode 100644 include/tscpp/util/BufferWriter.h rename include/{tscore => tscpp/util}/Scalar.h (92%) create mode 100644 include/tscpp/util/bwf_base.h rename include/{tscore/bwf_std_format.h => tscpp/util/bwf_ex.h} (86%) create mode 100644 include/tscpp/util/bwf_printf.h create mode 100644 include/tscpp/util/bwf_std.h delete mode 100644 src/tscore/BufferWriterFormat.cc create mode 100644 src/tscpp/util/bw_format.cc rename src/{tscore => tscpp/util}/unit_tests/test_BufferWriter.cc (99%) rename src/{tscore => tscpp/util}/unit_tests/test_Scalar.cc (72%) rename src/{tscore/unit_tests/test_BufferWriterFormat.cc => tscpp/util/unit_tests/test_bw_format.cc} (59%) diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h deleted file mode 100644 index 052a4de29cf..00000000000 --- a/include/tscore/BufferWriter.h +++ /dev/null @@ -1,897 +0,0 @@ -/** @file - - Utilities for generating character sequences in buffers. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "tscpp/util/TextView.h" -#include "tscpp/util/MemSpan.h" -#include "tscore/BufferWriterForward.h" - -namespace ts -{ -/** Base (abstract) class for concrete buffer writers. - */ -class BufferWriter -{ -public: - /** Add the character @a c to the buffer. - - @a c is added only if there is room in the buffer. If not, the instance is put in to an error - state. In either case the value for @c extent is incremented. - - @internal If any variant of @c write discards any characters, the instance must be put in an - error state (indicated by the override of @c error). Derived classes must not assume the - write() functions will not be called when the instance is in an error state. - - @return @c *this - */ - virtual BufferWriter &write(char c) = 0; - - /** Add @a data to the buffer, up to @a length bytes. - - Data is added only up to the remaining room in the buffer. If the remaining capacity is - exceeded (i.e. data is not written to the output), the instance is put in to an error - state. In either case the value for @c extent is incremented by @a length. - - @internal This uses the single character write to output the data. It is presumed concrete - subclasses will override this method to use more efficient mechanisms, dependent on the type - of output buffer. - - @return @c *this - */ - virtual BufferWriter & - write(const void *data, size_t length) - { - const char *d = static_cast(data); - - while (length--) { - write(*(d++)); - } - return *this; - } - - /** Add the contents of @a sv to the buffer, up to the size of the view. - - Data is added only up to the remaining room in the buffer. If the remaining capacity is - exceeded (i.e. data is not written to the output), the instance is put in to an error - state. In either case the value for @c extent is incremented by the size of @a sv. - - @return @c *this - */ - BufferWriter & - write(const std::string_view &sv) - { - return write(sv.data(), sv.size()); - } - - /// Get the address of the first byte in the output buffer. - virtual const char *data() const = 0; - - /// Get the error state. - /// @return @c true if in an error state, @c false if not. - virtual bool error() const = 0; - - /** Get the address of the next output byte in the buffer. - - Succeeding calls to non-const member functions, other than this method, must be presumed to - invalidate the current auxiliary buffer (contents and address). - - Care must be taken to not write to data beyond this plus @c remaining bytes. Usually the - safest mechanism is to create a @c FixedBufferWriter on the auxillary buffer and write to that. - - @code - ts::FixedBufferWriter subw(w.auxBuffer(), w.remaining()); - write_some_stuff(subw); // generate output into the buffer. - w.fill(subw.extent()); // update main buffer writer. - @endcode - - @return Address of the next output byte, or @c nullptr if there is no remaining capacity. - */ - virtual char * - auxBuffer() - { - return nullptr; - } - - /** Advance the buffer position @a n bytes. - - This treats the next @a n bytes as being written without changing the content. This is useful - only in conjuction with @a auxBuffer to indicate that @a n bytes of the auxillary buffer has - been written by some other mechanism. - - @internal Concrete subclasses @b must override this to advance in a way consistent with the - specific buffer type. - - @return @c *this - */ - virtual BufferWriter & - fill(size_t n) - { - return *this; - } - - /// Get the total capacity. - /// @return The total number of bytes that can be written without causing an error condition. - virtual size_t capacity() const = 0; - - /// Get the extent. - /// @return Total number of characters that have been written, including those discarded due to an error condition. - virtual size_t extent() const = 0; - - /// Get the output size. - /// @return Total number of characters that are in the buffer (successfully written and not discarded) - size_t - size() const - { - return std::min(this->extent(), this->capacity()); - } - - /// Get the remaining buffer space. - /// @return Number of additional characters that can be written without causing an error condidtion. - size_t - remaining() const - { - return capacity() - size(); - } - - /// Reduce the capacity by @a n bytes - /// If the capacity is reduced below the current @c size the instance goes in to an error state. - /// @return @c *this - virtual BufferWriter &clip(size_t n) = 0; - - /// Increase the capacity by @a n bytes. - /// If there is an error condition, this function clears it and sets the extent to the size. It - /// then increases the capacity by n characters. - virtual BufferWriter &extend(size_t n) = 0; - - // Force virtual destructor. - virtual ~BufferWriter() {} - - /** BufferWriter print. - - This prints its arguments to the @c BufferWriter @a w according to the format @a fmt. The format - string is based on Python style formating, each argument substitution marked by braces, {}. Each - specification has three parts, a @a name, a @a specifier, and an @a extention. These are - separated by colons. The name should be either omitted or a number, the index of the argument to - use. If omitted the place in the format string is used as the argument index. E.g. "{} {} {}", - "{} {1} {}", and "{0} {1} {2}" are equivalent. Using an explicit index does not reset the - position of subsequent substiations, therefore "{} {0} {}" is equivalent to "{0} {0} {2}". - */ - template BufferWriter &print(TextView fmt, Rest &&... rest); - /** Print overload to take arguments as a tuple instead of explicitly. - This is useful for forwarding variable arguments from other functions / methods. - */ - template BufferWriter &printv(TextView fmt, std::tuple const &args); - - /// Print using a preparsed @a fmt. - template BufferWriter &print(BWFormat const &fmt, Args &&... args); - /** Print overload to take arguments as a tuple instead of explicitly. - This is useful for forwarding variable arguments from other functions / methods. - */ - template BufferWriter &printv(BWFormat const &fmt, std::tuple const &args); - - /// Output the buffer contents to the @a stream. - /// @return The destination stream. - virtual std::ostream &operator>>(std::ostream &stream) const = 0; - /// Output the buffer contents to the file for file descriptor @a fd. - /// @return The number of bytes written. - virtual ssize_t operator>>(int fd) const = 0; -}; - -/** A @c BufferWrite concrete subclass to write to a fixed size buffer. - */ -class FixedBufferWriter : public BufferWriter -{ - using super_type = BufferWriter; - using self_type = FixedBufferWriter; - -public: - /** Construct a buffer writer on a fixed @a buffer of size @a capacity. - - If writing goes past the end of the buffer, the excess is dropped. - - @note If you create a instance of this class with capacity == 0 (and a nullptr buffer), you - can use it to measure the number of characters a series of writes would result it (from the - extent() value) without actually writing. - */ - FixedBufferWriter(char *buffer, size_t capacity); - - /** Construct empty buffer. - * This is useful for doing sizing before allocating a buffer. - */ - FixedBufferWriter(std::nullptr_t); - - FixedBufferWriter(const FixedBufferWriter &) = delete; - FixedBufferWriter &operator=(const FixedBufferWriter &) = delete; - /// Move constructor. - FixedBufferWriter(FixedBufferWriter &&) = default; - /// Move assignment. - FixedBufferWriter &operator=(FixedBufferWriter &&) = default; - - FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast(span.size())) {} - - /// Write a single character @a c to the buffer. - FixedBufferWriter & - write(char c) override - { - if (_attempted < _capacity) { - _buf[_attempted] = c; - } - ++_attempted; - - return *this; - } - - /// Write @a data to the buffer, up to @a length bytes. - FixedBufferWriter & - write(const void *data, size_t length) override - { - const size_t newSize = _attempted + length; - - if (_buf) { - if (newSize <= _capacity) { - std::memcpy(_buf + _attempted, data, length); - } else if (_attempted < _capacity) { - std::memcpy(_buf + _attempted, data, _capacity - _attempted); - } - } - _attempted = newSize; - - return *this; - } - - // Bring in non-overridden methods. - using super_type::write; - - /// Return the output buffer. - const char * - data() const override - { - return _buf; - } - - /// Return whether there has been an error. - bool - error() const override - { - return _attempted > _capacity; - } - - /// Get the start of the unused output buffer. - char * - auxBuffer() override - { - return error() ? nullptr : _buf + _attempted; - } - - /// Advance the used part of the output buffer. - FixedBufferWriter & - fill(size_t n) override - { - _attempted += n; - - return *this; - } - - /// Get the total capacity of the output buffer. - size_t - capacity() const override - { - return _capacity; - } - - /// Get the total output sent to the writer. - size_t - extent() const override - { - return _attempted; - } - - /// Reduce the capacity by @a n. - FixedBufferWriter & - clip(size_t n) override - { - ink_assert(n <= _capacity); - - _capacity -= n; - - return *this; - } - - /// Extend the capacity by @a n. - FixedBufferWriter & - extend(size_t n) override - { - if (error()) { - _attempted = _capacity; - } - - _capacity += n; - - return *this; - } - - /// Reduce extent to @a n. - /// If @a n is less than the capacity the error condition, if any, is cleared. - /// This can be used to clear the output by calling @c reduce(0). In contrast - /// to @c clip this reduces the data in the buffer, rather than the capacity. - self_type & - reduce(size_t n) - { - ink_assert(n <= _attempted); - - _attempted = n; - return *this; - } - - /// Clear the buffer, reset to empty (no data). - /// This is a convenience for reusing a buffer. For instance - /// @code - /// bw.reset().print("....."); // clear old data and print new data. - /// @endcode - /// This is equivalent to @c reduce(0) but clearer for that case. - self_type & - reset() - { - _attempted = 0; - return *this; - } - - /// Provide a string_view of all successfully written characters. - std::string_view - view() const - { - return std::string_view(_buf, size()); - } - - /// Provide a @c string_view of all successfully written characters as a user conversion. - operator std::string_view() const { return view(); } - - /** Get a @c FixedBufferWriter for the unused output buffer. - - If @a reserve is non-zero then the buffer size for the auxillary writer will be @a reserve bytes - smaller than the remaining buffer. This "reserves" space for additional output after writing - to the auxillary buffer, in a manner similar to @c clip / @c extend. - */ - FixedBufferWriter - auxWriter(size_t reserve = 0) - { - return {this->auxBuffer(), reserve < this->remaining() ? this->remaining() - reserve : 0}; - } - - /// Output the buffer contents to the @a stream. - std::ostream &operator>>(std::ostream &stream) const override; - /// Output the buffer contents to the file for file descriptor @a fd. - ssize_t operator>>(int fd) const override; - - // Overrides for co-variance - template self_type &print(TextView fmt, Rest &&... rest); - template self_type &printv(TextView fmt, std::tuple const &args); - template self_type &print(BWFormat const &fmt, Args &&... args); - template self_type &printv(BWFormat const &fmt, std::tuple const &args); - -protected: - char *const _buf; ///< Output buffer. - size_t _capacity; ///< Size of output buffer. - size_t _attempted = 0; ///< Number of characters written, including those discarded due error condition. -private: - // INTERNAL - Overload removed, make sure it's not used. - BufferWriter &write(size_t n); -}; - -/** A buffer writer that writes to an array of char (of fixed size N) that is internal to the writer instance. - - It's called 'local' because instances are typically declared as stack-allocated, local function - variables. -*/ -template class LocalBufferWriter : public FixedBufferWriter -{ - using self_type = LocalBufferWriter; - using super_type = FixedBufferWriter; - -public: - /// Construct an empty writer. - LocalBufferWriter() : FixedBufferWriter(_arr, N) {} - - /// Copy another writer. - /// Any data in @a that is copied over. - LocalBufferWriter(const LocalBufferWriter &that) : FixedBufferWriter(_arr, N) - { - std::memcpy(_arr, that._arr, that.size()); - _attempted = that._attempted; - } - - /// Copy another writer. - /// Any data in @a that is copied over. - template LocalBufferWriter(const LocalBufferWriter &that) : FixedBufferWriter(_arr, N) - { - size_t n = std::min(N, that.size()); - std::memcpy(_arr, that.data(), n); - // if a bigger space here, don't leave a gap between size and attempted. - _attempted = N > K ? n : that.extent(); - } - - /// Copy another writer. - /// Any data in @a that is copied over. - LocalBufferWriter & - operator=(const LocalBufferWriter &that) - { - if (this != &that) { - _attempted = that.extent(); - std::memcpy(_buf, that._buf, that.size()); - } - - return *this; - } - - /// Copy another writer. - /// Any data in @a that is copied over. - template - LocalBufferWriter & - operator=(const LocalBufferWriter &that) - { - size_t n = std::min(N, that.size()); - // if a bigger space here, don't leave a gap between size and attempted. - _attempted = N > K ? n : that.extent(); - std::memcpy(_arr, that.data(), n); - return *this; - } - - /// Increase capacity by @a n. - LocalBufferWriter & - extend(size_t n) override - { - if (error()) { - _attempted = _capacity; - } - - _capacity += n; - - ink_assert(_capacity <= N); - - return *this; - } - -protected: - char _arr[N]; ///< output buffer. -}; - -// --------------- Implementation -------------------- -/** Overridable formatting for type @a V. - - This is the output generator for data to a @c BufferWriter. Default stream operators call this with - the default format specification (although those can be overloaded specifically for performance). - User types should overload this function to format output for that type. - - @code - BufferWriter & - bwformat(BufferWriter &w, BWFSpec &, V const &v) - { - // generate output on @a w - } - @endcode - - The argument can be passed by value if that would be more efficient. - */ - -namespace bw_fmt -{ - /// Internal signature for template generated formatting. - /// @a args is a forwarded tuple of arguments to be processed. - template using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, BWFSpec const &, TUPLE const &args); - - /// Internal error / reporting message generators - void Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n); - - // MSVC will expand the parameter pack inside a lambda but not gcc, so this indirection is required. - - /// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This - /// (or the equivalent lambda) is needed because the array of formatters must have a homogenous - /// signature, not vary per argument. Effectively this indirection erases the type of the specific - /// argument being formatted. Instances of this have the signature @c ArgFormatterSignature. - template - BufferWriter & - Arg_Formatter(BufferWriter &w, BWFSpec const &spec, TUPLE const &args) - { - return bwformat(w, spec, std::get(args)); - } - - /// This exists only to expand the index sequence into an array of formatters for the tuple type - /// @a TUPLE. Due to langauge limitations it cannot be done directly. The formatters can be - /// accessed via standard array access in constrast to templated tuple access. The actual array is - /// static and therefore at run time the only operation is loading the address of the array. - template - ArgFormatterSignature * - Get_Arg_Formatter_Array(std::index_sequence) - { - static ArgFormatterSignature fa[sizeof...(N)] = {&bw_fmt::Arg_Formatter...}; - return fa; - } - - /// Perform alignment adjustments / fill on @a w of the content in @a lw. - /// This is the normal mechanism, but a number of the builtin types handle this internally - /// for performance reasons. - void Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw); - - /// Global named argument table. - using GlobalSignature = void (*)(BufferWriter &, BWFSpec const &); - using GlobalTable = std::map; - extern GlobalTable BWF_GLOBAL_TABLE; - extern GlobalSignature Global_Table_Find(std::string_view name); - - /// Generic integral conversion. - BufferWriter &Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t n, bool negative_p); - - /// Generic floating point conversion. - BufferWriter &Format_Floating(BufferWriter &w, BWFSpec const &spec, double n, bool negative_p); - -} // namespace bw_fmt - -using BWGlobalNameSignature = bw_fmt::GlobalSignature; -/// Add a global @a name to BufferWriter formatting, output generated by @a formatter. -/// @return @c true if the name was register, @c false if not (name already in use). -bool bwf_register_global(std::string_view name, BWGlobalNameSignature formatter); - -/** Compiled BufferWriter format. - - @note This is not as useful as hoped, the performance is not much better using this than parsing - on the fly (about 30% better, which is fine for tight loops but not for general use). - */ -class BWFormat -{ -public: - /// Construct from a format string @a fmt. - BWFormat(TextView fmt); - ~BWFormat(); - - /** Parse elements of a format string. - - @param fmt The format string [in|out] - @param literal A literal if found - @param spec A specifier if found (less enclosing braces) - @return @c true if a specifier was found, @c false if not. - - Pull off the next literal and/or specifier from @a fmt. The return value distinguishes - the case of no specifier found (@c false) or an empty specifier (@c true). - - */ - static bool parse(TextView &fmt, std::string_view &literal, std::string_view &spec); - - /** Parsed items from the format string. - - Literals are handled by putting the literal text in the extension field and setting the - global formatter @a _gf to @c LiteralFormatter, which writes out the extension as a literal. - */ - struct Item { - BWFSpec _spec; ///< Specification. - /// If the spec has a global formatter name, cache it here. - mutable bw_fmt::GlobalSignature _gf = nullptr; - - Item() {} - Item(BWFSpec const &spec, bw_fmt::GlobalSignature gf) : _spec(spec), _gf(gf) {} - }; - - using Items = std::vector; - Items _items; ///< Items from format string. - -protected: - /// Handles literals by writing the contents of the extension directly to @a w. - static void Format_Literal(BufferWriter &w, BWFSpec const &spec); -}; - -template -BufferWriter & -BufferWriter::print(TextView fmt, Args &&... args) -{ - return this->printv(fmt, std::forward_as_tuple(args...)); -} - -template -BufferWriter & -BufferWriter::printv(TextView fmt, std::tuple const &args) -{ - using namespace std::literals; - static constexpr int N = sizeof...(Args); // used as loop limit - static const auto fa = bw_fmt::Get_Arg_Formatter_Array(std::index_sequence_for{}); - int arg_idx = 0; // the next argument index to be processed. - - while (fmt.size()) { - // Next string piece of interest is an (optional) literal and then an (optinal) format specifier. - // There will always be a specifier except for the possible trailing literal. - std::string_view lit_v; - std::string_view spec_v; - bool spec_p = BWFormat::parse(fmt, lit_v, spec_v); - - if (lit_v.size()) { - this->write(lit_v); - } - - if (spec_p) { - BWFSpec spec{spec_v}; // parse the specifier. - size_t width = this->remaining(); - if (spec._max < width) { - width = spec._max; - } - FixedBufferWriter lw{this->auxBuffer(), width}; - - if (spec._name.size() == 0) { - spec._idx = arg_idx; - } - if (0 <= spec._idx) { - if (spec._idx < N) { - fa[spec._idx](lw, spec, args); - } else { - bw_fmt::Err_Bad_Arg_Index(lw, spec._idx, N); - } - ++arg_idx; - } else if (spec._name.size()) { - auto gf = bw_fmt::Global_Table_Find(spec._name); - if (gf) { - gf(lw, spec); - } else { - lw.write("{~"sv).write(spec._name).write("~}"sv); - } - } - if (lw.extent()) { - bw_fmt::Do_Alignment(spec, *this, lw); - } - } - } - return *this; -} - -template -BufferWriter & -BufferWriter::print(BWFormat const &fmt, Args &&... args) -{ - return this->printv(fmt, std::forward_as_tuple(args...)); -} - -template -BufferWriter & -BufferWriter::printv(BWFormat const &fmt, std::tuple const &args) -{ - using namespace std::literals; - static constexpr int N = sizeof...(Args); - static const auto fa = bw_fmt::Get_Arg_Formatter_Array(std::index_sequence_for{}); - - for (BWFormat::Item const &item : fmt._items) { - size_t width = this->remaining(); - if (item._spec._max < width) { - width = item._spec._max; - } - FixedBufferWriter lw{this->auxBuffer(), width}; - if (item._gf) { - item._gf(lw, item._spec); - } else { - auto idx = item._spec._idx; - if (0 <= idx && idx < N) { - fa[idx](lw, item._spec, args); - } else if (item._spec._name.size()) { - lw.write("{~"sv).write(item._spec._name).write("~}"sv); - } - } - bw_fmt::Do_Alignment(item._spec, *this, lw); - } - return *this; -} - -// Pointers that are not specialized. -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, const void *ptr) -{ - BWFSpec ptr_spec{spec}; - ptr_spec._radix_lead_p = true; - if (ptr_spec._type == BWFSpec::DEFAULT_TYPE || ptr_spec._type == 'p') { - ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex. - } else if (ptr_spec._type == 'P') { - ptr_spec._type = 'X'; // P means upper hex, overriding other specializations. - } - return bw_fmt::Format_Integer(w, ptr_spec, reinterpret_cast(ptr), false); -} - -// MemSpan -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span); - -// -- Common formatters -- - -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv); - -template -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, const char (&a)[N]) -{ - return bwformat(w, spec, std::string_view(a, N - 1)); -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, const char *v) -{ - if (spec._type == 'x' || spec._type == 'X') { - bwformat(w, spec, static_cast(v)); - } else { - bwformat(w, spec, std::string_view(v)); - } - return w; -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, TextView tv) -{ - return bwformat(w, spec, static_cast(tv)); -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, std::string const &s) -{ - return bwformat(w, spec, std::string_view{s}); -} - -template -auto -bwformat(BufferWriter &w, BWFSpec const &spec, F &&f) -> - typename std::enable_if::type>::value, BufferWriter &>::type -{ - return f < 0 ? bw_fmt::Format_Floating(w, spec, -f, true) : bw_fmt::Format_Floating(w, spec, f, false); -} - -/* Integer types. - - Due to some oddities for MacOS building, need a bit more template magic here. The underlying - integer rendering is in @c Format_Integer which takes @c intmax_t or @c uintmax_t. For @c - bwformat templates are defined, one for signed and one for unsigned. These forward their argument - to the internal renderer. To avoid additional ambiguity the template argument is checked with @c - std::enable_if to invalidate the overload if the argument type isn't a signed / unsigned - integer. One exception to this is @c char which is handled by a previous overload in order to - treat the value as a character and not an integer. The overall benefit is this works for any set - of integer types, rather tuning and hoping to get just the right set of overloads. - */ - -template -auto -bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) -> - typename std::enable_if::type>::value && - std::is_integral::type>::value, - BufferWriter &>::type -{ - return bw_fmt::Format_Integer(w, spec, i, false); -} - -template -auto -bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) -> - typename std::enable_if::type>::value && - std::is_integral::type>::value, - BufferWriter &>::type -{ - return i < 0 ? bw_fmt::Format_Integer(w, spec, -i, true) : bw_fmt::Format_Integer(w, spec, i, false); -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &, char c) -{ - return w.write(c); -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bool f) -{ - using namespace std::literals; - if ('s' == spec._type) { - w.write(f ? "true"sv : "false"sv); - } else if ('S' == spec._type) { - w.write(f ? "TRUE"sv : "FALSE"sv); - } else { - bw_fmt::Format_Integer(w, spec, static_cast(f), false); - } - return w; -} - -// Generically a stream operator is a formatter with the default specification. -template -BufferWriter & -operator<<(BufferWriter &w, V &&v) -{ - return bwformat(w, BWFSpec::DEFAULT, std::forward(v)); -} - -// std::string support -/** Print to a @c std::string - - Print to the string @a s. If there is overflow then resize the string sufficiently to hold the output - and print again. The effect is the string is resized only as needed to hold the output. - */ -template -std::string & -bwprintv(std::string &s, ts::TextView fmt, std::tuple const &args) -{ - auto len = s.size(); // remember initial size - size_t n = ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)).extent(); - s.resize(n); // always need to resize - if shorter, must clip pre-existing text. - if (n > len) { // dropped data, try again. - ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)); - } - return s; -} - -template -std::string & -bwprint(std::string &s, ts::TextView fmt, Args &&... args) -{ - return bwprintv(s, fmt, std::forward_as_tuple(args...)); -} - -// -- FixedBufferWriter -- -inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {} - -inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buf(buffer), _capacity(capacity) -{ - ink_assert(_capacity == 0 || buffer != nullptr); -} - -template -inline auto -FixedBufferWriter::print(TextView fmt, Args &&... args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); -} - -template -inline auto -FixedBufferWriter::printv(TextView fmt, std::tuple const &args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, args)); -} - -template -inline auto -FixedBufferWriter::print(BWFormat const &fmt, Args &&... args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); -} - -template -inline auto -FixedBufferWriter::printv(BWFormat const &fmt, std::tuple const &args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, args)); -} - -} // end namespace ts - -namespace std -{ -inline ostream & -operator<<(ostream &s, ts::BufferWriter const &w) -{ - return w >> s; -} -} // end namespace std diff --git a/include/tscore/BufferWriterForward.h b/include/tscore/BufferWriterForward.h deleted file mode 100644 index 9bc874c57df..00000000000 --- a/include/tscore/BufferWriterForward.h +++ /dev/null @@ -1,150 +0,0 @@ -/** @file - - Forward definitions for BufferWriter formatting. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "tscpp/util/TextView.h" -#include "tscore/ink_assert.h" - -namespace ts -{ -/** A parsed version of a format specifier. - */ -struct BWFSpec { - using self_type = BWFSpec; ///< Self reference type. - static constexpr char DEFAULT_TYPE = 'g'; ///< Default format type. - - /// Constructor a default instance. - constexpr BWFSpec() {} - - /// Construct by parsing @a fmt. - BWFSpec(TextView fmt); - - char _fill = ' '; ///< Fill character. - char _sign = '-'; ///< Numeric sign style, space + - - enum class Align : char { - NONE, ///< No alignment. - LEFT, ///< Left alignment '<'. - RIGHT, ///< Right alignment '>'. - CENTER, ///< Center alignment '^'. - SIGN ///< Align plus/minus sign before numeric fill. '=' - } _align = Align::NONE; ///< Output field alignment. - char _type = DEFAULT_TYPE; ///< Type / radix indicator. - bool _radix_lead_p = false; ///< Print leading radix indication. - // @a _min is unsigned because there's no point in an invalid default, 0 works fine. - unsigned int _min = 0; ///< Minimum width. - int _prec = -1; ///< Precision - unsigned int _max = std::numeric_limits::max(); ///< Maxium width - int _idx = -1; ///< Positional "name" of the specification. - std::string_view _name; ///< Name of the specification. - std::string_view _ext; ///< Extension if provided. - - static const self_type DEFAULT; - - /// Validate @a c is a specifier type indicator. - static bool is_type(char c); - /// Check if the type flag is numeric. - static bool is_numeric_type(char c); - /// Check if the type is an upper case variant. - static bool is_upper_case_type(char c); - /// Check if the type @a in @a this is numeric. - bool has_numeric_type() const; - /// Check if the type in @a this is an upper case variant. - bool has_upper_case_type() const; - /// Check if the type is a raw pointer. - bool has_pointer_type() const; - -protected: - /// Validate character is alignment character and return the appropriate enum value. - Align align_of(char c); - - /// Validate is sign indicator. - bool is_sign(char c); - - /// Handrolled initialization the character syntactic property data. - static const struct Property { - Property(); ///< Default constructor, creates initialized flag set. - /// Flag storage, indexed by character value. - uint8_t _data[0x100]; - /// Flag mask values. - static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type. - static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character. - static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag. - static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output. - static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character. - } _prop; -}; - -inline BWFSpec::Align -BWFSpec::align_of(char c) -{ - return static_cast(_prop._data[static_cast(c)] & Property::ALIGN_MASK); -} - -inline bool -BWFSpec::is_sign(char c) -{ - return _prop._data[static_cast(c)] & Property::SIGN_CHAR; -} - -inline bool -BWFSpec::is_type(char c) -{ - return _prop._data[static_cast(c)] & Property::TYPE_CHAR; -} - -inline bool -BWFSpec::is_upper_case_type(char c) -{ - return _prop._data[static_cast(c)] & Property::UPPER_TYPE_CHAR; -} - -inline bool -BWFSpec::has_numeric_type() const -{ - return _prop._data[static_cast(_type)] & Property::NUMERIC_TYPE_CHAR; -} - -inline bool -BWFSpec::has_upper_case_type() const -{ - return _prop._data[static_cast(_type)] & Property::UPPER_TYPE_CHAR; -} - -inline bool -BWFSpec::has_pointer_type() const -{ - return _type == 'p' || _type == 'P'; -} - -class BWFormat; - -class BufferWriter; - -} // namespace ts diff --git a/include/tscore/CryptoHash.h b/include/tscore/CryptoHash.h index c9e1835f264..25f359c26f5 100644 --- a/include/tscore/CryptoHash.h +++ b/include/tscore/CryptoHash.h @@ -22,7 +22,7 @@ #pragma once -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include /// Apache Traffic Server commons. @@ -180,7 +180,7 @@ CryptoContext::finalize(CryptoHash &hash) return reinterpret_cast(_obj)->finalize(hash); } -ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, ats::CryptoHash const &hash); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, ats::CryptoHash const &hash); } // namespace ats diff --git a/include/tscore/SourceLocation.h b/include/tscore/SourceLocation.h index e3af488b383..c719064db48 100644 --- a/include/tscore/SourceLocation.h +++ b/include/tscore/SourceLocation.h @@ -23,7 +23,7 @@ #pragma once -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" // The SourceLocation class wraps up a source code location, including // file name, function name, and line number, and contains a method to @@ -59,13 +59,13 @@ class SourceLocation } char *str(char *buf, int buflen) const; - ts::BufferWriter &print(ts::BufferWriter &w, ts::BWFSpec const &spec) const; + ts::BufferWriter &print(ts::BufferWriter &w, ts::bwf::Spec const &spec) const; }; namespace ts { inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, SourceLocation const &loc) +bwformat(BufferWriter &w, bwf::Spec const &spec, SourceLocation const &loc) { return loc.print(w, spec); } diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h index 4cbf14c1d41..5076ff3e927 100644 --- a/include/tscore/ink_inet.h +++ b/include/tscore/ink_inet.h @@ -31,7 +31,7 @@ #include "tscore/ink_memory.h" #include "tscore/ink_apidefs.h" -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" #if !TS_HAS_IN6_IS_ADDR_UNSPECIFIED #if defined(IN6_IS_ADDR_UNSPECIFIED) @@ -1551,10 +1551,10 @@ IpEndpoint::setToLoopback(int family) // BufferWriter formatting support. namespace ts { -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IpAddr const &addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr); inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, IpEndpoint const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, IpEndpoint const &addr) { return bwformat(w, spec, &addr.sa); } diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h index b5ec194a1f4..fc1944d2ad1 100644 --- a/include/tscore/ts_file.h +++ b/include/tscore/ts_file.h @@ -30,7 +30,7 @@ #include #include "tscore/ink_memory.h" #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" namespace ts { diff --git a/include/tscpp/util/BufferWriter.h b/include/tscpp/util/BufferWriter.h new file mode 100644 index 00000000000..b23949aa607 --- /dev/null +++ b/include/tscpp/util/BufferWriter.h @@ -0,0 +1,556 @@ +/** @file + + Utilities for generating character sequences in buffers. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/TextView.h" +#include "tscpp/util/MemSpan.h" + +namespace ts +{ +namespace bwf +{ + struct Spec; + class Format; + class BoundNames; +} // namespace bwf + +/** Base (abstract) class for concrete buffer writers. + */ +class BufferWriter +{ +public: + /** Add the character @a c to the buffer. + + @a c is added only if there is room in the buffer. If not, the instance is put in to an error + state. In either case the value for @c extent is incremented. + + @internal If any variant of @c write discards any characters, the instance must be put in an + error state (indicated by the override of @c error). Derived classes must not assume the + write() functions will not be called when the instance is in an error state. + + @return @c *this + */ + virtual BufferWriter &write(char c) = 0; + + /** Add @a data to the buffer, up to @a length bytes. + + Data is added only up to the remaining room in the buffer. If the remaining capacity is + exceeded (i.e. data is not written to the output), the instance is put in to an error + state. In either case the value for @c extent is incremented by @a length. + + @internal This uses the single character write to output the data. It is presumed concrete + subclasses will override this method to use more efficient mechanisms, dependent on the type + of output buffer. + + @return @c *this + */ + virtual BufferWriter &write(const void *data, size_t length); + + /** Add the contents of @a sv to the buffer, up to the size of the view. + + Data is added only up to the remaining room in the buffer. If the remaining capacity is + exceeded (i.e. data is not written to the output), the instance is put in to an error + state. In either case the value for @c extent is incremented by the size of @a sv. + + @return @c *this + */ + BufferWriter &write(const std::string_view &sv); + + /// Get the address of the first byte in the output buffer. + virtual const char *data() const = 0; + + /// Get the error state. + /// @return @c true if in an error state, @c false if not. + virtual bool error() const = 0; + + /** Get the address of the next output byte in the buffer. + + Succeeding calls to non-const member functions, other than this method, must be presumed to + invalidate the current auxiliary buffer (contents and address). + + Care must be taken to not write to data beyond this plus @c remaining bytes. Usually the + safest mechanism is to create a @c FixedBufferWriter on the auxillary buffer and write to that. + + @code + ts::FixedBufferWriter subw(w.aux_data(), w.remaining()); + write_some_stuff(subw); // generate output into the buffer. + w.fill(subw.extent()); // update main buffer writer. + @endcode + + @return Address of the next output byte, or @c nullptr if there is no remaining capacity. + */ + virtual char *aux_data(); + + /// Get the total capacity. + /// @return The total number of bytes that can be written without causing an error condition. + virtual size_t capacity() const = 0; + + /// Get the extent. + /// @return Total number of characters that have been written, including those discarded due to an error condition. + virtual size_t extent() const = 0; + + /// Get the output size. + /// @return Total number of characters that are in the buffer (successfully written and not discarded) + size_t size() const; + + /// Get the remaining buffer space. + /// @return Number of additional characters that can be written without causing an error condidtion. + size_t remaining() const; + + /** Increase the extent by @a n bytes. + + @param n The number of bytes to add to the extent. + + The buffer content is unchanged, only the extent value is adjusted. + + This is useful + when doing local buffer filling using @c aux_data. After writing data there, it can be + added to the extent using this method. + + @internal Concrete subclasses @b must override this to advance in a way consistent with the + specific buffer type. + + @return @c *this + */ + virtual BufferWriter &commit(size_t n) = 0; + + /** Decrease the extent by @a n. + * + * @param n Number of bytes to remove from the extent. + * + * The buffer content is unchanged, only the extent value is adjusted. + */ + virtual BufferWriter &discard(size_t n) = 0; + + /// Reduce the capacity by @a n bytes + /// If the capacity is reduced below the current @c size the instance goes in to an error state. + /// @see restore + /// @return @c *this + virtual BufferWriter &restrict(size_t n) = 0; + + /// Restore @a n bytes of capacity. + /// If there is an error condition, this function clears it and sets the extent to the size. It + /// then increases the capacity by n characters. + /// @note It is required that any restored capacity have been previously removed by @c shrink. + /// @see shrink + virtual BufferWriter &restore(size_t n) = 0; + + /** Copy data from one part of the buffer to another. + * + * The copy is guaranteed to be correct even if the @a src and @a dst overlap. The regions are + * clipped by the current extent. That is, bytes cannot be copied to nor from unwritten buffer. + * If the extent is currently more than the capacity, the copy is performed as if the buffer + * existed and then clipped to the actual buffer space. + * + * @param dst Offset of the first by to copy onto. + * @param src Offset of the first byte to copy from. + * @param n Number of bytes to copy. + * @return @c *this + */ + virtual BufferWriter ©(size_t dst, size_t src, size_t n) = 0; + + // Force virtual destructor. + virtual ~BufferWriter(); + + /** BufferWriter print. + + This prints its arguments to the @c BufferWriter @a w according to the format @a fmt. The format + string is based on Python style formating, each argument substitution marked by braces, {}. Each + specification has three parts, a @a name, a @a specifier, and an @a extention. These are + separated by colons. The name should be either omitted or a number, the index of the argument to + use. If omitted the place in the format string is used as the argument index. E.g. "{} {} {}", + "{} {1} {}", and "{0} {1} {2}" are equivalent. Using an explicit index does not reset the + position of subsequent substiations, therefore "{} {0} {}" is equivalent to "{0} {0} {2}". + + @note This must be declared here, but the implementation is in @c bwf_base.h + */ + template BufferWriter &print(const TextView &fmt, Rest &&... rest); + + /** Print overload to take arguments as a tuple instead of explicitly. + This is useful for forwarding variable arguments from other functions / methods. + */ + template BufferWriter &printv(const TextView &fmt, const std::tuple &args); + + /// Print using a preparsed @a fmt. + template BufferWriter &print(const bwf::Format &fmt, Args &&... args); + template BufferWriter &printv(const bwf::Format &fmt, const std::tuple &args); + + /** Print the arguments on to the buffer. + * + * This is the base implementation, all of the other variants are wrappers for this. + * + * @tparam F Format processor - returns chunks of the format. + * @tparam Args Arguments for the format. + * @param names Name set for specifier names. + */ + template + BufferWriter &print_nv(bwf::BoundNames const &names, F &&f, std::tuple const &args); + /// Convenience for no format argument style invocation. + template BufferWriter &print_nv(const bwf::BoundNames &names, F &&f); + + /// Output the buffer contents to the @a stream. + /// @return The destination stream. + virtual std::ostream &operator>>(std::ostream &stream) const = 0; +}; + +/** A @c BufferWrite concrete subclass to write to a fixed size buffer. + */ +class FixedBufferWriter : public BufferWriter +{ + using super_type = BufferWriter; + using self_type = FixedBufferWriter; + +public: + /** Construct a buffer writer on a fixed @a buffer of size @a capacity. + + If writing goes past the end of the buffer, the excess is dropped. + + @note If you create a instance of this class with capacity == 0 (and a nullptr buffer), you + can use it to measure the number of characters a series of writes would result it (from the + extent() value) without actually writing. + */ + FixedBufferWriter(char *buffer, size_t capacity); + + /** Construct from span + * + */ + FixedBufferWriter(const MemSpan &span); + + /** Construct empty buffer. + * This is useful for doing sizing before allocating a buffer. + */ + FixedBufferWriter(std::nullptr_t); + + FixedBufferWriter(const FixedBufferWriter &) = delete; + + FixedBufferWriter &operator=(const FixedBufferWriter &) = delete; + + /// Move constructor. + FixedBufferWriter(FixedBufferWriter &&) = default; + + /// Move assignment. + FixedBufferWriter &operator=(FixedBufferWriter &&) = default; + + FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast(span.size())) {} + + /// Write a single character @a c to the buffer. + FixedBufferWriter &write(char c) override; + + /// Write @a data to the buffer, up to @a length bytes. + FixedBufferWriter &write(const void *data, size_t length) override; + + // Bring in non-overridden methods. + using super_type::write; + + /// Return the output buffer. + const char *data() const override; + + /// Return whether there has been an error. + bool error() const override; + + /// Get the start of the unused output buffer. + char *aux_data() override; + + /// Get the span of the unused output buffer + MemSpan aux_span(); + + /// Get the total capacity of the output buffer. + size_t capacity() const override; + + /// Get the total output sent to the writer. + size_t extent() const override; + + /// Advance the used part of the output buffer. + self_type &commit(size_t n) override; + + /// Drop @a n characters from the end of the buffer. + /// The extent is reduced but the data is not overwritten and can be recovered with + /// @c fill. + self_type &discard(size_t n) override; + + /// Reduce the capacity by @a n. + self_type &restrict(size_t n) override; + + /// Extend the capacity by @a n. + self_type &restore(size_t n) override; + + /// Copy data in the buffer. + FixedBufferWriter ©(size_t dst, size_t src, size_t n) override; + + /// Clear the buffer, reset to empty (no data). + /// This is a convenience for reusing a buffer. For instance + /// @code + /// bw.reset().print("....."); // clear old data and print new data. + /// @endcode + /// This is equivalent to @c reduce(0) but clearer for that case. + self_type &clear(); + + /// Provide a string_view of all successfully written characters. + std::string_view view() const; + + /// Provide a @c string_view of all successfully written characters as a user conversion. + operator std::string_view() const; + + /// Output the buffer contents to the @a stream. + std::ostream &operator>>(std::ostream &stream) const override; + + // Overrides for co-variance + template self_type &print(TextView fmt, Rest &&... rest); + + template self_type &printv(TextView fmt, std::tuple const &args); + + template self_type &print(bwf::Format const &fmt, Args &&... args); + + template self_type &printv(bwf::Format const &fmt, std::tuple const &args); + +protected: + char *const _buf; ///< Output buffer. + size_t _capacity; ///< Size of output buffer. + size_t _attempted = 0; ///< Number of characters written, including those discarded due error condition. + size_t _restriction = 0; ///< Restricted capacity. +}; + +/** A buffer writer that writes to an array of char (of fixed size N) that is internal to the writer instance. + + It's called 'local' because instances are typically declared as stack-allocated, local function + variables. +*/ +template class LocalBufferWriter : public FixedBufferWriter +{ + using self_type = LocalBufferWriter; + using super_type = FixedBufferWriter; + +public: + /// Construct an empty writer. + LocalBufferWriter(); + LocalBufferWriter(const LocalBufferWriter &that) = delete; + LocalBufferWriter &operator=(const LocalBufferWriter &that) = delete; + +protected: + char _arr[N]; ///< output buffer. +}; + +// --------------- Implementation -------------------- + +inline BufferWriter::~BufferWriter() {} + +inline BufferWriter & +BufferWriter::write(const void *data, size_t length) +{ + const char *d = static_cast(data); + + while (length--) { + this->write(*(d++)); + } + return *this; +} + +inline BufferWriter & +BufferWriter::write(const std::string_view &sv) +{ + return this->write(sv.data(), sv.size()); +} + +inline char * +BufferWriter::aux_data() +{ + return nullptr; +} + +inline size_t +BufferWriter::size() const +{ + return std::min(this->extent(), this->capacity()); +} + +inline size_t +BufferWriter::remaining() const +{ + return this->capacity() - this->size(); +} + +// --- FixedBufferWriter --- +inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buf(buffer), _capacity(capacity) +{ + if (_capacity != 0 && buffer == nullptr) { + throw(std::invalid_argument{"FixedBufferWriter created with null buffer and non-zero size."}); + }; +} + +inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {} + +inline FixedBufferWriter & +FixedBufferWriter::write(char c) +{ + if (_attempted < _capacity) { + _buf[_attempted] = c; + } + ++_attempted; + + return *this; +} + +inline FixedBufferWriter & +FixedBufferWriter::write(const void *data, size_t length) +{ + const size_t newSize = _attempted + length; + + if (_buf) { + if (newSize <= _capacity) { + std::memcpy(_buf + _attempted, data, length); + } else if (_attempted < _capacity) { + std::memcpy(_buf + _attempted, data, _capacity - _attempted); + } + } + _attempted = newSize; + + return *this; +} + +/// Return the output buffer. +inline const char * +FixedBufferWriter::data() const +{ + return _buf; +} + +inline bool +FixedBufferWriter::error() const +{ + return _attempted > _capacity; +} + +inline char * +FixedBufferWriter::aux_data() +{ + return error() ? nullptr : _buf + _attempted; +} + +inline MemSpan +FixedBufferWriter::aux_span() +{ + return error() ? MemSpan{} : MemSpan{_buf + _attempted, static_cast(this->remaining())}; +} + +inline auto +FixedBufferWriter::commit(size_t n) -> self_type & +{ + _attempted += n; + + return *this; +} + +inline size_t +FixedBufferWriter::capacity() const +{ + return _capacity; +} + +inline size_t +FixedBufferWriter::extent() const +{ + return _attempted; +} + +inline auto +FixedBufferWriter::restrict(size_t n) -> self_type & +{ + if (n > _capacity) { + throw(std::invalid_argument{"FixedBufferWriter restrict value more than capacity"}); + } + + _capacity -= n; + _restriction += n; + + return *this; +} + +inline auto +FixedBufferWriter::restore(size_t n) -> self_type & +{ + if (error()) { + _attempted = _capacity; + } + n = std::min(n, _restriction); + + _capacity += n; + _restriction -= n; + + return *this; +} + +inline auto +FixedBufferWriter::discard(size_t n) -> self_type & +{ + _attempted -= std::min(_attempted, n); + return *this; +} + +inline auto +FixedBufferWriter::clear() -> self_type & +{ + _attempted = 0; + return *this; +} + +inline auto +FixedBufferWriter::copy(size_t dst, size_t src, size_t n) -> self_type & +{ + auto limit = std::min(_capacity, _attempted); // max offset of region possible. + MemSpan src_span{_buf + src, std::min(limit, src + n)}; + MemSpan dst_span{_buf + dst, std::min(limit, dst + n)}; + std::memmove(dst_span.data(), src_span.data(), std::min(dst_span.size(), src_span.size())); + return *this; +} + +inline std::string_view +FixedBufferWriter::view() const +{ + return std::string_view(_buf, size()); +} + +/// Provide a @c string_view of all successfully written characters as a user conversion. +inline FixedBufferWriter::operator std::string_view() const +{ + return this->view(); +} + +// --- LocalBufferWriter --- +template LocalBufferWriter::LocalBufferWriter() : super_type(_arr, N) {} + +} // namespace ts + +namespace std +{ +inline ostream & +operator<<(ostream &s, ts::BufferWriter const &w) +{ + return w >> s; +} +} // end namespace std diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index 0c13e17fd64..51fd7876f9f 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -19,6 +19,11 @@ library_includedir=$(includedir)/tscpp/util library_include_HEADERS = \ + BufferWriter.h \ + bwf_printf.h \ + bwf_ex.h \ + bwf_base.h \ + bwf_std.h \ IntrusiveDList.h \ IntrusiveHashMap.h \ MemArena.h \ diff --git a/include/tscpp/util/MemArena.h b/include/tscpp/util/MemArena.h index 4e24cf26f7a..bbfb625ecd3 100644 --- a/include/tscpp/util/MemArena.h +++ b/include/tscpp/util/MemArena.h @@ -26,7 +26,7 @@ #include #include "tscpp/util/MemSpan.h" -#include "tscore/Scalar.h" +#include "tscpp/util/Scalar.h" #include "tscpp/util/IntrusiveDList.h" namespace ts diff --git a/include/tscore/Scalar.h b/include/tscpp/util/Scalar.h similarity index 92% rename from include/tscore/Scalar.h rename to include/tscpp/util/Scalar.h index e71a0824da0..62755da83e7 100644 --- a/include/tscore/Scalar.h +++ b/include/tscpp/util/Scalar.h @@ -8,21 +8,18 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. + See the NOTICE file distributed with this work for additional information regarding copyright + ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance with the License. You may obtain a + copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Unless required by applicable law or agreed to in writing, software distributed under the License + is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing permissions and limitations under + the License. */ #pragma once @@ -31,7 +28,8 @@ #include #include #include -#include "tscore/BufferWriter.h" + +#include "tscpp/util/ts_meta.h" namespace tag { @@ -895,70 +893,45 @@ Scalar::minus(Counter n) const -> self namespace detail { - // These classes exist only to create distinguishable overloads. - struct tag_label_A { - }; - struct tag_label_B : public tag_label_A { - }; - // The purpose is to print a label for a tagged type only if the tag class defines a member that - // is the label. This creates a base function that always works and does nothing. The second - // function creates an overload if the tag class has a member named 'label' that has an stream IO - // output operator. When invoked with a second argument of B then the second overload exists and - // is used, otherwise only the first exists and that is used. The critical technology is the use - // of 'auto' and 'decltype' which effectively checks if the code inside 'decltype' compiles. - template - inline std::ostream & - tag_label(std::ostream &s, tag_label_A const &) - { - return s; - } template - inline BufferWriter & - tag_label(BufferWriter &w, BWFSpec const &, tag_label_A const &) + auto + tag_label(std::ostream &, const meta::CaseTag<0> &) -> void { - return w; } + template - inline auto - tag_label(std::ostream &s, tag_label_B const &) -> decltype(s << T::label, s) + auto + tag_label(std::ostream &w, const meta::CaseTag<1> &) -> decltype(T::label, meta::CaseVoidFunc()) { - return s << T::label; + w << T::label; } + template - inline auto - tag_label(BufferWriter &w, BWFSpec const &spec, tag_label_B const &) -> decltype(bwformat(w, spec, T::label), w) + inline std::ostream & + tag_label(std::ostream &w) { - return bwformat(w, spec, T::label); + tag_label(w, meta::CaseArg); + return w; } } // namespace detail - -template -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, Scalar const &x) -{ - static constexpr ts::detail::tag_label_B b{}; - bwformat(w, spec, x.value()); - return ts::detail::tag_label(w, spec, b); -} - } // namespace ts namespace std { +/// Compute common type of two scalars. +/// In `std` to overload the base definition. This yields a type that has the common type of the +/// counter type and a scale that is the GCF of the input scales. +template struct common_type, ts::Scalar> { + using R = std::ratio; + using type = ts::Scalar::type, T>; +}; + template ostream & operator<<(ostream &s, ts::Scalar const &x) { - static ts::detail::tag_label_B b; // Can't be const or the compiler gets upset. s << x.value(); - return ts::detail::tag_label(s, b); + return ts::detail::tag_label(s); } -/// Compute common type of two scalars. -/// In `std` to overload the base definition. This yields a type that has the common type of the -/// counter type and a scale that is the GCF of the input scales. -template struct common_type, ts::Scalar> { - typedef std::ratio R; - typedef ts::Scalar::type, T> type; -}; } // namespace std diff --git a/include/tscpp/util/bwf_base.h b/include/tscpp/util/bwf_base.h new file mode 100644 index 00000000000..9f0b23c70de --- /dev/null +++ b/include/tscpp/util/bwf_base.h @@ -0,0 +1,963 @@ +/** @file + + Basic formatting support for @c BufferWriter. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/TextView.h" +#include "tscpp/util/MemSpan.h" +#include "tscpp/util/MemArena.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/ts_meta.h" + +namespace ts +{ +namespace bwf +{ + /** Parsed version of a format specifier. + * + * Literals are represented as an instance of this class, with the type set to + * @c LITERAL_TYPE and the literal text in the @a _ext field. + */ + struct Spec { + using self_type = Spec; ///< Self reference type. + + static constexpr char DEFAULT_TYPE = 'g'; ///< Default format type. + static constexpr char INVALID_TYPE = 0; ///< Type for missing or invalid specifier. + static constexpr char LITERAL_TYPE = '"'; ///< Internal type to mark a literal. + static constexpr char CAPTURE_TYPE = 1; ///< Internal type to mark a capture. + + static constexpr char SIGN_ALWAYS = '+'; ///< Always print a sign character. + static constexpr char SIGN_NEVER = ' '; ///< Never print a sign character. + static constexpr char SIGN_NEG = '-'; ///< Print a sign character only for negative values (default). + + /// Constructor a default instance. + constexpr Spec() {} + + /// Construct by parsing @a fmt. + Spec(const TextView &fmt); + /// Parse a specifier + bool parse(TextView fmt); + + char _fill = ' '; ///< Fill character. + char _sign = SIGN_NEG; ///< Numeric sign style. + enum class Align : char { + NONE, ///< No alignment. + LEFT, ///< Left alignment '<'. + RIGHT, ///< Right alignment '>'. + CENTER, ///< Center alignment '^'. + SIGN ///< Align plus/minus sign before numeric fill. '=' + } _align = Align::NONE; ///< Output field alignment. + char _type = DEFAULT_TYPE; ///< Type / radix indicator. + bool _radix_lead_p = false; ///< Print leading radix indication. + // @a _min is unsigned because there's no point in an invalid default, 0 works fine. + unsigned int _min = 0; ///< Minimum width. + int _prec = -1; ///< Precision + unsigned int _max = std::numeric_limits::max(); ///< Maximum width + int _idx = -1; ///< Positional "name" of the specification. + std::string_view _name; ///< Name of the specification. + std::string_view _ext; ///< Extension if provided. + + /// Global default instance for use in situations where a format specifier isn't available. + static const self_type DEFAULT; + + /// Validate @a c is a specifier type indicator. + static bool is_type(char c); + + /// Check if the type flag is numeric. + static bool is_numeric_type(char c); + + /// Check if the type is an upper case variant. + static bool is_upper_case_type(char c); + + /// Check if the type @a in @a this is numeric. + bool has_numeric_type() const; + + /// Check if the type in @a this is an upper case variant. + bool has_upper_case_type() const; + + /// Check if the type is a raw pointer. + bool has_pointer_type() const; + + /// Check if the type is valid. + bool has_valid_type() const; + + protected: + /// Validate character is alignment character and return the appropriate enum value. + Align align_of(char c); + + /// Validate is sign indicator. + bool is_sign(char c); + + /// Handrolled initialization the character syntactic property data. + static const struct Property { + Property(); ///< Default constructor, creates initialized flag set. + /// Flag storage, indexed by character value. + uint8_t _data[0x100]; + /// Flag mask values. + static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type. + static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character. + static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag. + static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output. + static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character. + } _prop; + }; + + /** Format string support. + * + * This contains the parsing logic for format strings and also serves as the type for pre-compiled + * format string. + * + * When used by the print formatting logic, there is an abstraction layer, "extraction", which + * performs the equivalent of the @c parse method. This allows the formatting to treat pre-compiled + * or immediately parsed format strings the same. It also enables providing any parser that can + * deliver literals and @c Spec instances. + */ + class Format + { + public: + /// Construct from a format string @a fmt. + Format(TextView fmt); + + /// Extraction support for TextView. + struct TextViewExtractor { + TextView _fmt; + explicit operator bool() const; + bool operator()(std::string_view &literal_v, Spec &spec); + + /** Parse elements of a format string. + + @param fmt The format string [in|out] + @param literal A literal if found + @param spec A specifier if found (less enclosing braces) + @return @c true if a specifier was found, @c false if not. + + Pull off the next literal and/or specifier from @a fmt. The return value distinguishes + the case of no specifier found (@c false) or an empty specifier (@c true). + + */ + static bool parse(TextView &fmt, std::string_view &literal, std::string_view &spec); + }; + /// Wrap the format string in an extractor. + static TextViewExtractor bind(TextView fmt); + + /// Extraction support for pre-parsed format strings. + struct FormatExtractor { + const std::vector &_fmt; ///< Parsed format string. + int _idx = 0; ///< Element index. + explicit operator bool() const; + bool operator()(std::string_view &literal_v, Spec &spec); + }; + /// Wrap the format instance in an extractor. + FormatExtractor bind() const; + + protected: + /// Default constructor for use by subclasses with alternate formatting. + Format() = default; + + std::vector _items; ///< Items from format string. + }; + + // Name binding - support for having format specifier names. + + /// Generic name generator signature. + using BoundNameSignature = BufferWriter &(BufferWriter &, Spec const &); + + /** Protocol class for handling bound names. + * + * This is an API facade for names that are fully bound and do not need any data / context + * beyond that of the @c BufferWriter. It is expected other name collections will subclass + * this to pass to the formatting logic. + */ + class BoundNames + { + public: + virtual ~BoundNames(); + /** Generate output text for @a name on the output @a w using the format specifier @a spec. + * This must match the @c BoundNameSignature type. + * + * @param w Output stream. + * @param spec Parsed format specifier. + * + * @note The tag name can be found in @c spec._name. + * + * @return + */ + virtual BufferWriter &operator()(BufferWriter &w, Spec const &spec) const = 0; + + /** Capture an argument. + * + * @param w Output. + * @param spec Capturing specifier. + * @param arg The captured argument. + * + * @note This is really for C / printf support where some specifiers are dependent on values + * passed in other arguments. + */ + virtual void capture(BufferWriter &w, Spec const &spec, std::any const &arg) const; + + protected: + /// Write missing name output. + BufferWriter &err_invalid_name(BufferWriter &w, Spec const &) const; + }; + + /// Empty bound names - used for where no name binding is available or desired. + /// Throws if any name is used. + class NilBoundNames : public BoundNames + { + public: + BufferWriter &operator()(BufferWriter &, Spec const &) const override; + }; + + /** Binding names to generators. + * @tparam F The function signature for generators in this container. + * + * This is a base class used by different types of name containers. It is not expected to be used + * directly. + */ + template class NameBinding + { + private: + using self_type = NameBinding; ///< self reference type. + public: + using Generator = std::function; + + /// Construct an empty name set. + NameBinding(); + /// Construct and assign the names and generators in @a list + NameBinding(std::initializer_list> list); + + /** Assign the @a generator to the @a name. + * + * @param name Name associated with the @a generator. + * @param generator The generator function. + */ + self_type &assign(std::string_view name, const Generator &generator); + + protected: + /// Copy @a name in to local storage and return a view of it. + std::string_view localize(std::string_view name); + + using Map = std::unordered_map; + Map _map; ///< Mapping of name -> generator + MemArena _arena{1024}; ///< Local name storage. + }; + + /** A class to hold global name bindings. + * + * These names access global data and therefore have no context. An instance of this is used + * as the default if no explicit name set is provided. + */ + class GlobalNames : public NameBinding + { + using self_type = GlobalNames; + using super_type = NameBinding; + using Map = super_type::Map; + + public: + using super_type::super_type; + /// Provide an accessor for formatting. + BoundNames &bind(); + + protected: + class Binding : public BoundNames + { + public: + Binding(const super_type::Map &map); + BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override; + + protected: + const Map &_map; + } _binding{super_type::_map}; + }; + + /** Binding for context based names. + * + * @tparam T The context type. This is used directly. If the context needs to be @c const + * then this parameter should make that explicit, e.g. @c Names. This + * paramater is accessible via the @c context_type alias. + * + * This enables named format specifications, such as "{tag}", generated from a context of type @a + * T. Each supported tag requires a @a Generator which is a functor of type + * + * @code + * BufferWriter & generator(BufferWriter & w, const Spec & spec, T & context); + * @endcode + */ + template class ContextNames : public NameBinding + { + private: + using self_type = ContextNames; ///< self reference type. + using super_type = NameBinding; + + public: + using context_type = T; ///< Export for external convenience. + /// Functional type for a generator. + using Generator = typename super_type::Generator; + using BoundGenerator = std::function; + + using super_type::super_type; // inherit @c super_type constructors. + + /** Assign the bound generator @a bg to @a name. + * + * This is used for generators in the namespace that do not require the context. + * + * @param name Name associated with the generator. + * @param bg A bound generator that requires no context. + * @return @c *this + */ + self_type &assign(std::string_view name, const BoundGenerator &bg); + + /// Inherit unbound generator assignment from the @c super_type. + using super_type::assign; + + /** Bind the names to a specific @a context. + * + * @param context The instance of @a T to use in the generators. + * @return A reference to an internal instance of a subclass of the protocol class @c BoundNames. + */ + const BoundNames &bind(context_type &context); + + protected: + using Map = typename super_type::Map; + /// Subclass of @a BoundNames used to bind this set of names to a context. + class Binding : public BoundNames + { + using self_type = Binding; + using super_type = BoundNames; + + public: + /// Invoke the generator for @a name. + BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override; + + protected: + Binding(Map const &map); ///< Must have a map reference. + self_type &assign(context_type *); + + Map const &_map; ///< The mapping for name look ups. + context_type *_ctx = nullptr; ///< Context for generators. + + friend ContextNames; + } _binding{super_type::_map}; + }; + + /** Default global names. + * This nameset is used if no other is provided. Therefore bindings added to this nameset will be + * available in the default formatting use. + */ + extern GlobalNames Global_Names; + + // --------------- Implementation -------------------- + /// --- Spec --- + + inline Spec::Align + Spec::align_of(char c) + { + return static_cast(_prop._data[static_cast(c)] & Property::ALIGN_MASK); + } + + inline bool + Spec::is_sign(char c) + { + return _prop._data[static_cast(c)] & Property::SIGN_CHAR; + } + + inline bool + Spec::is_type(char c) + { + return _prop._data[static_cast(c)] & Property::TYPE_CHAR; + } + + inline bool + Spec::is_upper_case_type(char c) + { + return _prop._data[static_cast(c)] & Property::UPPER_TYPE_CHAR; + } + + inline bool + Spec::is_numeric_type(char c) + { + return _prop._data[static_cast(c)] & Property::NUMERIC_TYPE_CHAR; + } + + inline bool + Spec::has_numeric_type() const + { + return _prop._data[static_cast(_type)] & Property::NUMERIC_TYPE_CHAR; + } + + inline bool + Spec::has_upper_case_type() const + { + return _prop._data[static_cast(_type)] & Property::UPPER_TYPE_CHAR; + } + + inline bool + Spec::has_pointer_type() const + { + return _type == 'p' || _type == 'P'; + } + + inline bool + Spec::has_valid_type() const + { + return _type != INVALID_TYPE; + } + + inline auto + Format::bind(ts::TextView fmt) -> TextViewExtractor + { + return {fmt}; + } + + inline auto + Format::bind() const -> FormatExtractor + { + return {_items}; + } + + inline Format::TextViewExtractor::operator bool() const { return !_fmt.empty(); } + inline Format::FormatExtractor::operator bool() const { return _idx < static_cast(_fmt.size()); } + + /// --- Names / Generators --- + + // Base implementation does nothing as this is rarely used. + inline void + BoundNames::capture(BufferWriter &, ts::bwf::Spec const &, std::any const &) const + { + } + + inline BufferWriter & + BoundNames::err_invalid_name(BufferWriter &w, const Spec &spec) const + { + return w.print("{{~{}~}}", spec._name); + } + + inline BufferWriter & + NilBoundNames::operator()(BufferWriter &, bwf::Spec const &) const + { + throw std::runtime_error("Use of nil bound names in BW formating"); + } + + template inline ContextNames::Binding::Binding(Map const &map) : _map(map) {} + + template + inline const BoundNames & + ContextNames::bind(context_type &ctx) + { + return _binding.assign(&ctx); + } + + template + inline auto + ContextNames::Binding::assign(context_type *ctx) -> self_type & + { + _ctx = ctx; + return *this; + } + + template + BufferWriter & + ContextNames::Binding::operator()(BufferWriter &w, const Spec &spec) const + { + if (!spec._name.empty()) { + if (auto spot = _map.find(spec._name); spot != _map.end()) { + spot->second(w, spec, *_ctx); + } else { + this->err_invalid_name(w, spec); + } + } + return w; + } + + template NameBinding::NameBinding() {} + + template NameBinding::NameBinding(std::initializer_list> list) + { + for (auto &&[name, generator] : list) { + this->assign(name, generator); + } + } + + template + std::string_view + NameBinding::localize(std::string_view name) + { + auto span = _arena.alloc(name.size()); + memcpy(span.data(), name.data(), name.size()); + return span.view(); + } + + template + auto + NameBinding::assign(std::string_view name, const Generator &generator) -> self_type & + { + name = this->localize(name); + _map[name] = generator; + return *this; + } + + inline GlobalNames::Binding::Binding(const super_type::Map &map) : _map(map) {} + + inline BufferWriter & + GlobalNames::Binding::operator()(BufferWriter &w, const Spec &spec) const + { + if (!spec._name.empty()) { + if (auto spot = _map.find(spec._name); spot != _map.end()) { + spot->second(w, spec); + } else { + this->err_invalid_name(w, spec); + } + } + return w; + } + + inline BoundNames & + GlobalNames::bind() + { + return _binding; + } + + /// --- Formatting --- + + /// Internal signature for template generated formatting. + /// @a args is a forwarded tuple of arguments to be processed. + template using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, Spec const &, TUPLE const &args); + + /// Internal error / reporting message generators + void Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n); + + // MSVC will expand the parameter pack inside a lambda but not gcc, so this indirection is required. + + /// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This + /// (or the equivalent lambda) is needed because the array of formatters must have a homogenous + /// signature, not vary per argument. Effectively this indirection erases the type of the specific + /// argument being formatted. Instances of this have the signature @c ArgFormatterSignature. + template + BufferWriter & + Arg_Formatter(BufferWriter &w, Spec const &spec, TUPLE const &args) + { + return bwformat(w, spec, std::get(args)); + } + + /// This exists only to expand the index sequence into an array of formatters for the tuple type + /// @a TUPLE. Due to langauge limitations it cannot be done directly. The formatters can be + /// accessed via standard array access in contrast to templated tuple access. The actual array is + /// static and therefore at run time the only operation is loading the address of the array. + template + ArgFormatterSignature * + Get_Arg_Formatter_Array(std::index_sequence) + { + static ArgFormatterSignature fa[sizeof...(N)] = {&bwf::Arg_Formatter...}; + return fa; + } + + /// Perform alignment adjustments / fill on @a w of the content in @a lw. + /// This is the normal mechanism, in cases where the length can be known or limited before + /// conversion, it can be more efficient to work in a temporary local buffer and copy out + /// as neeed without moving data in the output buffer. + void Adjust_Alignment(BufferWriter &aux, Spec const &spec); + + /// Generic integral conversion. + BufferWriter &Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t n, bool negative_p); + + /// Generic floating point conversion. + BufferWriter &Format_Float(BufferWriter &w, Spec const &spec, double n, bool negative_p); + + /* Capture support, which allows format extractors to capture arguments and consume them. + * This was built in order to support C style formatting, which needs to capture arguments + * to set the minimum width and/or the precision of other arguments. + * + * The key component is the ability to dynamically access an element of a tuple using + * @c std::any. + * + * Note: Much of this was originally in the meta support but it caused problems in use if + * the tuple header wasn't also included. I was unable to determine why, as this code doesn't + * depend on tuple explicitly. + */ + /// The signature for accessing an element of a tuple. + template using TupleAccessorSignature = std::any (*)(T const &t); + /// Template access method. + template + std::any + TupleAccessor(T const &t) + { + return std::any(&std::get(t)); + } + /// Create and return an array of specialized accessors, indexed by tuple index. + template + std::array, sizeof...(N)> & + Tuple_Accessor_Array(std::index_sequence) + { + static std::array, sizeof...(N)> accessors = {&TupleAccessor...}; + return accessors; + } + /// Get the Nth element of the tuple as @c std::any. + template + std::any + Tuple_Nth(T const &t, size_t idx) + { + return Tuple_Accessor_Array(std::make_index_sequence::value>())[idx](t); + } + /// If capture is used, the format extractor must provide a @c capture method. This isn't required + /// so make it compile time optional, but throw if the extractor sets up for capture and didn't + /// provide one. + template + auto + arg_capture(F &&f, BufferWriter &, Spec const &, std::any &&, ts::meta::CaseTag<0>) -> void + { + throw std::runtime_error("Capture specification used in format extractor that does not support capture"); + } + template + auto + arg_capture(F &&f, BufferWriter &w, Spec const &spec, std::any &&value, ts::meta::CaseTag<1>) + -> decltype(f.capture(w, spec, value)) + { + return f.capture(w, spec, value); + } + +} // namespace bwf + +/* [Need to clip this out and put it in Sphinx + * + * The format parser :arg:`F` performs parsing of the format specifier, which is presumed to be + * bound to this instance of :arg:`F`. The parser is called as a functor and must have a function + * method with the signature + * + * bool (string_view & literal_v, bwf::Spec & spec) + * + * The parser must parse out to the next specifier in the format, or the end of the format if there + * are no more specifiers. If the format is exhausted this should return @c false. A return of @c true + * indicates there is either a literal, a specifier, or both. + * + * When a literal is found, it should be returned in :arg:`literal_v`. If a specifier is found and + * parsed, it should be put in :arg:`spec`. Both of these *must* be cleared if the corresponding + * data is not found in the incremental parse of the format. + */ + +// This is the real printing logic, all other variants pack up their arguments and send them here. +template +BufferWriter & +BufferWriter::print_nv(bwf::BoundNames const &names, F &&f, std::tuple const &args) +{ + using namespace std::literals; + static constexpr int N = sizeof...(Args); // used as loop limit + static const auto fa = bwf::Get_Arg_Formatter_Array(std::index_sequence_for{}); + int arg_idx = 0; // the next argument index to be processed. + + // Parser is required to return @c false if there's no more data, @c true if something was parsed. + while (f) { + std::string_view lit_v; + bwf::Spec spec; + bool spec_p = f(lit_v, spec); + if (lit_v.size()) { + this->write(lit_v); + } + + if (spec_p) { + size_t width = this->remaining(); + if (spec._max < width) { + width = spec._max; + } + FixedBufferWriter lw{this->aux_data(), width}; + + if (spec._name.size() == 0) { + spec._idx = arg_idx++; + } + if (0 <= spec._idx) { + if (spec._idx < N) { + if (spec._type == bwf::Spec::CAPTURE_TYPE) { + bwf::arg_capture(f, lw, spec, bwf::Tuple_Nth(args, static_cast(spec._idx)), ts::meta::CaseArg); + } else { + fa[spec._idx](lw, spec, args); + } + } else { + bwf::Err_Bad_Arg_Index(lw, spec._idx, N); + } + } else if (spec._name.size()) { + names(lw, spec); + } + if (lw.extent()) { + bwf::Adjust_Alignment(lw, spec); + this->commit(lw.extent()); + } + } + } + return *this; +} + +template +BufferWriter & +BufferWriter::print(const TextView &fmt, Args &&... args) +{ + return this->print_nv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), std::forward_as_tuple(args...)); +} + +template +BufferWriter & +BufferWriter::print(bwf::Format const &fmt, Args &&... args) +{ + return this->print_nv(bwf::Global_Names.bind(), fmt.bind(), std::forward_as_tuple(args...)); +} + +template +BufferWriter & +BufferWriter::printv(TextView const &fmt, std::tuple const &args) +{ + return this->print_nv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), args); +} + +template +BufferWriter & +BufferWriter::printv(const bwf::Format &fmt, const std::tuple &args) +{ + return this->print_nv(bwf::Global_Names.bind(), fmt.bind(), args); +} + +template +BufferWriter & +BufferWriter::print_nv(const bwf::BoundNames &names, F &&f) +{ + return print_nv(names, f, std::make_tuple()); +} + +// ---- Formatting for specific types. + +// Pointers that are not specialized. +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) +{ + bwf::Spec ptr_spec{spec}; + ptr_spec._radix_lead_p = true; + if (ptr_spec._type == bwf::Spec::DEFAULT_TYPE || ptr_spec._type == 'p') { + ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex. + } else if (ptr_spec._type == 'P') { + ptr_spec._type = 'X'; // P means upper hex, overriding other specializations. + } + return bwf::Format_Integer(w, ptr_spec, reinterpret_cast(ptr), false); +} + +// MemSpan +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan const &span); + +// -- Common formatters -- + +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv); + +template +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const char (&a)[N]) +{ + return bwformat(w, spec, std::string_view(a, N - 1)); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const char *v) +{ + if (spec._type == 'x' || spec._type == 'X') { + bwformat(w, spec, static_cast(v)); + } else { + bwformat(w, spec, std::string_view(v)); + } + return w; +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, TextView tv) +{ + return bwformat(w, spec, static_cast(tv)); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, std::string const &s) +{ + return bwformat(w, spec, std::string_view{s}); +} + +template +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, F &&f) -> + typename std::enable_if::type>::value, BufferWriter &>::type +{ + return f < 0 ? bwf::Format_Float(w, spec, -f, true) : bwf::Format_Float(w, spec, f, false); +} + +/* Integer types. + + Due to some oddities for MacOS building, need a bit more template magic here. The underlying + integer rendering is in @c Format_Integer which takes @c intmax_t or @c uintmax_t. For @c + bwformat templates are defined, one for signed and one for unsigned. These forward their argument + to the internal renderer. To avoid additional ambiguity the template argument is checked with @c + std::enable_if to invalidate the overload if the argument type isn't a signed / unsigned + integer. One exception to this is @c char which is handled by a previous overload in order to + treat the value as a character and not an integer. The overall benefit is this works for any set + of integer types, rather tuning and hoping to get just the right set of overloads. + */ + +template +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) -> + typename std::enable_if::type>::value && + std::is_integral::type>::value, + BufferWriter &>::type +{ + return bwf::Format_Integer(w, spec, i, false); +} + +template +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) -> + typename std::enable_if::type>::value && + std::is_integral::type>::value, + BufferWriter &>::type +{ + bool neg_p = false; + uintmax_t n = static_cast(i); + if (i < 0) { + n = static_cast(-i); + neg_p = true; + } + return bwf::Format_Integer(w, spec, n, neg_p); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &, char c) +{ + return w.write(c); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bool f) +{ + using namespace std::literals; + if ('s' == spec._type) { + w.write(f ? "true"sv : "false"sv); + } else if ('S' == spec._type) { + w.write(f ? "TRUE"sv : "FALSE"sv); + } else { + bwf::Format_Integer(w, spec, static_cast(f), false); + } + return w; +} + +// Generically a stream operator is a formatter with the default specification. +template +BufferWriter & +operator<<(BufferWriter &w, V &&v) +{ + return bwformat(w, bwf::Spec::DEFAULT, std::forward(v)); +} + +// std::string support +/** Print to a @c std::string + + Print to the string @a s. If there is overflow then resize the string sufficiently to hold the output + and print again. The effect is the string is resized only as needed to hold the output. + */ +template +std::string & +bwprintv(std::string &s, TextView fmt, std::tuple const &args) +{ + auto len = s.size(); // remember initial size + size_t n = FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)).extent(); + s.resize(n); // always need to resize - if shorter, must clip pre-existing text. + if (n > len) { // dropped data, try again. + FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)); + } + return s; +} + +template +std::string & +bwprint(std::string &s, TextView fmt, Args &&... args) +{ + return bwprintv(s, fmt, std::forward_as_tuple(args...)); +} + +template +inline auto +FixedBufferWriter::print(TextView fmt, Args &&... args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); +} + +template +inline auto +FixedBufferWriter::printv(TextView fmt, std::tuple const &args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, args)); +} + +template +inline auto +FixedBufferWriter::print(bwf::Format const &fmt, Args &&... args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); +} + +template +inline auto +FixedBufferWriter::printv(bwf::Format const &fmt, std::tuple const &args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, args)); +} + +// Special case support for @c Scalar, because @c Scalar is a base utility for some other utilities +// there can be some unpleasant cirularities if @c Scalar includes BufferWriter formatting. If the +// support is here then it's fine because anything using BWF for @c Scalar must include this header. +template class Scalar; +namespace detail +{ + template + auto + tag_label(BufferWriter &w, const bwf::Spec &, meta::CaseTag<0>) -> void + { + } + + template + auto + tag_label(BufferWriter &w, const bwf::Spec &, meta::CaseTag<1>) -> decltype(T::label, meta::CaseVoidFunc()) + { + w.print("{}", T::label); + } +} // namespace detail + +template +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, Scalar const &x) +{ + bwformat(w, spec, x.value()); + if (!spec.has_numeric_type()) { + detail::tag_label(w, spec, meta::CaseArg); + } + return w; +} + +} // namespace ts diff --git a/include/tscore/bwf_std_format.h b/include/tscpp/util/bwf_ex.h similarity index 86% rename from include/tscore/bwf_std_format.h rename to include/tscpp/util/bwf_ex.h index ce7f4f7080a..b2685b797f4 100644 --- a/include/tscore/bwf_std_format.h +++ b/include/tscpp/util/bwf_ex.h @@ -23,21 +23,10 @@ #pragma once -#include #include #include -#include "tscpp/util/TextView.h" -#include "tscore/BufferWriterForward.h" -namespace std -{ -template -ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, atomic const &v) -{ - return ts::bwformat(w, spec, v.load()); -} -} // end namespace std +#include "tscpp/util/bwf_base.h" namespace ts { @@ -45,6 +34,13 @@ namespace bwf { using namespace std::literals; // enable ""sv + /** Output @a text @a n times. + * + */ + struct Pattern { + int _n; ///< # of instances of @a pattern. + std::string_view _text; ///< output text. + }; /** Format wrapper for @c errno. * This stores a copy of the argument or @c errno if an argument isn't provided. The output * is then formatted with the short, long, and numeric value of @c errno. If the format specifier @@ -123,8 +119,9 @@ namespace bwf }; } // namespace bwf -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::OptionalAffix const &opts); } // namespace ts diff --git a/include/tscpp/util/bwf_printf.h b/include/tscpp/util/bwf_printf.h new file mode 100644 index 00000000000..061a1790b75 --- /dev/null +++ b/include/tscpp/util/bwf_printf.h @@ -0,0 +1,79 @@ +/** @file + + BufferWriter formatting in snprintf style. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include + +#include "tscpp/util/TextView.h" +#include "tscpp/util/bwf_base.h" + +namespace ts +{ +namespace bwf +{ + /** C / printf style formatting for BufferWriter. + * + * This is a wrapper style class, it is not for use in a persistent context. The general use pattern + * will be to pass a temporary instance in to the @c BufferWriter formatting. E.g + * + * @code + * void bwprintf(BufferWriter& w, TextView fmt, arg1, arg2, arg3, ...) { + * w.print_v(C_Format(fmt), std::forward_as_tuple(args)); + * @endcode + */ + class C_Format + { + public: + /// Construct for @a fmt. + C_Format(TextView const &fmt); + + /// Check if there is any more format to process. + explicit operator bool() const; + + /// Get the next pieces of the format. + bool operator()(std::string_view &literal, Spec &spec); + + void capture(BufferWriter &w, Spec const &spec, std::any const &value); + + protected: + TextView _fmt; + Spec _saved; // spec for which the width and/or prec is needed. + bool _saved_p{false}; // flag for having a saved _spec. + bool _prec_p{false}; // need the precision captured? + }; + + // ---- Implementation ---- + inline C_Format::C_Format(TextView const &fmt) : _fmt(fmt) {} + + inline C_Format::operator bool() const { return _saved_p || !_fmt.empty(); } + +} // namespace bwf + +template +int +bwprintf(BufferWriter &w, TextView const &fmt, Args &&... args) +{ + size_t n = w.size(); + w.print_nv(bwf::NilBoundNames(), bwf::C_Format(fmt), std::forward_as_tuple(args...)); + return static_cast(w.size() - n); +} + +} // namespace ts diff --git a/include/tscpp/util/bwf_std.h b/include/tscpp/util/bwf_std.h new file mode 100644 index 00000000000..e032befe551 --- /dev/null +++ b/include/tscpp/util/bwf_std.h @@ -0,0 +1,37 @@ +/** @file + + BufferWriter formatters for types in the std namespace. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include "tscpp/util/bwf_base.h" + +namespace std +{ +template +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, atomic const &v) +{ + return ts::bwformat(w, spec, v.load()); +} +} // end namespace std diff --git a/iocore/eventsystem/IOBuffer.cc b/iocore/eventsystem/IOBuffer.cc index ff8658dc0a6..eb534d6b6d5 100644 --- a/iocore/eventsystem/IOBuffer.cc +++ b/iocore/eventsystem/IOBuffer.cc @@ -424,25 +424,3 @@ MIOBufferWriter::operator>>(std::ostream &stream) const } return stream; } - -ssize_t -MIOBufferWriter::operator>>(int fd) const -{ - ssize_t zret = 0; - IOBufferReader *reader = _miob->alloc_reader(); - if (reader) { - IOBufferBlock *b; - while (nullptr != (b = reader->get_current_block())) { - auto n = b->read_avail(); - auto r = ::write(fd, b->start(), n); - if (r <= 0) { - break; - } else { - reader->consume(r); - zret += r; - } - } - _miob->dealloc_reader(reader); - } - return zret; -} diff --git a/iocore/eventsystem/I_MIOBufferWriter.h b/iocore/eventsystem/I_MIOBufferWriter.h index 639f4615482..1a5a974931c 100644 --- a/iocore/eventsystem/I_MIOBufferWriter.h +++ b/iocore/eventsystem/I_MIOBufferWriter.h @@ -27,7 +27,7 @@ #include #include "tscore/ink_assert.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #if !defined(UNIT_TEST_BUFFER_WRITER) #include @@ -59,7 +59,7 @@ class MIOBufferWriter : public ts::BufferWriter } char * - auxBuffer() override + aux_data() override { IOBufferBlock *iobbPtr = _miob->first_write_block(); @@ -86,7 +86,7 @@ class MIOBufferWriter : public ts::BufferWriter // This function should not be called if no auxiliary buffer is available. // self_type & - fill(size_t n) override + commit(size_t n) override { if (n) { IOBufferBlock *iobbPtr = _miob->first_write_block(); @@ -117,11 +117,11 @@ class MIOBufferWriter : public ts::BufferWriter // Not useful in this derived class. // - self_type &clip(size_t) override { return *this; } + self_type &restrict(size_t) override { return *this; } // Not useful in this derived class. // - self_type &extend(size_t) override { return *this; } + self_type &restore(size_t) override { return *this; } // This must not be called for this derived class. // @@ -137,7 +137,6 @@ class MIOBufferWriter : public ts::BufferWriter std::ostream &operator>>(std::ostream &stream) const override; /// Output the buffer contents to the file for file descriptor @a fd. /// @return The number of bytes written. - ssize_t operator>>(int fd) const override; protected: MIOBuffer *_miob; diff --git a/iocore/eventsystem/Makefile.am b/iocore/eventsystem/Makefile.am index e1b83484485..cd884f9d350 100644 --- a/iocore/eventsystem/Makefile.am +++ b/iocore/eventsystem/Makefile.am @@ -70,8 +70,7 @@ libinkevent_a_SOURCES = \ UnixEvent.cc \ UnixEventProcessor.cc -check_PROGRAMS = test_Buffer test_Event \ - test_MIOBufferWriter +check_PROGRAMS = test_Buffer test_Event test_LD_FLAGS = \ @AM_LDFLAGS@ \ @@ -121,11 +120,7 @@ test_Buffer_LDADD = $(test_LD_ADD) test_Event_LDADD = $(test_LD_ADD) -test_MIOBufferWriter_SOURCES = unit_tests/test_MIOBufferWriter.cc -test_MIOBufferWriter_CPPFLAGS = $(test_CPP_FLAGS) -I$(abs_top_srcdir)/tests/include -test_MIOBufferWriter_LDFLAGS = $(test_LD_FLAGS) -test_MIOBufferWriter_LDADD = $(test_LD_ADD) include $(top_srcdir)/build/tidy.mk diff --git a/mgmt/LocalManager.cc b/mgmt/LocalManager.cc index 4b94d9cdc65..baa05480f59 100644 --- a/mgmt/LocalManager.cc +++ b/mgmt/LocalManager.cc @@ -36,8 +36,8 @@ #include #include #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" #if TS_USE_POSIX_CAP #include @@ -901,7 +901,7 @@ LocalManager::startProxy(const char *onetime_options) char options_buffer[OPTIONS_SIZE]; ts::FixedBufferWriter w{options_buffer, OPTIONS_SIZE}; - w.clip(1); + w.restrict(1); w.print("{}{}", ts::bwf::OptionalAffix(proxy_options), ts::bwf::OptionalAffix(onetime_options)); // Make sure we're starting the proxy in mgmt mode @@ -936,7 +936,7 @@ LocalManager::startProxy(const char *onetime_options) } } - w.extend(1); + w.restore(1); w.write('\0'); // null terminate. Debug("lm", "[LocalManager::startProxy] Launching %s '%s'", absolute_proxy_binary, w.data()); diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc index ed208d99b72..4e3d6243818 100644 --- a/proxy/IPAllow.cc +++ b/proxy/IPAllow.cc @@ -26,7 +26,7 @@ #include #include "IPAllow.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" extern char *readIntoBuffer(const char *file_path, const char *module_name, int *read_size_ptr); @@ -215,7 +215,7 @@ IpAllow::BuildTable() TextView src(file_buff, file_size); TextView line; auto err_prefix = [&]() -> ts::BufferWriter & { - return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file_path, line_num); + return bw_err.clear().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file_path, line_num); }; while (!(line = src.take_prefix_at('\n')).empty()) { @@ -280,7 +280,7 @@ IpAllow::BuildTable() if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) { nonstandard_methods.push_back(method_name); Debug("ip-allow", "%s", - bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data()); + bw_err.clear().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data()); } else { // valid method. acl_method_mask |= ACL::MethodIdxToMask(method_idx); } diff --git a/proxy/http/ForwardedConfig.cc b/proxy/http/ForwardedConfig.cc index 09caffc8320..feec2b97b91 100644 --- a/proxy/http/ForwardedConfig.cc +++ b/proxy/http/ForwardedConfig.cc @@ -45,14 +45,14 @@ class BadOptionsErrMsg add(ts::TextView badOpt) { if (_count == 0) { - _err << "\"Forwarded\" configuration: "; + _err.write("\"Forwarded\" configuration: "); _addQuoted(badOpt); _count = 1; } else if (_count == 1) { _saveLast = badOpt; _count = 2; } else { - _err << ", "; + _err.write(", "); _addQuoted(_saveLast); _saveLast = badOpt; ++_count; @@ -69,12 +69,12 @@ class BadOptionsErrMsg } if (_count == 1) { - _err << " is a bad option."; + _err.write(" is a bad option."); } else if (_count != 0) { - _err << " and "; + _err.write(" and "); _addQuoted(_saveLast); - _err << " are bad options."; + _err.write(" are bad options."); } return true; } @@ -83,7 +83,7 @@ class BadOptionsErrMsg void _addQuoted(ts::TextView sv) { - _err << '\"' << sv << '\"'; + _err.write('\"').write(sv).write('\"'); } ts::FixedBufferWriter &_err; diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index fd61d074243..cf3b5d0fe81 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -47,7 +47,7 @@ #include "tscore/IpMap.h" #include "tscore/Regex.h" #include "string_view" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "HttpProxyAPIEnums.h" #include "ProxyConfig.h" #include "records/P_RecProcess.h" diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc index 3b596354312..852952a2f86 100644 --- a/proxy/http/HttpConnectionCount.cc +++ b/proxy/http/HttpConnectionCount.cc @@ -24,8 +24,8 @@ #include #include #include "HttpConnectionCount.h" -#include "tscore/bwf_std_format.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/BufferWriter.h" using namespace std::literals; @@ -262,9 +262,9 @@ OutboundConnTrack::to_json_string() { std::string text; size_t extent = 0; - static const ts::BWFormat header_fmt{R"({{"count": {}, "list": [ + static const ts::bwf::Format header_fmt{R"({{"count": {}, "list": [ )"}; - static const ts::BWFormat item_fmt{ + static const ts::bwf::Format item_fmt{ R"( {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "queued": {}, "alert": {}}}, )"}; static const std::string_view trailer{" \n]}"}; @@ -288,12 +288,12 @@ OutboundConnTrack::to_json_string() text.resize(extent); ts::FixedBufferWriter w(const_cast(text.data()), text.size()); - w.clip(trailer.size()); + w.restrict(trailer.size()); w.print(header_fmt, groups.size()); for (auto g : groups) { printer(w, g); } - w.extend(trailer.size()); + w.restore(trailer.size()); w.write(trailer); return text; } @@ -363,7 +363,7 @@ OutboundConnTrack::Warning_Bad_Match_Type(std::string_view tag) w.write(n); w.write("',"sv); } - w.auxBuffer()[-1] = '\0'; // clip trailing comma and null terminate. + w.aux_data()[-1] = '\0'; // clip trailing comma and null terminate. Warning("%s", w.data()); } @@ -407,7 +407,7 @@ OutboundConnTrack::TxnState::Warn_Blocked(TxnConfig *config, int64_t sm_id, int namespace ts { BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::MatchType type) +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::MatchType type) { if (spec.has_numeric_type()) { bwformat(w, spec, static_cast(type)); @@ -418,7 +418,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::MatchType type } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key const &key) +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group::Key const &key) { switch (key._match_type) { case OutboundConnTrack::MATCH_BOTH: @@ -438,7 +438,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key con } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group const &g) +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group const &g) { switch (g._match_type) { case OutboundConnTrack::MATCH_BOTH: diff --git a/proxy/http/HttpConnectionCount.h b/proxy/http/HttpConnectionCount.h index 7ff0a6eecfe..9f6b189070e 100644 --- a/proxy/http/HttpConnectionCount.h +++ b/proxy/http/HttpConnectionCount.h @@ -36,7 +36,7 @@ #include "tscpp/util/IntrusiveHashMap.h" #include "tscore/Diags.h" #include "tscore/CryptoHash.h" -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" #include "tscpp/util/TextView.h" #include #include "HttpProxyAPIEnums.h" @@ -271,7 +271,7 @@ class OutboundConnTrack /// Internal implementation class instance. struct Imp { ts::IntrusiveHashMap _table; ///< Hash table of upstream groups. - std::mutex _mutex; ///< Lock for insert & find. + std::mutex _mutex; ///< Lock for insert & find. }; static Imp _imp; @@ -430,7 +430,7 @@ Action *register_ShowConnectionCount(Continuation *, HTTPHdr *); namespace ts { -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::MatchType type); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key const &key); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group const &g); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::MatchType type); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group::Key const &key); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group const &g); } // namespace ts diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc index a67916710ad..23622ba789e 100644 --- a/proxy/http/HttpDebugNames.cc +++ b/proxy/http/HttpDebugNames.cc @@ -28,6 +28,7 @@ #include "Transform.h" #include "HttpSM.h" #include "HttpUpdateSM.h" +#include "tscpp/util/bwf_base.h" //---------------------------------------------------------------------------- const char * @@ -482,7 +483,7 @@ HttpDebugNames::get_api_hook_name(TSHttpHookID t) } ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState_t state) +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::ServerState_t state) { if (spec.has_numeric_type()) { return bwformat(w, spec, static_cast(state)); @@ -492,7 +493,7 @@ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState } ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::CacheAction_t state) +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::CacheAction_t state) { if (spec.has_numeric_type()) { return bwformat(w, spec, static_cast(state)); @@ -502,7 +503,7 @@ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::CacheAction } ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::StateMachineAction_t state) +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::StateMachineAction_t state) { if (spec.has_numeric_type()) { return bwformat(w, spec, static_cast(state)); @@ -512,7 +513,7 @@ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::StateMachin } ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, TSHttpHookID id) +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, TSHttpHookID id) { if (spec.has_numeric_type()) { return bwformat(w, spec, static_cast(id)); diff --git a/proxy/http/HttpDebugNames.h b/proxy/http/HttpDebugNames.h index ec7a494082a..7631f5c4465 100644 --- a/proxy/http/HttpDebugNames.h +++ b/proxy/http/HttpDebugNames.h @@ -24,7 +24,7 @@ #pragma once #include "HttpTransact.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" class HttpDebugNames { @@ -37,7 +37,7 @@ class HttpDebugNames static const char *get_server_state_name(HttpTransact::ServerState_t state); }; -ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState_t state); -ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::CacheAction_t state); -ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::StateMachineAction_t state); -ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, TSHttpHookID id); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::ServerState_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::CacheAction_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::StateMachineAction_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, TSHttpHookID id); diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index a342e276f31..487aae5f248 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -39,6 +39,7 @@ #include "http2/Http2SessionAccept.h" #include "HttpConnectionCount.h" #include "HttpProxyServerMain.h" +#include "tscpp/util/bwf_base.h" #include @@ -58,22 +59,22 @@ extern int num_accept_threads; /// Global BufferWriter format name functions. namespace { -void -TS_bwf_thread(ts::BufferWriter &w, ts::BWFSpec const &spec) +ts::BufferWriter & +TS_bwf_thread(ts::BufferWriter &w, ts::bwf::Spec const &spec) { - bwformat(w, spec, this_thread()); + return bwformat(w, spec, this_thread()); } -void -TS_bwf_ethread(ts::BufferWriter &w, ts::BWFSpec const &spec) +ts::BufferWriter & +TS_bwf_ethread(ts::BufferWriter &w, ts::bwf::Spec const &spec) { - bwformat(w, spec, this_ethread()); + return bwformat(w, spec, this_ethread()); } } // namespace // File / process scope initializations static bool HTTP_SERVER_INITIALIZED __attribute__((unused)) = []() -> bool { - ts::bwf_register_global("ts-thread", &TS_bwf_thread); - ts::bwf_register_global("ts-ethread", &TS_bwf_ethread); + ts::bwf::Global_Names.assign("ts-thread", &TS_bwf_thread); + ts::bwf::Global_Names.assign("ts-ethread", &TS_bwf_ethread); return true; }(); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index d339068a7da..e70fb05ba20 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -43,7 +43,7 @@ #include "HttpPages.h" #include "IPAllow.h" #include "tscore/I_Layout.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/bwf_ex.h" #include #include diff --git a/proxy/http/HttpServerSession.cc b/proxy/http/HttpServerSession.cc index cadbcc0e085..8988c454f8c 100644 --- a/proxy/http/HttpServerSession.cc +++ b/proxy/http/HttpServerSession.cc @@ -29,8 +29,8 @@ ****************************************************************************/ #include "tscore/ink_config.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_std.h" #include "tscore/Allocator.h" #include "HttpServerSession.h" #include "HttpSessionManager.h" diff --git a/proxy/http/HttpTransactHeaders.cc b/proxy/http/HttpTransactHeaders.cc index 5f18843b4d8..bf326f0c317 100644 --- a/proxy/http/HttpTransactHeaders.cc +++ b/proxy/http/HttpTransactHeaders.cc @@ -27,7 +27,7 @@ #include #include "tscore/ink_platform.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "HttpTransact.h" #include "HttpTransactHeaders.h" @@ -1026,80 +1026,80 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (optSet[HttpForwarded::FOR] and ats_is_ip(&s->client_info.src_addr.sa)) { // NOTE: The logic within this if statement assumes that hdr is empty at this point. - hdr << "for="; + hdr.write("for="); bool is_ipv6 = ats_is_ip6(&s->client_info.src_addr.sa); if (is_ipv6) { - hdr << "\"["; + hdr.write("\"["); } - if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) { + if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.aux_data(), hdr.remaining()) == nullptr) { Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed"); return; } // Fail-safe. - hdr.auxBuffer()[hdr.remaining() - 1] = '\0'; + hdr.aux_data()[hdr.remaining() - 1] = '\0'; - hdr.fill(strlen(hdr.auxBuffer())); + hdr.commit(strlen(hdr.aux_data())); if (is_ipv6) { - hdr << "]\""; + hdr.write("]\""); } } if (optSet[HttpForwarded::BY_UNKNOWN]) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=unknown"; + hdr.write("by=unknown"); } if (optSet[HttpForwarded::BY_SERVER_NAME]) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=" << s->http_config_param->proxy_hostname; + hdr.write("by=").write(s->http_config_param->proxy_hostname); } const Machine &m = *Machine::instance(); if (optSet[HttpForwarded::BY_UUID] and m.uuid.valid()) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=_" << m.uuid.getString(); + hdr.write("by=_").write(m.uuid.getString()); } if (optSet[HttpForwarded::BY_IP] and (m.ip_string_len > 0)) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by="; + hdr.write("by="); bool is_ipv6 = ats_is_ip6(&s->client_info.dst_addr.sa); if (is_ipv6) { - hdr << "\"["; + hdr.write("\"["); } - if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) { + if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.aux_data(), hdr.remaining()) == nullptr) { Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed"); return; } // Fail-safe. - hdr.auxBuffer()[hdr.remaining() - 1] = '\0'; + hdr.aux_data()[hdr.remaining() - 1] = '\0'; - hdr.fill(strlen(hdr.auxBuffer())); + hdr.commit(strlen(hdr.aux_data())); if (is_ipv6) { - hdr << "]\""; + hdr.write("]\""); } } @@ -1118,15 +1118,15 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (optSet[HttpForwarded::PROTO] and (n_proto > 0)) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "proto="; + hdr.write("proto="); - int numChars = HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), ProtocolStackDetail::Compact, + int numChars = HttpTransactHeaders::write_hdr_protocol_stack(hdr.aux_data(), hdr.remaining(), ProtocolStackDetail::Compact, protoBuf.data(), n_proto, '-'); if (numChars > 0) { - hdr.fill(size_t(numChars)); + hdr.commit(size_t(numChars)); } } @@ -1139,16 +1139,16 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP bool needsDoubleQuotes = hSV.find(':') != std::string_view::npos; if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "host="; + hdr.write("host="); if (needsDoubleQuotes) { - hdr << '"'; + hdr.write('"'); } - hdr << hSV; + hdr.write(hSV); if (needsDoubleQuotes) { - hdr << '"'; + hdr.write('"'); } } } @@ -1156,18 +1156,18 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (n_proto > 0) { auto Conn = [&](HttpForwarded::Option opt, HttpTransactHeaders::ProtocolStackDetail detail) -> void { if (optSet[opt] && hdr.remaining() > 0) { - ts::FixedBufferWriter lw{hdr.auxBuffer(), hdr.remaining()}; + ts::FixedBufferWriter lw{hdr.aux_data(), hdr.remaining()}; if (hdr.size()) { - lw << ';'; + hdr.write(';'); } - lw << "connection="; + hdr.write("connection="); int numChars = - HttpTransactHeaders::write_hdr_protocol_stack(lw.auxBuffer(), lw.remaining(), detail, protoBuf.data(), n_proto, '-'); - if (numChars > 0 && !lw.fill(size_t(numChars)).error()) { - hdr.fill(lw.size()); + HttpTransactHeaders::write_hdr_protocol_stack(lw.aux_data(), lw.remaining(), detail, protoBuf.data(), n_proto, '-'); + if (numChars > 0 && !lw.commit(size_t(numChars)).error()) { + hdr.commit(lw.size()); } } }; diff --git a/proxy/http/unit_tests/test_ForwardedConfig.cc b/proxy/http/unit_tests/test_ForwardedConfig.cc index 7835648ff6c..dbb593dee20 100644 --- a/proxy/http/unit_tests/test_ForwardedConfig.cc +++ b/proxy/http/unit_tests/test_ForwardedConfig.cc @@ -97,7 +97,7 @@ test(const char *spec, const char *reqErr, OptionBitSet bS) { ts::LocalBufferWriter<1024> error; - error << "cheese"; + error.write("cheese"); REQUIRE(bS == optStrToBitset(XS(spec), error)); std::size_t len = std::strlen(reqErr); diff --git a/src/traffic_cache_tool/CacheDefs.cc b/src/traffic_cache_tool/CacheDefs.cc index 820c5d10b89..bcafe6d5ac7 100644 --- a/src/traffic_cache_tool/CacheDefs.cc +++ b/src/traffic_cache_tool/CacheDefs.cc @@ -22,8 +22,10 @@ */ #include "CacheDefs.h" +#include #include #include +#include "tscpp/util/bwf_base.h" using namespace std; using namespace ts; diff --git a/src/traffic_cache_tool/CacheDefs.h b/src/traffic_cache_tool/CacheDefs.h index 065db639a74..1dfea3c420b 100644 --- a/src/traffic_cache_tool/CacheDefs.h +++ b/src/traffic_cache_tool/CacheDefs.h @@ -25,7 +25,7 @@ #include #include #include "tscore/I_Version.h" -#include "tscore/Scalar.h" +#include "tscpp/util/Scalar.h" #include "tscore/Regex.h" #include #include "tscpp/util/TextView.h" diff --git a/src/traffic_cache_tool/CacheScan.cc b/src/traffic_cache_tool/CacheScan.cc index 06be5355217..023ddebdc5a 100644 --- a/src/traffic_cache_tool/CacheScan.cc +++ b/src/traffic_cache_tool/CacheScan.cc @@ -26,6 +26,7 @@ #include "../../proxy/hdrs/HdrHeap.h" #include "../../proxy/hdrs/MIME.h" #include "../../proxy/hdrs/URL.h" +#include "tscpp/util/bwf_base.h" // using namespace ct; diff --git a/src/traffic_cache_tool/CacheTool.cc b/src/traffic_cache_tool/CacheTool.cc index f16a1e86a6e..12b5176f373 100644 --- a/src/traffic_cache_tool/CacheTool.cc +++ b/src/traffic_cache_tool/CacheTool.cc @@ -38,7 +38,7 @@ #include "tscore/ink_memory.h" #include "tscore/ink_file.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "tscore/CryptoHash.h" #include "tscore/ArgParser.h" #include diff --git a/src/traffic_cache_tool/Makefile.inc b/src/traffic_cache_tool/Makefile.inc index 82063805082..8cc7d16f5bd 100644 --- a/src/traffic_cache_tool/Makefile.inc +++ b/src/traffic_cache_tool/Makefile.inc @@ -41,7 +41,8 @@ traffic_cache_tool_traffic_cache_tool_LDADD = \ $(top_builddir)/src/tscore/.libs/ink_memory.o \ $(top_builddir)/src/tscore/.libs/ink_mutex.o \ $(top_builddir)/src/tscore/.libs/ink_string.o \ - $(top_builddir)/src/tscore/.libs/BufferWriterFormat.o \ + $(top_builddir)/src/tscpp/util/.libs/MemArena.o \ + $(top_builddir)/src/tscpp/util/.libs/bw_format.o \ $(top_builddir)/src/tscore/.libs/ts_file.o \ $(top_builddir)/src/tscpp/util/.libs/TextView.o \ $(top_builddir)/lib/tsconfig/.libs/Errata.o \ diff --git a/src/traffic_manager/traffic_manager.cc b/src/traffic_manager/traffic_manager.cc index 87be590793b..9e8822eb8ab 100644 --- a/src/traffic_manager/traffic_manager.cc +++ b/src/traffic_manager/traffic_manager.cc @@ -56,7 +56,7 @@ #endif #include #include -#include "tscore/bwf_std_format.h" +#include "tscpp/util/bwf_ex.h" #define FD_THROTTLE_HEADROOM (128 + 64) // TODO: consolidate with THROTTLE_FD_HEADROOM #define DIAGS_LOG_FILENAME "manager.log" diff --git a/src/tscore/BufferWriterFormat.cc b/src/tscore/BufferWriterFormat.cc deleted file mode 100644 index d5152e6f24c..00000000000 --- a/src/tscore/BufferWriterFormat.cc +++ /dev/null @@ -1,1023 +0,0 @@ -/** @file - - Formatted output for BufferWriter. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; - -namespace -{ -// Customized version of string to int. Using this instead of the general @c svtoi function -// made @c bwprint performance test run in < 30% of the time, changing it from about 2.5 -// times slower than snprintf to the same speed. This version handles only positive integers -// in decimal. -inline int -tv_to_positive_decimal(ts::TextView src, ts::TextView *out) -{ - int zret = 0; - - if (out) { - out->clear(); - } - src.ltrim_if(&isspace); - if (src.size()) { - const char *start = src.data(); - const char *limit = start + src.size(); - while (start < limit && ('0' <= *start && *start <= '9')) { - zret = zret * 10 + *start - '0'; - ++start; - } - if (out && (start > src.data())) { - out->assign(src.data(), start); - } - } - return zret; -} -} // namespace - -namespace ts -{ -const BWFSpec BWFSpec::DEFAULT; - -const BWFSpec::Property BWFSpec::_prop; - -#pragma GCC diagnostic ignored "-Wchar-subscripts" -BWFSpec::Property::Property() -{ - memset(_data, 0, sizeof(_data)); - _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; - _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['g'] = TYPE_CHAR; - _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['p'] = TYPE_CHAR; - _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR; - _data['s'] = TYPE_CHAR; - _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR; - _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; - - _data[' '] = SIGN_CHAR; - _data['-'] = SIGN_CHAR; - _data['+'] = SIGN_CHAR; - - _data['<'] = static_cast(BWFSpec::Align::LEFT); - _data['>'] = static_cast(BWFSpec::Align::RIGHT); - _data['^'] = static_cast(BWFSpec::Align::CENTER); - _data['='] = static_cast(BWFSpec::Align::SIGN); -} - -/// Parse a format specification. -BWFSpec::BWFSpec(TextView fmt) -{ - TextView num; // temporary for number parsing. - intmax_t n; - - _name = fmt.take_prefix_at(':'); - // if it's parsable as a number, treat it as an index. - n = tv_to_positive_decimal(_name, &num); - if (num.size()) { - _idx = static_cast(n); - } - - if (fmt.size()) { - TextView sz = fmt.take_prefix_at(':'); // the format specifier. - _ext = fmt; // anything past the second ':' is the extension. - if (sz.size()) { - // fill and alignment - if ('%' == *sz) { // enable URI encoding of the fill character so metasyntactic chars can be used if needed. - if (sz.size() < 4) { - throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark"); - } - if (Align::NONE == (_align = align_of(sz[3]))) { - throw std::invalid_argument("Fill URI without alignment mark"); - } - char d1 = sz[1], d0 = sz[2]; - if (!isxdigit(d0) || !isxdigit(d1)) { - throw std::invalid_argument("URI encoding with non-hex characters"); - } - _fill = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10; - _fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4; - sz += 4; - } else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) { - _fill = *sz; - sz += 2; - } else if (Align::NONE != (_align = align_of(*sz))) { - ++sz; - } - if (!sz.size()) { - return; - } - // sign - if (is_sign(*sz)) { - _sign = *sz; - if (!(++sz).size()) { - return; - } - } - // radix prefix - if ('#' == *sz) { - _radix_lead_p = true; - if (!(++sz).size()) { - return; - } - } - // 0 fill for integers - if ('0' == *sz) { - if (Align::NONE == _align) { - _align = Align::SIGN; - } - _fill = '0'; - ++sz; - } - n = tv_to_positive_decimal(sz, &num); - if (num.size()) { - _min = static_cast(n); - sz.remove_prefix(num.size()); - if (!sz.size()) { - return; - } - } - // precision - if ('.' == *sz) { - n = tv_to_positive_decimal(++sz, &num); - if (num.size()) { - _prec = static_cast(n); - sz.remove_prefix(num.size()); - if (!sz.size()) { - return; - } - } else { - throw std::invalid_argument("Precision mark without precision"); - } - } - // style (type). Hex, octal, etc. - if (is_type(*sz)) { - _type = *sz; - if (!(++sz).size()) { - return; - } - } - // maximum width - if (',' == *sz) { - n = tv_to_positive_decimal(++sz, &num); - if (num.size()) { - _max = static_cast(n); - sz.remove_prefix(num.size()); - if (!sz.size()) { - return; - } - } else { - throw std::invalid_argument("Maximum width mark without width"); - } - // Can only have a type indicator here if there was a max width. - if (is_type(*sz)) { - _type = *sz; - if (!(++sz).size()) { - return; - } - } - } - } - } -} - -namespace bw_fmt -{ - GlobalTable BWF_GLOBAL_TABLE; - - void - Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) - { - static const BWFormat fmt{"{{BAD_ARG_INDEX:{} of {}}}"sv}; - w.print(fmt, i, n); - } - - /** This performs generic alignment operations. - - If a formatter specialization performs this operation instead, that should result in output that - is at least @a spec._min characters wide, which will cause this function to make no further - adjustments. - */ - void - Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw) - { - size_t extent = lw.extent(); - size_t min = spec._min; - size_t size = lw.size(); - if (extent < min) { - size_t delta = min - extent; - char *base = w.auxBuffer(); // should be first byte of @a lw e.g. lw.data() - avoid const_cast. - char *limit = base + lw.capacity(); // first invalid byte. - char *dst; // used to track memory operation targest; - char *last; // track limit of memory operation. - size_t d2; - switch (spec._align) { - case BWFSpec::Align::RIGHT: - dst = base + delta; // move existing content to here. - if (dst < limit) { - last = dst + size; // amount of data to move. - if (last > limit) { - last = limit; - } - std::memmove(dst, base, last - dst); - } - dst = base; - last = base + delta; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - case BWFSpec::Align::CENTER: - d2 = (delta + 1) / 2; // always > 0 because min > extent - // Move the original content right to make space to fill on the left. - dst = base + d2; // move existing content to here. - if (dst < limit) { - last = dst + size; // amount of data to move. - if (last > limit) { - last = limit; - } - std::memmove(dst, base, last - dst); // move content. - } - // Left fill. - dst = base; - last = base + d2; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - // Right fill. - dst += size; - last = dst + delta / 2; // round down - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - default: - // Everything else is equivalent to LEFT - distinction is for more specialized - // types such as integers. - dst = base + size; - last = dst + delta; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - } - w.fill(min); - } else { - size_t max = spec._max; - if (max < extent) { - extent = max; - } - w.fill(extent); - } - } - - // Conversions from remainder to character, in upper and lower case versions. - // Really only useful for hexadecimal currently. - namespace - { - char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - static const std::array POWERS_OF_TEN = { - {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}}; - } // namespace - - /// Templated radix based conversions. Only a small number of radix are supported - /// and providing a template minimizes cut and paste code while also enabling - /// compiler optimizations (e.g. for power of 2 radix the modulo / divide become - /// bit operations). - template - size_t - To_Radix(uintmax_t n, char *buff, size_t width, char *digits) - { - static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36"); - char *out = buff + width; - if (n) { - while (n) { - *--out = digits[n % RADIX]; - n /= RADIX; - } - } else { - *--out = '0'; - } - return (buff + width) - out; - } - - template - void - Write_Aligned(BufferWriter &w, F const &f, BWFSpec::Align align, int width, char fill, char neg) - { - switch (align) { - case BWFSpec::Align::LEFT: - if (neg) { - w.write(neg); - } - f(); - while (width-- > 0) { - w.write(fill); - } - break; - case BWFSpec::Align::RIGHT: - while (width-- > 0) { - w.write(fill); - } - if (neg) { - w.write(neg); - } - f(); - break; - case BWFSpec::Align::CENTER: - for (int i = width / 2; i > 0; --i) { - w.write(fill); - } - if (neg) { - w.write(neg); - } - f(); - for (int i = (width + 1) / 2; i > 0; --i) { - w.write(fill); - } - break; - case BWFSpec::Align::SIGN: - if (neg) { - w.write(neg); - } - while (width-- > 0) { - w.write(fill); - } - f(); - break; - default: - if (neg) { - w.write(neg); - } - f(); - break; - } - } - - BufferWriter & - Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t i, bool neg_p) - { - size_t n = 0; - int width = static_cast(spec._min); // amount left to fill. - char neg = 0; - char prefix1 = spec._radix_lead_p ? '0' : 0; - char prefix2 = 0; - char buff[std::numeric_limits::digits + 1]; - - if (neg_p) { - neg = '-'; - } else if (spec._sign != '-') { - neg = spec._sign; - } - - switch (spec._type) { - case 'x': - prefix2 = 'x'; - n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); - break; - case 'X': - prefix2 = 'X'; - n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS); - break; - case 'b': - prefix2 = 'b'; - n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); - break; - case 'B': - prefix2 = 'B'; - n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS); - break; - case 'o': - n = bw_fmt::To_Radix<8>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); - break; - default: - prefix1 = 0; - n = bw_fmt::To_Radix<10>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS); - break; - } - // Clip fill width by stuff that's already committed to be written. - if (neg) { - --width; - } - if (prefix1) { - --width; - if (prefix2) { - --width; - } - } - width -= static_cast(n); - std::string_view digits{buff + sizeof(buff) - n, n}; - - if (spec._align == BWFSpec::Align::SIGN) { // custom for signed case because prefix and digits are seperated. - if (neg) { - w.write(neg); - } - if (prefix1) { - w.write(prefix1); - if (prefix2) { - w.write(prefix2); - } - } - while (width-- > 0) { - w.write(spec._fill); - } - w.write(digits); - } else { // use generic Write_Aligned - Write_Aligned(w, - [&]() { - if (prefix1) { - w.write(prefix1); - if (prefix2) { - w.write(prefix2); - } - } - w.write(digits); - }, - spec._align, width, spec._fill, neg); - } - return w; - } - - /// Format for floating point values. Seperates floating point into a whole number and a - /// fraction. The fraction is converted into an unsigned integer based on the specified - /// precision, spec._prec. ie. 3.1415 with precision two is seperated into two unsigned - /// integers 3 and 14. The different pieces are assembled and placed into the BufferWriter. - /// The default is two decimal places. ie. X.XX. The value is always written in base 10. - /// - /// format: whole.fraction - /// or: left.right - BufferWriter & - Format_Floating(BufferWriter &w, BWFSpec const &spec, double f, bool neg_p) - { - static const std::string_view infinity_bwf{"Inf"}; - static const std::string_view nan_bwf{"NaN"}; - static const std::string_view zero_bwf{"0"}; - static const std::string_view subnormal_bwf{"subnormal"}; - static const std::string_view unknown_bwf{"unknown float"}; - - // Handle floating values that are not normal - if (!std::isnormal(f)) { - std::string_view unnormal; - switch (std::fpclassify(f)) { - case FP_INFINITE: - unnormal = infinity_bwf; - break; - case FP_NAN: - unnormal = nan_bwf; - break; - case FP_ZERO: - unnormal = zero_bwf; - break; - case FP_SUBNORMAL: - unnormal = subnormal_bwf; - break; - default: - unnormal = unknown_bwf; - } - - w.write(unnormal); - return w; - } - - uint64_t whole_part = static_cast(f); - if (whole_part == f || spec._prec == 0) { // integral - return Format_Integer(w, spec, whole_part, neg_p); - } - - static constexpr char dec = '.'; - double frac; - size_t l = 0; - size_t r = 0; - char whole[std::numeric_limits::digits10 + 1]; - char fraction[std::numeric_limits::digits10 + 1]; - char neg = 0; - int width = static_cast(spec._min); // amount left to fill. - unsigned int precision = (spec._prec == BWFSpec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 - - frac = f - whole_part; // split the number - - if (neg_p) { - neg = '-'; - } else if (spec._sign != '-') { - neg = spec._sign; - } - - // Shift the floating point based on the precision. Used to convert - // trailing fraction into an integer value. - uint64_t shift; - if (precision < POWERS_OF_TEN.size()) { - shift = POWERS_OF_TEN[precision]; - } else { // not precomputed. - shift = POWERS_OF_TEN.back(); - for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) { - shift *= 10; - } - } - - uint64_t frac_part = static_cast(frac * shift + 0.5 /* rounding */); - - l = bw_fmt::To_Radix<10>(whole_part, whole, sizeof(whole), bw_fmt::LOWER_DIGITS); - r = bw_fmt::To_Radix<10>(frac_part, fraction, sizeof(fraction), bw_fmt::LOWER_DIGITS); - - // Clip fill width - if (neg) { - --width; - } - width -= static_cast(l); - --width; // '.' - width -= static_cast(r); - - std::string_view whole_digits{whole + sizeof(whole) - l, l}; - std::string_view frac_digits{fraction + sizeof(fraction) - r, r}; - - Write_Aligned(w, - [&]() { - w.write(whole_digits); - w.write(dec); - w.write(frac_digits); - }, - spec._align, width, spec._fill, neg); - - return w; - } - - /// Write out the @a data as hexadecimal, using @a digits as the conversion. - void - Hex_Dump(BufferWriter &w, std::string_view data, const char *digits) - { - const char *ptr = data.data(); - for (auto n = data.size(); n > 0; --n) { - char c = *ptr++; - w.write(digits[(c >> 4) & 0xF]); - w.write(digits[c & 0xf]); - } - } - -} // namespace bw_fmt - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv) -{ - int width = static_cast(spec._min); // amount left to fill. - if (spec._prec > 0) { - sv.remove_prefix(spec._prec); - } - - if ('x' == spec._type || 'X' == spec._type) { - const char *digits = 'x' == spec._type ? bw_fmt::LOWER_DIGITS : bw_fmt::UPPER_DIGITS; - width -= sv.size() * 2; - if (spec._radix_lead_p) { - w.write('0'); - w.write(spec._type); - width -= 2; - } - bw_fmt::Write_Aligned(w, [&w, &sv, digits]() { bw_fmt::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0); - } else { - width -= sv.size(); - bw_fmt::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); - } - return w; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span) -{ - static const BWFormat default_fmt{"{:#x}@{:p}"}; - if (spec._ext.size() && 'd' == spec._ext.front()) { - const char *digits = 'X' == spec._type ? bw_fmt::UPPER_DIGITS : bw_fmt::LOWER_DIGITS; - if (spec._radix_lead_p) { - w.write('0'); - w.write(digits[33]); - } - bw_fmt::Hex_Dump(w, span.view(), digits); - } else { - w.print(default_fmt, span.size(), span.data()); - } - return w; -} - -/// Preparse format string for later use. -BWFormat::BWFormat(ts::TextView fmt) -{ - BWFSpec lit_spec{BWFSpec::DEFAULT}; - int arg_idx = 0; - - while (fmt) { - std::string_view lit_str; - std::string_view spec_str; - bool spec_p = this->parse(fmt, lit_str, spec_str); - - if (lit_str.size()) { - lit_spec._ext = lit_str; - _items.emplace_back(lit_spec, &Format_Literal); - } - if (spec_p) { - bw_fmt::GlobalSignature gf = nullptr; - BWFSpec parsed_spec{spec_str}; - if (parsed_spec._name.size() == 0) { // no name provided, use implicit index. - parsed_spec._idx = arg_idx; - } - if (parsed_spec._idx < 0) { // name wasn't missing or a valid index, assume global name. - gf = bw_fmt::Global_Table_Find(parsed_spec._name); - } else { - ++arg_idx; // bump this if not a global name. - } - _items.emplace_back(parsed_spec, gf); - } - } -} - -BWFormat::~BWFormat() {} - -/// Parse out the next literal and/or format specifier from the format string. -/// Pass the results back in @a literal and @a specifier as appropriate. -/// Update @a fmt to strip the parsed text. -bool -BWFormat::parse(ts::TextView &fmt, std::string_view &literal, std::string_view &specifier) -{ - TextView::size_type off; - - // Check for brace delimiters. - off = fmt.find_if([](char c) { return '{' == c || '}' == c; }); - if (off == TextView::npos) { - // not found, it's a literal, ship it. - literal = fmt; - fmt.remove_prefix(literal.size()); - return false; - } - - // Processing for braces that don't enclose specifiers. - if (fmt.size() > off + 1) { - char c1 = fmt[off]; - char c2 = fmt[off + 1]; - if (c1 == c2) { - // double braces count as literals, but must tweak to out only 1 brace. - literal = fmt.take_prefix_at(off + 1); - return false; - } else if ('}' == c1) { - throw std::invalid_argument("BWFormat:: Unopened } in format string."); - } else { - literal = std::string_view{fmt.data(), off}; - fmt.remove_prefix(off + 1); - } - } else { - throw std::invalid_argument("BWFormat: Invalid trailing character in format string."); - } - - if (fmt.size()) { - // Need to be careful, because an empty format is OK and it's hard to tell if - // take_prefix_at failed to find the delimiter or found it as the first byte. - off = fmt.find('}'); - if (off == TextView::npos) { - throw std::invalid_argument("BWFormat: Unclosed { in format string"); - } - specifier = fmt.take_prefix_at(off); - return true; - } - return false; -} - -void -BWFormat::Format_Literal(BufferWriter &w, BWFSpec const &spec) -{ - w.write(spec._ext); -} - -bw_fmt::GlobalSignature -bw_fmt::Global_Table_Find(std::string_view name) -{ - if (name.size()) { - auto spot = bw_fmt::BWF_GLOBAL_TABLE.find(name); - if (spot != bw_fmt::BWF_GLOBAL_TABLE.end()) { - return spot->second; - } - } - return nullptr; -} - -std::ostream & -FixedBufferWriter::operator>>(std::ostream &s) const -{ - return s << this->view(); -} - -ssize_t -FixedBufferWriter::operator>>(int fd) const -{ - return ::write(fd, this->data(), this->size()); -} - -bool -bwf_register_global(std::string_view name, BWGlobalNameSignature formatter) -{ - return ts::bw_fmt::BWF_GLOBAL_TABLE.emplace(name, formatter).second; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e) -{ - // Hand rolled, might not be totally compliant everywhere, but probably close enough. - // The long string will be locally accurate. - // Clang requires the double braces. Why, Turing only knows. - static const std::array SHORT_NAME = {{ - "SUCCESS: ", - "EPERM: ", - "ENOENT: ", - "ESRCH: ", - "EINTR: ", - "EIO: ", - "ENXIO: ", - "E2BIG ", - "ENOEXEC: ", - "EBADF: ", - "ECHILD: ", - "EAGAIN: ", - "ENOMEM: ", - "EACCES: ", - "EFAULT: ", - "ENOTBLK: ", - "EBUSY: ", - "EEXIST: ", - "EXDEV: ", - "ENODEV: ", - "ENOTDIR: ", - "EISDIR: ", - "EINVAL: ", - "ENFILE: ", - "EMFILE: ", - "ENOTTY: ", - "ETXTBSY: ", - "EFBIG: ", - "ENOSPC: ", - "ESPIPE: ", - "EROFS: ", - "EMLINK: ", - "EPIPE: ", - "EDOM: ", - "ERANGE: ", - "EDEADLK: ", - "ENAMETOOLONG: ", - "ENOLCK: ", - "ENOSYS: ", - "ENOTEMPTY: ", - "ELOOP: ", - "EWOULDBLOCK: ", - "ENOMSG: ", - "EIDRM: ", - "ECHRNG: ", - "EL2NSYNC: ", - "EL3HLT: ", - "EL3RST: ", - "ELNRNG: ", - "EUNATCH: ", - "ENOCSI: ", - "EL2HTL: ", - "EBADE: ", - "EBADR: ", - "EXFULL: ", - "ENOANO: ", - "EBADRQC: ", - "EBADSLT: ", - "EDEADLOCK: ", - "EBFONT: ", - "ENOSTR: ", - "ENODATA: ", - "ETIME: ", - "ENOSR: ", - "ENONET: ", - "ENOPKG: ", - "EREMOTE: ", - "ENOLINK: ", - "EADV: ", - "ESRMNT: ", - "ECOMM: ", - "EPROTO: ", - "EMULTIHOP: ", - "EDOTDOT: ", - "EBADMSG: ", - "EOVERFLOW: ", - "ENOTUNIQ: ", - "EBADFD: ", - "EREMCHG: ", - "ELIBACC: ", - "ELIBBAD: ", - "ELIBSCN: ", - "ELIBMAX: ", - "ELIBEXEC: ", - "EILSEQ: ", - "ERESTART: ", - "ESTRPIPE: ", - "EUSERS: ", - "ENOTSOCK: ", - "EDESTADDRREQ: ", - "EMSGSIZE: ", - "EPROTOTYPE: ", - "ENOPROTOOPT: ", - "EPROTONOSUPPORT: ", - "ESOCKTNOSUPPORT: ", - "EOPNOTSUPP: ", - "EPFNOSUPPORT: ", - "EAFNOSUPPORT: ", - "EADDRINUSE: ", - "EADDRNOTAVAIL: ", - "ENETDOWN: ", - "ENETUNREACH: ", - "ENETRESET: ", - "ECONNABORTED: ", - "ECONNRESET: ", - "ENOBUFS: ", - "EISCONN: ", - "ENOTCONN: ", - "ESHUTDOWN: ", - "ETOOMANYREFS: ", - "ETIMEDOUT: ", - "ECONNREFUSED: ", - "EHOSTDOWN: ", - "EHOSTUNREACH: ", - "EALREADY: ", - "EINPROGRESS: ", - "ESTALE: ", - "EUCLEAN: ", - "ENOTNAM: ", - "ENAVAIL: ", - "EISNAM: ", - "EREMOTEIO: ", - "EDQUOT: ", - "ENOMEDIUM: ", - "EMEDIUMTYPE: ", - "ECANCELED: ", - "ENOKEY: ", - "EKEYEXPIRED: ", - "EKEYREVOKED: ", - "EKEYREJECTED: ", - "EOWNERDEAD: ", - "ENOTRECOVERABLE: ", - "ERFKILL: ", - "EHWPOISON: ", - }}; - // This provides convenient safe access to the errno short name array. - auto short_name = [](int n) { return n < static_cast(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; - static const BWFormat number_fmt{"[{}]"sv}; // numeric value format. - if (spec.has_numeric_type()) { // if numeric type, print just the numeric part. - w.print(number_fmt, e._e); - } else { - w.write(short_name(e._e)); - w.write(strerror(e._e)); - if (spec._type != 's' && spec._type != 'S') { - w.write(' '); - w.print(number_fmt, e._e); - } - } - return w; -} - -bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date) -{ - if (spec.has_numeric_type()) { - bwformat(w, spec, date._epoch); - } else { - struct tm t; - auto r = w.remaining(); - size_t n{0}; - // Verify @a fmt is null terminated, even outside the bounds of the view. - ink_assert(date._fmt.data()[date._fmt.size() - 1] == 0 || date._fmt.data()[date._fmt.size()] == 0); - // Get the time, GMT or local if specified. - if (spec._ext == "local"sv) { - localtime_r(&date._epoch, &t); - } else { - gmtime_r(&date._epoch, &t); - } - // Try a direct write, faster if it works. - if (r > 0) { - n = strftime(w.auxBuffer(), r, date._fmt.data(), &t); - } - if (n > 0) { - w.fill(n); - } else { - // Direct write didn't work. Unfortunately need to write to a temporary buffer or the sizing - // isn't correct if @a w is clipped because @c strftime returns 0 if the buffer isn't large - // enough. - char buff[256]; // hope for the best - no real way to resize appropriately on failure. - n = strftime(buff, sizeof(buff), date._fmt.data(), &t); - w.write(buff, n); - } - } - return w; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts) -{ - return w.write(opts._prefix).write(opts._text).write(opts._suffix); -} - -} // namespace ts - -namespace -{ -void -BWF_Timestamp(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - // Unfortunately need to write to a temporary buffer or the sizing isn't correct if @a w is clipped - // because @c strftime returns 0 if the buffer isn't large enough. - char buff[32]; - std::time_t t = std::time(nullptr); - auto n = strftime(buff, sizeof(buff), "%Y %b %d %H:%M:%S", std::localtime(&t)); - w.write(buff, n); -} - -void -BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); -} - -void -BWF_Tick(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, std::chrono::high_resolution_clock::now().time_since_epoch().count()); -} - -void -BWF_ThreadID(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, pthread_self()); -} - -void -BWF_ThreadName(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ -#if defined(__FreeBSD_version) - bwformat(w, spec, "thread"sv); // no thread names in FreeBSD. -#else - char name[32]; // manual says at least 16, bump that up a bit. - pthread_getname_np(pthread_self(), name, sizeof(name)); - bwformat(w, spec, std::string_view{name}); -#endif -} - -static bool BW_INITIALIZED __attribute__((unused)) = []() -> bool { - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("now", &BWF_Now); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("tick", &BWF_Tick); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("timestamp", &BWF_Timestamp); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-id", &BWF_ThreadID); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-name", &BWF_ThreadName); - return true; -}(); - -} // namespace - -namespace std -{ -ostream & -operator<<(ostream &s, ts::FixedBufferWriter &w) -{ - return s << w.view(); -} -} // namespace std diff --git a/src/tscore/CryptoHash.cc b/src/tscore/CryptoHash.cc index fec88fb84e0..46d6a684a10 100644 --- a/src/tscore/CryptoHash.cc +++ b/src/tscore/CryptoHash.cc @@ -29,6 +29,7 @@ #include "tscore/ink_code.h" #include "tscore/CryptoHash.h" #include "tscore/SHA256.h" +#include "tscpp/util/bwf_base.h" #if TS_ENABLE_FIPS == 1 CryptoContext::HashType CryptoContext::Setting = CryptoContext::SHA256; @@ -111,9 +112,9 @@ CryptoHash::toHexStr(char buffer[(CRYPTO_HASH_SIZE * 2) + 1]) const namespace ats { ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, ats::CryptoHash const &hash) +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, ats::CryptoHash const &hash) { - ts::BWFSpec local_spec{spec}; + ts::bwf::Spec local_spec{spec}; if ('X' != local_spec._type) local_spec._type = 'x'; return bwformat(w, local_spec, std::string_view(reinterpret_cast(hash.u8), CRYPTO_HASH_SIZE)); diff --git a/src/tscore/Diags.cc b/src/tscore/Diags.cc index 8d2cd02a185..fba1510e73f 100644 --- a/src/tscore/Diags.cc +++ b/src/tscore/Diags.cc @@ -42,7 +42,7 @@ #include "tscore/ink_time.h" #include "tscore/ink_hrtime.h" #include "tscore/ink_thread.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "tscore/Diags.h" int diags_on_for_plugins = 0; @@ -220,7 +220,7 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat LocalBufferWriter<1024> format_writer; // Save room for optional newline and terminating NUL bytes. - format_writer.clip(2); + format_writer.restrict(2); ////////////////////// // append timestamp // @@ -245,8 +245,8 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat /////////////////////// // add the thread id // /////////////////////// - format_writer.fill( - snprintf(format_writer.auxBuffer(), format_writer.remaining(), "{0x%" PRIx64 "} ", (uint64_t)ink_thread_self())); + format_writer.commit( + snprintf(format_writer.aux_data(), format_writer.remaining(), "{0x%" PRIx64 "} ", (uint64_t)ink_thread_self())); ////////////////////////////////// // append the diag level prefix // @@ -283,7 +283,7 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat ////////////////////////////////////////////////////// format_writer.write(format_string, strlen(format_string)); - format_writer.extend(2); + format_writer.restore(2); if (format_writer.data()[format_writer.size() - 1] != '\n') { format_writer.write('\n'); } diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index c0ff6b7f549..57fcac40bf2 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -60,7 +60,6 @@ libtscore_la_SOURCES = \ BaseLogFile.h \ BufferWriter.h \ BufferWriterForward.h \ - BufferWriterFormat.cc \ ConsistentHash.cc \ ConsistentHash.h \ ContFlags.cc \ @@ -248,8 +247,6 @@ test_tscore_SOURCES = \ unit_tests/test_AcidPtr.cc \ unit_tests/test_arena.cc \ unit_tests/test_ArgParser.cc \ - unit_tests/test_BufferWriter.cc \ - unit_tests/test_BufferWriterFormat.cc \ unit_tests/test_Extendible.cc \ unit_tests/test_History.cc \ unit_tests/test_ink_inet.cc \ @@ -262,7 +259,6 @@ test_tscore_SOURCES = \ unit_tests/test_PriorityQueue.cc \ unit_tests/test_Ptr.cc \ unit_tests/test_Regex.cc \ - unit_tests/test_Scalar.cc \ unit_tests/test_scoped_resource.cc \ unit_tests/test_ts_file.cc \ unit_tests/test_Vec.cc diff --git a/src/tscore/SourceLocation.cc b/src/tscore/SourceLocation.cc index c586935b6ab..e9fa79890cb 100644 --- a/src/tscore/SourceLocation.cc +++ b/src/tscore/SourceLocation.cc @@ -25,8 +25,8 @@ #include #include "tscore/SourceLocation.h" #include "tscore/ink_defs.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" // This method takes a SourceLocation source location data structure and // converts it to a human-readable representation, in the buffer @@ -57,7 +57,7 @@ SourceLocation::str(char *buf, int buflen) const } ts::BufferWriter & -SourceLocation::print(ts::BufferWriter &w, ts::BWFSpec const &) const +SourceLocation::print(ts::BufferWriter &w, ts::bwf::Spec const &) const { if (this->valid()) { ts::TextView base{ts::TextView{file, strlen(file)}.take_suffix_at('/')}; diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc index 12524878b77..130b2e99df3 100644 --- a/src/tscore/ink_inet.cc +++ b/src/tscore/ink_inet.cc @@ -31,6 +31,7 @@ #include "tscore/ink_assert.h" #include "ts/apidefs.h" #include "tscpp/util/TextView.h" +#include "tscpp/util/bwf_base.h" #include "tscore/ink_inet.h" IpAddr const IpAddr::INVALID; @@ -692,10 +693,10 @@ ats_tcp_somaxconn() namespace ts { BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, in_addr_t addr) { uint8_t *ptr = reinterpret_cast(&addr); - BWFSpec local_spec{spec}; // Format for address elements. + bwf::Spec local_spec{spec}; // Format for address elements. bool align_p = false; if (spec._ext.size()) { @@ -710,7 +711,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) if (align_p) { local_spec._min = 3; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; } @@ -726,10 +727,10 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, in6_addr const &addr) { using QUAD = uint16_t const; - BWFSpec local_spec{spec}; // Format for address elements. + bwf::Spec local_spec{spec}; // Format for address elements. uint8_t const *ptr = addr.s6_addr; uint8_t const *limit = ptr + sizeof(addr.s6_addr); QUAD *lower = nullptr; // the best zero range @@ -748,7 +749,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) if (align_p) { local_spec._min = 4; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; // do 0 compression if there's no internal fill. @@ -794,9 +795,9 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, IpAddr const &addr) { - BWFSpec local_spec{spec}; // Format for address elements and port. + bwf::Spec local_spec{spec}; // Format for address elements and port. bool addr_p{true}; bool family_p{false}; @@ -848,9 +849,9 @@ bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr) { - BWFSpec local_spec{spec}; // Format for address elements and port. + bwf::Spec local_spec{spec}; // Format for address elements and port. bool port_p{true}; bool addr_p{true}; bool family_p{false}; @@ -918,7 +919,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr) if (local_numeric_fill_p) { local_spec._min = 5; local_spec._fill = local_numeric_fill_char; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; } diff --git a/src/tscore/unit_tests/test_History.cc b/src/tscore/unit_tests/test_History.cc index 3e699139da0..05234b46fcd 100644 --- a/src/tscore/unit_tests/test_History.cc +++ b/src/tscore/unit_tests/test_History.cc @@ -24,7 +24,7 @@ #include #include "tscore/History.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "catch.hpp" using std::string_view; @@ -73,7 +73,7 @@ TEST_CASE("History", "[libts][History]") w.print("{}", sm->history[0].location); REQUIRE(w.view() == "test_History.cc:69 (____C_A_T_C_H____T_E_S_T____0)"); - w.reset().print("{}", sm->history[1].location); + w.clear().print("{}", sm->history[1].location); REQUIRE(w.view() == "test_History.cc:70 (____C_A_T_C_H____T_E_S_T____0)"); REQUIRE(sm->history[0].event == 1); @@ -105,10 +105,10 @@ TEST_CASE("History", "[libts][History]") REQUIRE(sm2->history.size() == 2); REQUIRE(sm2->history.overflowed() == true); - w.reset().print("{}", sm2->history[0].location); + w.clear().print("{}", sm2->history[0].location); REQUIRE(w.view() == "test_History.cc:103 (____C_A_T_C_H____T_E_S_T____0)"); - w.reset().print("{}", sm2->history[1].location); + w.clear().print("{}", sm2->history[1].location); REQUIRE(w.view() == "test_History.cc:98 (____C_A_T_C_H____T_E_S_T____0)"); sm2->history.clear(); diff --git a/src/tscore/unit_tests/test_ink_inet.cc b/src/tscore/unit_tests/test_ink_inet.cc index bd5749818b8..75466c152d8 100644 --- a/src/tscore/unit_tests/test_ink_inet.cc +++ b/src/tscore/unit_tests/test_ink_inet.cc @@ -22,13 +22,13 @@ */ #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "tscore/ink_inet.h" #include #include #include "ts/apidefs.h" #include "tscore/ink_inet.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" using namespace std::literals; @@ -164,87 +164,87 @@ TEST_CASE("inet formatting", "[libts][ink_inet][bwformat]") REQUIRE(0 == ats_ip_pton(addr_1, &ep.sa)); w.print("{}", ep); REQUIRE(w.view() == addr_1); - w.reset().print("{::p}", ep); + w.clear().print("{::p}", ep); REQUIRE(w.view() == "8080"); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == addr_1.substr(1, 24)); // check the brackets are dropped. - w.reset().print("[{::a}]", ep); + w.clear().print("[{::a}]", ep); REQUIRE(w.view() == addr_1.substr(0, 26)); // check the brackets are dropped. - w.reset().print("[{0::a}]:{0::p}", ep); + w.clear().print("[{0::a}]:{0::p}", ep); REQUIRE(w.view() == addr_1); // check the brackets are dropped. - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "ffee:0000:0000:0000:24c3:3349:3cee:0143"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "ffee: 0: 0: 0:24c3:3349:3cee: 143"); ep.setToLoopback(AF_INET6); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "::1"); REQUIRE(0 == ats_ip_pton(addr_3, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337:ded:beef::"); REQUIRE(0 == ats_ip_pton(addr_4, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337::ded:beef"); REQUIRE(0 == ats_ip_pton(addr_5, &ep.sa)); - w.reset().print("{:X:a}", ep); + w.clear().print("{:X:a}", ep); REQUIRE(w.view() == "1337::DED:BEEF:0:0:956"); REQUIRE(0 == ats_ip_pton(addr_6, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337:0:0:ded:beef::"); REQUIRE(0 == ats_ip_pton(addr_null, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "::"); REQUIRE(0 == ats_ip_pton(addr_2, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == addr_2.substr(0, 13)); - w.reset().print("{0::a}", ep); + w.clear().print("{0::a}", ep); REQUIRE(w.view() == addr_2.substr(0, 13)); - w.reset().print("{::ap}", ep); + w.clear().print("{::ap}", ep); REQUIRE(w.view() == addr_2); - w.reset().print("{::f}", ep); + w.clear().print("{::f}", ep); REQUIRE(w.view() == IP_PROTO_TAG_IPV4); - w.reset().print("{::fpa}", ep); + w.clear().print("{::fpa}", ep); REQUIRE(w.view() == "172.17.99.231:23995 ipv4"); - w.reset().print("{0::a} .. {0::p}", ep); + w.clear().print("{0::a} .. {0::p}", ep); REQUIRE(w.view() == "172.17.99.231 .. 23995"); - w.reset().print("<+> {0::a} <+> {0::p}", ep); + w.clear().print("<+> {0::a} <+> {0::p}", ep); REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995"); - w.reset().print("<+> {0::a} <+> {0::p} <+>", ep); + w.clear().print("<+> {0::a} <+> {0::p} <+>", ep); REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995 <+>"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "172. 17. 99.231"); - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "172.017.099.231"); // Documentation examples REQUIRE(0 == ats_ip_pton(addr_7, &ep.sa)); - w.reset().print("To {}", ep); + w.clear().print("To {}", ep); REQUIRE(w.view() == "To 172.19.3.105:4951"); - w.reset().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice. + w.clear().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice. REQUIRE(w.view() == "To 172.19.3.105 on port 4951"); - w.reset().print("To {::=}", ep); + w.clear().print("To {::=}", ep); REQUIRE(w.view() == "To 172.019.003.105:04951"); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "172.19.3.105"); - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "172.019.003.105"); - w.reset().print("{::0=a}", ep); + w.clear().print("{::0=a}", ep); REQUIRE(w.view() == "172.019.003.105"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "172. 19. 3.105"); - w.reset().print("{:>20:a}", ep); + w.clear().print("{:>20:a}", ep); REQUIRE(w.view() == " 172.19.3.105"); - w.reset().print("{:>20:=a}", ep); + w.clear().print("{:>20:=a}", ep); REQUIRE(w.view() == " 172.019.003.105"); - w.reset().print("{:>20: =a}", ep); + w.clear().print("{:>20: =a}", ep); REQUIRE(w.view() == " 172. 19. 3.105"); - w.reset().print("{:<20:a}", ep); + w.clear().print("{:<20:a}", ep); REQUIRE(w.view() == "172.19.3.105 "); - w.reset().print("{:p}", reinterpret_cast(0x1337beef)); + w.clear().print("{:p}", reinterpret_cast(0x1337beef)); REQUIRE(w.view() == "0x1337beef"); } diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index cd126013020..782b7ec0d91 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -27,6 +27,7 @@ AM_CPPFLAGS += -I$(abs_top_srcdir)/include libtscpputil_la_LDFLAGS = -no-undefined -version-info @TS_LIBTOOL_VERSION@ libtscpputil_la_SOURCES = \ + bw_format.cc \ IntrusiveDList.h \ IntrusiveHashMap.h \ MemArena.h \ @@ -42,9 +43,13 @@ test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS) test_tscpputil_LDADD = libtscpputil.la test_tscpputil_SOURCES = \ unit_tests/unit_test_main.cc \ + unit_tests/test_IntrusiveDList.cc \ + unit_tests/test_IntrusiveHashMap.cc \ unit_tests/test_MemArena.cc \ + unit_tests/test_bw_format.cc \ unit_tests/test_MemSpan.cc \ unit_tests/test_PostScript.cc \ + unit_tests/test_Scalar.cc \ unit_tests/test_TextView.cc \ unit_tests/test_ts_meta.cc diff --git a/src/tscpp/util/bw_format.cc b/src/tscpp/util/bw_format.cc new file mode 100644 index 00000000000..e12852616cb --- /dev/null +++ b/src/tscpp/util/bw_format.cc @@ -0,0 +1,1104 @@ +/** @file + + Formatted output for BufferWriter. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_base.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/bwf_printf.h" +#include "tscpp/util/ts_meta.h" + +using namespace std::literals; + +ts::bwf::GlobalNames ts::bwf::Global_Names; + +namespace +{ +// Customized version of string to int. Using this instead of the general @c svtoi function made @c +// bwprint performance test run in < 30% of the time, changing it from about 2.5 times slower than +// snprintf to a little faster. This version handles only positive integers in decimal. + +inline unsigned +radix10(ts::TextView src, ts::TextView &out) +{ + unsigned zret = 0; + + out.clear(); + src.ltrim_if(&isspace); + if (src.size()) { + auto start = src.data(); + zret = ts::svto_radix<10>(src); + if (start != src.data()) { + out.assign(start, src.data()); + } + } + return zret; +} +} // namespace + +namespace ts +{ +namespace bwf +{ + const Spec Spec::DEFAULT; + + const Spec::Property Spec::_prop; + +#pragma GCC diagnostic ignored "-Wchar-subscripts" + Spec::Property::Property() + { + memset(_data, 0, sizeof(_data)); + _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; + _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['g'] = TYPE_CHAR; + _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['p'] = TYPE_CHAR; + _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR; + _data['s'] = TYPE_CHAR; + _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR; + _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; + + _data[SIGN_NEVER] = SIGN_CHAR; + _data[SIGN_NEG] = SIGN_CHAR; + _data[SIGN_ALWAYS] = SIGN_CHAR; + + _data['<'] = static_cast(Spec::Align::LEFT); + _data['>'] = static_cast(Spec::Align::RIGHT); + _data['^'] = static_cast(Spec::Align::CENTER); + _data['='] = static_cast(Spec::Align::SIGN); + } + + Spec::Spec(const TextView &fmt) { this->parse(fmt); } + /// Parse a format specification. + bool + Spec::parse(TextView fmt) + { + TextView num; // temporary for number parsing. + intmax_t n; + + _name = fmt.take_prefix_at(':'); + // if it's parsable as a number, treat it as an index. + n = radix10(_name, num); + if (num.size() == _name.size()) { + _idx = static_cast(n); + } + + if (fmt.size()) { + TextView sz = fmt.take_prefix_at(':'); // the format specifier. + _ext = fmt; // anything past the second ':' is the extension. + if (sz.size()) { + // fill and alignment + if ('%' == *sz) { // enable URI encoding of the fill character so + // metasyntactic chars can be used if needed. + if (sz.size() < 4) { + throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark"); + } + if (Align::NONE == (_align = align_of(sz[3]))) { + throw std::invalid_argument("Fill URI without alignment mark"); + } + char d1 = sz[1], d0 = sz[2]; + if (!isxdigit(d0) || !isxdigit(d1)) { + throw std::invalid_argument("URI encoding with non-hex characters"); + } + _fill = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10; + _fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4; + sz += 4; + } else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) { + _fill = *sz; + sz += 2; + } else if (Align::NONE != (_align = align_of(*sz))) { + ++sz; + } + if (!sz.size()) { + return true; + } + // sign + if (is_sign(*sz)) { + _sign = *sz; + if (!(++sz).size()) { + return true; + } + } + // radix prefix + if ('#' == *sz) { + _radix_lead_p = true; + if (!(++sz).size()) { + return true; + } + } + // 0 fill for integers + if ('0' == *sz) { + if (Align::NONE == _align) { + _align = Align::SIGN; + } + _fill = '0'; + ++sz; + } + n = radix10(sz, num); + if (num.size()) { + _min = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } + // precision + if ('.' == *sz) { + n = radix10(++sz, num); + if (num.size()) { + _prec = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } else { + throw std::invalid_argument("Precision mark without precision"); + } + } + // style (type). Hex, octal, etc. + if (is_type(*sz)) { + _type = *sz; + if (!(++sz).size()) { + return true; + } + } + // maximum width + if (',' == *sz) { + n = radix10(++sz, num); + if (num.size()) { + _max = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } else { + throw std::invalid_argument("Maximum width mark without width"); + } + // Can only have a type indicator here if there was a max width. + if (is_type(*sz)) { + _type = *sz; + if (!(++sz).size()) { + return true; + } + } + } + } + } + return true; + } + + /// Parse out the next literal and/or format specifier from the format string. + /// Pass the results back in @a literal and @a specifier as appropriate. + /// Update @a fmt to strip the parsed text. + /// @return @c true if a specifier was parsed, @c false if not. + bool + Format::TextViewExtractor::parse(TextView &fmt, std::string_view &literal, std::string_view &specifier) + { + TextView::size_type off; + + // Check for brace delimiters. + off = fmt.find_if([](char c) { return '{' == c || '}' == c; }); + if (off == TextView::npos) { + // not found, it's a literal, ship it. + literal = fmt; + fmt.remove_prefix(literal.size()); + return false; + } + + // Processing for braces that don't enclose specifiers. + if (fmt.size() > off + 1) { + char c1 = fmt[off]; + char c2 = fmt[off + 1]; + if (c1 == c2) { + // double braces count as literals, but must tweak to output only 1 brace. + literal = fmt.take_prefix_at(off + 1); + return false; + } else if ('}' == c1) { + throw std::invalid_argument("Unopened } in format string."); + } else { + literal = std::string_view{fmt.data(), off}; + fmt.remove_prefix(off + 1); + } + } else { + throw std::invalid_argument("Invalid trailing character in format string."); + } + + if (fmt.size()) { + // Need to be careful, because an empty format is OK and it's hard to tell + // if take_prefix_at failed to find the delimiter or found it as the first + // byte. + off = fmt.find('}'); + if (off == TextView::npos) { + throw std::invalid_argument("BWFormat: Unclosed { in format string"); + } + specifier = fmt.take_prefix_at(off); + return true; + } + return false; + } + + bool + Format::TextViewExtractor::operator()(std::string_view &literal_v, Spec &spec) + { + if (!_fmt.empty()) { + std::string_view spec_v; + if (parse(_fmt, literal_v, spec_v)) { + return spec.parse(spec_v); + } + } + return false; + } + + bool + Format::FormatExtractor::operator()(std::string_view &literal_v, ts::bwf::Spec &spec) + { + literal_v = {}; + if (_idx < int(_fmt.size()) && _fmt[_idx]._type == Spec::LITERAL_TYPE) { + literal_v = _fmt[_idx++]._ext; + } + if (_idx < int(_fmt.size()) && _fmt[_idx]._type != Spec::LITERAL_TYPE) { + spec = _fmt[_idx++]; + return true; + } + return false; + } + + void + Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) + { + static const Format fmt{"{{BAD_ARG_INDEX:{} of {}}}"sv}; + w.print(fmt, i, n); + } + + /** This performs generic alignment operations. + + If a formatter specialization performs this operation instead, that should + result in output that is at least @a spec._min characters wide, which will + cause this function to make no further adjustments. + */ + void + Adjust_Alignment(BufferWriter &aux, Spec const &spec) + { + size_t extent = aux.extent(); + size_t min = spec._min; + if (extent < min) { + size_t delta = min - extent; + size_t left_delta = 0, right_delta = delta; // left justify values + if (Spec::Align::RIGHT == spec._align) { + left_delta = delta; + right_delta = 0; + } else if (Spec::Align::CENTER == spec._align) { + left_delta = delta / 2; + right_delta = (delta + 1) / 2; + } + if (left_delta > 0) { + size_t work_area = extent + left_delta; + aux.commit(left_delta); // cover work area. + aux.copy(left_delta, 0, extent); // move to create space for left fill. + aux.discard(work_area); // roll back to write the left fill. + for (int i = left_delta; i > 0; --i) { + aux.write(spec._fill); + } + aux.commit(extent); + } + for (int i = right_delta; i > 0; --i) { + aux.write(spec._fill); + } + + } else { + size_t max = spec._max; + if (max < extent) { + aux.discard(extent - max); + } + } + } + + // Conversions from remainder to character, in upper and lower case versions. + // Really only useful for hexadecimal currently. + namespace + { + char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + static const std::array POWERS_OF_TEN = { + {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}}; + } // namespace + + /// Templated radix based conversions. Only a small number of radix are + /// supported and providing a template minimizes cut and paste code while also + /// enabling compiler optimizations (e.g. for power of 2 radix the modulo / + /// divide become bit operations). + template + size_t + To_Radix(uintmax_t n, char *buff, size_t width, char *digits) + { + static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36"); + char *out = buff + width; + if (n) { + while (n) { + *--out = digits[n % RADIX]; + n /= RADIX; + } + } else { + *--out = '0'; + } + return (buff + width) - out; + } + + template + void + Write_Aligned(BufferWriter &w, F const &f, Spec::Align align, int width, char fill, char neg) + { + switch (align) { + case Spec::Align::LEFT: + if (neg) { + w.write(neg); + } + f(); + while (width-- > 0) { + w.write(fill); + } + break; + case Spec::Align::RIGHT: + while (width-- > 0) { + w.write(fill); + } + if (neg) { + w.write(neg); + } + f(); + break; + case Spec::Align::CENTER: + for (int i = width / 2; i > 0; --i) { + w.write(fill); + } + if (neg) { + w.write(neg); + } + f(); + for (int i = (width + 1) / 2; i > 0; --i) { + w.write(fill); + } + break; + case Spec::Align::SIGN: + if (neg) { + w.write(neg); + } + while (width-- > 0) { + w.write(fill); + } + f(); + break; + default: + if (neg) { + w.write(neg); + } + f(); + break; + } + } + + BufferWriter & + Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t i, bool neg_p) + { + size_t n = 0; + int width = static_cast(spec._min); // amount left to fill. + char neg = 0; + char prefix1 = spec._radix_lead_p ? '0' : 0; + char prefix2 = 0; + char buff[std::numeric_limits::digits + 1]; + + if (spec._sign != Spec::SIGN_NEVER) { + if (neg_p) { + neg = '-'; + } else if (spec._sign == Spec::SIGN_ALWAYS) { + neg = spec._sign; + } + } + + switch (spec._type) { + case 'x': + prefix2 = 'x'; + n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + case 'X': + prefix2 = 'X'; + n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); + break; + case 'b': + prefix2 = 'b'; + n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + case 'B': + prefix2 = 'B'; + n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); + break; + case 'o': + n = bwf::To_Radix<8>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + default: + prefix1 = 0; + n = bwf::To_Radix<10>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + } + // Clip fill width by stuff that's already committed to be written. + if (neg) { + --width; + } + if (prefix1) { + --width; + if (prefix2) { + --width; + } + } + width -= static_cast(n); + std::string_view digits{buff + sizeof(buff) - n, n}; + + if (spec._align == Spec::Align::SIGN) { // custom for signed case because + // prefix and digits are seperated. + if (neg) { + w.write(neg); + } + if (prefix1) { + w.write(prefix1); + if (prefix2) { + w.write(prefix2); + } + } + while (width-- > 0) { + w.write(spec._fill); + } + w.write(digits); + } else { // use generic Write_Aligned + Write_Aligned(w, + [&]() { + if (prefix1) { + w.write(prefix1); + if (prefix2) { + w.write(prefix2); + } + } + w.write(digits); + }, + spec._align, width, spec._fill, neg); + } + return w; + } + + /// Format for floating point values. Seperates floating point into a whole + /// number and a fraction. The fraction is converted into an unsigned integer + /// based on the specified precision, spec._prec. ie. 3.1415 with precision two + /// is seperated into two unsigned integers 3 and 14. The different pieces are + /// assembled and placed into the BufferWriter. The default is two decimal + /// places. ie. X.XX. The value is always written in base 10. + /// + /// format: whole.fraction + /// or: left.right + BufferWriter & + Format_Float(BufferWriter &w, Spec const &spec, double f, bool neg_p) + { + static const std::string_view infinity_bwf{"Inf"}; + static const std::string_view nan_bwf{"NaN"}; + static const std::string_view zero_bwf{"0"}; + static const std::string_view subnormal_bwf{"subnormal"}; + static const std::string_view unknown_bwf{"unknown float"}; + + // Handle floating values that are not normal + if (!std::isnormal(f)) { + std::string_view unnormal; + switch (std::fpclassify(f)) { + case FP_INFINITE: + unnormal = infinity_bwf; + break; + case FP_NAN: + unnormal = nan_bwf; + break; + case FP_ZERO: + unnormal = zero_bwf; + break; + case FP_SUBNORMAL: + unnormal = subnormal_bwf; + break; + default: + unnormal = unknown_bwf; + } + + w.write(unnormal); + return w; + } + + uint64_t whole_part = static_cast(f); + if (whole_part == f || spec._prec == 0) { // integral + return Format_Integer(w, spec, whole_part, neg_p); + } + + static constexpr char dec = '.'; + double frac; + size_t l = 0; + size_t r = 0; + char whole[std::numeric_limits::digits10 + 1]; + char fraction[std::numeric_limits::digits10 + 1]; + char neg = 0; + int width = static_cast(spec._min); // amount left to fill. + unsigned int precision = (spec._prec == Spec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 + + frac = f - whole_part; // split the number + + if (neg_p) { + neg = '-'; + } else if (spec._sign != '-') { + neg = spec._sign; + } + + // Shift the floating point based on the precision. Used to convert + // trailing fraction into an integer value. + uint64_t shift; + if (precision < POWERS_OF_TEN.size()) { + shift = POWERS_OF_TEN[precision]; + } else { // not precomputed. + shift = POWERS_OF_TEN.back(); + for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) { + shift *= 10; + } + } + + uint64_t frac_part = static_cast(frac * shift + 0.5 /* rounding */); + + l = bwf::To_Radix<10>(whole_part, whole, sizeof(whole), bwf::LOWER_DIGITS); + r = bwf::To_Radix<10>(frac_part, fraction, sizeof(fraction), bwf::LOWER_DIGITS); + + // Clip fill width + if (neg) { + --width; + } + width -= static_cast(l); + --width; // '.' + width -= static_cast(r); + + std::string_view whole_digits{whole + sizeof(whole) - l, l}; + std::string_view frac_digits{fraction + sizeof(fraction) - r, r}; + + Write_Aligned(w, + [&]() { + w.write(whole_digits); + w.write(dec); + w.write(frac_digits); + }, + spec._align, width, spec._fill, neg); + + return w; + } + + /// Write out the @a data as hexadecimal, using @a digits as the conversion. + void + Hex_Dump(BufferWriter &w, std::string_view data, const char *digits) + { + const char *ptr = data.data(); + for (auto n = data.size(); n > 0; --n) { + char c = *ptr++; + w.write(digits[(c >> 4) & 0xF]); + w.write(digits[c & 0xf]); + } + } + + /// Preparse format string for later use. + Format::Format(TextView fmt) + { + Spec lit_spec; + int arg_idx = 0; + auto ex{bind(fmt)}; + std::string_view literal_v; + + lit_spec._type = Spec::LITERAL_TYPE; + + while (ex) { + Spec spec; + bool spec_p = ex(literal_v, spec); + + if (literal_v.size()) { + lit_spec._ext = literal_v; + _items.emplace_back(lit_spec); + } + + if (spec_p) { + if (spec._name.size() == 0) { // no name provided, use implicit index. + spec._idx = arg_idx++; + } + if (spec._idx >= 0) { + ++arg_idx; + } + _items.emplace_back(spec); + } + } + } + + BoundNames::~BoundNames() {} +} // namespace bwf + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv) +{ + int width = static_cast(spec._min); // amount left to fill. + if (spec._prec > 0) { + sv = sv.substr(0, spec._prec); + } + + if ('x' == spec._type || 'X' == spec._type) { + const char *digits = 'x' == spec._type ? bwf::LOWER_DIGITS : bwf::UPPER_DIGITS; + width -= sv.size() * 2; + if (spec._radix_lead_p) { + w.write('0'); + w.write(spec._type); + width -= 2; + } + bwf::Write_Aligned(w, [&w, &sv, digits]() { bwf::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0); + } else { + width -= sv.size(); + bwf::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); + } + return w; +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan const &span) +{ + static const bwf::Format default_fmt{"{:#x}@{:p}"}; + if (spec._ext.size() && 'd' == spec._ext.front()) { + const char *digits = 'X' == spec._type ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS; + if (spec._radix_lead_p) { + w.write('0'); + w.write(digits[33]); + } + bwf::Hex_Dump(w, span.view(), digits); + } else { + w.print(default_fmt, span.size(), span.data()); + } + return w; +} + +std::ostream & +FixedBufferWriter::operator>>(std::ostream &s) const +{ + return s << this->view(); +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) +{ + // Hand rolled, might not be totally compliant everywhere, but probably close + // enough. The long string will be locally accurate. Clang requires the double + // braces. Why, Turing only knows. + static const std::array SHORT_NAME = {{ + "SUCCESS: ", + "EPERM: ", + "ENOENT: ", + "ESRCH: ", + "EINTR: ", + "EIO: ", + "ENXIO: ", + "E2BIG ", + "ENOEXEC: ", + "EBADF: ", + "ECHILD: ", + "EAGAIN: ", + "ENOMEM: ", + "EACCES: ", + "EFAULT: ", + "ENOTBLK: ", + "EBUSY: ", + "EEXIST: ", + "EXDEV: ", + "ENODEV: ", + "ENOTDIR: ", + "EISDIR: ", + "EINVAL: ", + "ENFILE: ", + "EMFILE: ", + "ENOTTY: ", + "ETXTBSY: ", + "EFBIG: ", + "ENOSPC: ", + "ESPIPE: ", + "EROFS: ", + "EMLINK: ", + "EPIPE: ", + "EDOM: ", + "ERANGE: ", + "EDEADLK: ", + "ENAMETOOLONG: ", + "ENOLCK: ", + "ENOSYS: ", + "ENOTEMPTY: ", + "ELOOP: ", + "EWOULDBLOCK: ", + "ENOMSG: ", + "EIDRM: ", + "ECHRNG: ", + "EL2NSYNC: ", + "EL3HLT: ", + "EL3RST: ", + "ELNRNG: ", + "EUNATCH: ", + "ENOCSI: ", + "EL2HTL: ", + "EBADE: ", + "EBADR: ", + "EXFULL: ", + "ENOANO: ", + "EBADRQC: ", + "EBADSLT: ", + "EDEADLOCK: ", + "EBFONT: ", + "ENOSTR: ", + "ENODATA: ", + "ETIME: ", + "ENOSR: ", + "ENONET: ", + "ENOPKG: ", + "EREMOTE: ", + "ENOLINK: ", + "EADV: ", + "ESRMNT: ", + "ECOMM: ", + "EPROTO: ", + "EMULTIHOP: ", + "EDOTDOT: ", + "EBADMSG: ", + "EOVERFLOW: ", + "ENOTUNIQ: ", + "EBADFD: ", + "EREMCHG: ", + "ELIBACC: ", + "ELIBBAD: ", + "ELIBSCN: ", + "ELIBMAX: ", + "ELIBEXEC: ", + "EILSEQ: ", + "ERESTART: ", + "ESTRPIPE: ", + "EUSERS: ", + "ENOTSOCK: ", + "EDESTADDRREQ: ", + "EMSGSIZE: ", + "EPROTOTYPE: ", + "ENOPROTOOPT: ", + "EPROTONOSUPPORT: ", + "ESOCKTNOSUPPORT: ", + "EOPNOTSUPP: ", + "EPFNOSUPPORT: ", + "EAFNOSUPPORT: ", + "EADDRINUSE: ", + "EADDRNOTAVAIL: ", + "ENETDOWN: ", + "ENETUNREACH: ", + "ENETRESET: ", + "ECONNABORTED: ", + "ECONNRESET: ", + "ENOBUFS: ", + "EISCONN: ", + "ENOTCONN: ", + "ESHUTDOWN: ", + "ETOOMANYREFS: ", + "ETIMEDOUT: ", + "ECONNREFUSED: ", + "EHOSTDOWN: ", + "EHOSTUNREACH: ", + "EALREADY: ", + "EINPROGRESS: ", + "ESTALE: ", + "EUCLEAN: ", + "ENOTNAM: ", + "ENAVAIL: ", + "EISNAM: ", + "EREMOTEIO: ", + "EDQUOT: ", + "ENOMEDIUM: ", + "EMEDIUMTYPE: ", + "ECANCELED: ", + "ENOKEY: ", + "EKEYEXPIRED: ", + "EKEYREVOKED: ", + "EKEYREJECTED: ", + "EOWNERDEAD: ", + "ENOTRECOVERABLE: ", + "ERFKILL: ", + "EHWPOISON: ", + }}; + // This provides convenient safe access to the errno short name array. + auto short_name = [](int n) { return n < int(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; + static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format. + if (spec.has_numeric_type()) { // if numeric type, print just the numeric + // part. + w.print(number_fmt, e._e); + } else { + w.write(short_name(e._e)); + w.write(strerror(e._e)); + if (spec._type != 's' && spec._type != 'S') { + w.write(' '); + w.print(number_fmt, e._e); + } + } + return w; +} + +bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date) +{ + if (spec.has_numeric_type()) { + bwformat(w, spec, date._epoch); + } else { + struct tm t; + auto r = w.remaining(); + size_t n{0}; + // Verify @a fmt is null terminated, even outside the bounds of the view. + if (date._fmt.data()[date._fmt.size() - 1] != 0 && date._fmt.data()[date._fmt.size()] != 0) { + throw(std::invalid_argument{"BWF Date String is not null terminated."}); + } + // Get the time, GMT or local if specified. + if (spec._ext == "local"sv) { + localtime_r(&date._epoch, &t); + } else { + gmtime_r(&date._epoch, &t); + } + // Try a direct write, faster if it works. + if (r > 0) { + n = strftime(w.aux_data(), r, date._fmt.data(), &t); + } + if (n > 0) { + w.commit(n); + } else { + // Direct write didn't work. Unfortunately need to write to a temporary + // buffer or the sizing isn't correct if @a w is clipped because @c + // strftime returns 0 if the buffer isn't large enough. + char buff[256]; // hope for the best - no real way to resize appropriately on failure. + n = strftime(buff, sizeof(buff), date._fmt.data(), &t); + w.write(buff, n); + } + } + return w; +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::OptionalAffix const &opts) +{ + return w.write(opts._prefix).write(opts._text).write(opts._suffix); +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern) +{ + auto limit = std::min(spec._max, pattern._text.size() * pattern._n); + decltype(limit) n = 0; + while (n < limit) { + w.write(pattern._text); + n += pattern._text.size(); + } + return w; +} + +} // namespace ts + +namespace std +{ +ostream & +operator<<(ostream &s, ts::FixedBufferWriter &w) +{ + return s << w.view(); +} +} // namespace std + +// --- C_Format / printf support ---- + +namespace ts +{ +namespace bwf +{ + void + C_Format::capture(BufferWriter &w, Spec const &spec, std::any const &value) + { + unsigned v; + if (typeid(int *) == value.type()) + v = static_cast(*std::any_cast(value)); + else if (typeid(unsigned *) == value.type()) + v = *std::any_cast(value); + else if (typeid(size_t *) == value.type()) + v = static_cast(*std::any_cast(value)); + else + return; + + if (spec._ext == "w") + _saved._min = v; + if (spec._ext == "p") { + _saved._prec = v; + } + } + + bool + C_Format::operator()(std::string_view &literal, Spec &spec) + { + TextView parsed; + + // clean up any old business from a previous specifier. + if (_prec_p) { + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "p"; + _prec_p = false; + return true; + } else if (_saved_p) { + spec = _saved; + _saved_p = false; + return true; + } + + if (!_fmt.empty()) { + bool width_p = false; + literal = _fmt.take_prefix_at('%'); + if (_fmt.empty()) { + return false; + } + if (!_fmt.empty()) { + if ('%' == *_fmt) { + literal = {literal.data(), literal.size() + 1}; + ++_fmt; + return false; + } + } + + spec._align = Spec::Align::RIGHT; // default unless overridden. + do { + char c = *_fmt; + if ('-' == c) { + spec._align = Spec::Align::LEFT; + } else if ('+' == c) { + spec._sign = Spec::SIGN_ALWAYS; + } else if (' ' == c) { + spec._sign = Spec::SIGN_NEVER; + } else if ('#' == c) { + spec._radix_lead_p = true; + } else if ('0' == c) { + spec._fill = '0'; + } else { + break; + } + ++_fmt; + } while (!_fmt.empty()); + + if (_fmt.empty()) { + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + + if ('*' == *_fmt) { + width_p = true; // signal need to capture width. + ++_fmt; + } else { + auto width = radix10(_fmt, parsed); + if (!parsed.empty()) { + spec._min = width; + } + } + + if ('.' == *_fmt) { + ++_fmt; + if ('*' == *_fmt) { + _prec_p = true; + ++_fmt; + } else { + auto x = radix10(_fmt, parsed); + if (!parsed.empty()) { + spec._prec = x; + } else { + spec._prec = 0; + } + } + } + + if (_fmt.empty()) { + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + + char c = *_fmt++; + // strip length modifiers. + if ('l' == c || 'h' == c) + c = *_fmt++; + if ('l' == c || 'z' == c || 'j' == c || 't' == c || 'h' == c) + c = *_fmt++; + + switch (c) { + case 'c': + spec._type = c; + break; + case 'i': + case 'd': + case 'j': + case 'z': + spec._type = 'd'; + break; + case 'x': + case 'X': + spec._type = c; + break; + case 'f': + spec._type = 'f'; + break; + case 's': + spec._type = 's'; + break; + case 'p': + spec._type = c; + break; + default: + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + if (width_p || _prec_p) { + _saved_p = true; + _saved = spec; + spec = Spec::DEFAULT; + if (width_p) { + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "w"; + } else if (_prec_p) { + _prec_p = false; + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "p"; + } + } + return true; + } + return false; + } + +} // namespace bwf +} // namespace ts diff --git a/src/tscore/unit_tests/test_BufferWriter.cc b/src/tscpp/util/unit_tests/test_BufferWriter.cc similarity index 99% rename from src/tscore/unit_tests/test_BufferWriter.cc rename to src/tscpp/util/unit_tests/test_BufferWriter.cc index 52678b9aad6..c1d900b617d 100644 --- a/src/tscore/unit_tests/test_BufferWriter.cc +++ b/src/tscpp/util/unit_tests/test_BufferWriter.cc @@ -22,7 +22,7 @@ */ #include "catch.hpp" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include namespace diff --git a/src/tscore/unit_tests/test_Scalar.cc b/src/tscpp/util/unit_tests/test_Scalar.cc similarity index 72% rename from src/tscore/unit_tests/test_Scalar.cc rename to src/tscpp/util/unit_tests/test_Scalar.cc index 3f8ac69ba9b..7de5f5bdd6e 100644 --- a/src/tscore/unit_tests/test_Scalar.cc +++ b/src/tscpp/util/unit_tests/test_Scalar.cc @@ -4,28 +4,23 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include -#include "tscore/Scalar.h" -//#include -//#include -//#include +#include "tscpp/util/Scalar.h" +#include "tscpp/util/bwf_base.h" +#include "catch.hpp" using Bytes = ts::Scalar<1, off_t>; using Paragraphs = ts::Scalar<16, off_t>; @@ -228,58 +223,21 @@ TEST_CASE("Scalar Arithmetic", "[libts][Scalar][arithmetic]") REQUIRE(kb >= b); } -#if 0 struct KBytes_tag { - static std::string const label; + static constexpr std::string_view label{" bytes"}; }; -std::string const KBytes_tag::label(" bytes"); -void -Test_IO() +TEST_CASE("Scalar Formatting", "[libts][Scalar][bwf]") { - typedef ts::Scalar<1024, long int, KBytes_tag> KBytes; - typedef ts::Scalar<1024, int> KiBytes; + using KBytes = ts::Scalar<1024, long int, KBytes_tag>; + using KiBytes = ts::Scalar<1000, int>; KBytes x(12); KiBytes y(12); + ts::LocalBufferWriter<128> w; - std::cout << "Testing" << std::endl; - std::cout << "x is " << x << std::endl; - std::cout << "y is " << y << std::endl; + w.print("x is {}", x); + REQUIRE(w.view() == "x is 12288 bytes"); + w.clear().print("y is {}", y); + REQUIRE(w.view() == "y is 12000"); } - -void -test_Compile() -{ - // These tests aren't normally run, they exist to detect compiler issues. - - typedef ts::Scalar<1024, short> KBytes; - typedef ts::Scalar<1024, int> KiBytes; - int delta = 10; - - KBytes x(12); - KiBytes y(12); - - if (x > 12) { - std::cout << "Operator > works" << std::endl; - } - if (y > 12) { - std::cout << "Operator > works" << std::endl; - } - - (void)(x.inc(10)); - (void)(x.inc(static_cast(10))); - (void)(x.inc(static_cast(10))); - (void)(x.inc(delta)); - (void)(y.inc(10)); - (void)(y.inc(static_cast(10))); - (void)(y.inc(static_cast(10))); - (void)(y.inc(delta)); - - (void)(x.dec(10)); - (void)(x.dec(static_cast(10))); - (void)(x.dec(static_cast(10))); - (void)(x.dec(delta)); -} - -#endif diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscpp/util/unit_tests/test_bw_format.cc similarity index 59% rename from src/tscore/unit_tests/test_BufferWriterFormat.cc rename to src/tscpp/util/unit_tests/test_bw_format.cc index e8dee671a08..9a4b16b5eef 100644 --- a/src/tscore/unit_tests/test_BufferWriterFormat.cc +++ b/src/tscpp/util/unit_tests/test_bw_format.cc @@ -4,34 +4,34 @@ @section license License - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include "catch.hpp" -#include "../../../tests/include/catch.hpp" #include #include -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include + #include "tscpp/util/MemSpan.h" -#include "tscore/INK_MD5.h" -#include "tscore/CryptoHash.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_std.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/bwf_printf.h" + +#include "catch.hpp" using namespace std::literals; +using ts::TextView; TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]") { @@ -41,7 +41,7 @@ TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]") REQUIRE(bw.view() == "The quick brown fox"); - bw.reduce(0); + bw.clear(); bw << "x=" << bw.capacity(); REQUIRE(bw.view() == "x=50"); } @@ -53,187 +53,188 @@ TEST_CASE("bwprint basics", "[bwprint]") bw.print(fmt1); REQUIRE(bw.view() == fmt1); - bw.reduce(0); + bw.clear(); bw.print("Arg {}", 1); REQUIRE(bw.view() == "Arg 1"); - bw.reduce(0); + bw.clear(); bw.print("arg 1 {1} and 2 {2} and 0 {0}", "zero", "one", "two"); REQUIRE(bw.view() == "arg 1 one and 2 two and 0 zero"); - bw.reduce(0); + bw.clear(); bw.print("args {2}{0}{1}", "zero", "one", "two"); REQUIRE(bw.view() == "args twozeroone"); - bw.reduce(0); + bw.clear(); bw.print("left |{:<10}|", "text"); REQUIRE(bw.view() == "left |text |"); - bw.reduce(0); + bw.clear(); bw.print("right |{:>10}|", "text"); REQUIRE(bw.view() == "right | text|"); - bw.reduce(0); + bw.clear(); bw.print("right |{:.>10}|", "text"); REQUIRE(bw.view() == "right |......text|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:.^10}|", "text"); REQUIRE(bw.view() == "center |...text...|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:.^11}|", "text"); REQUIRE(bw.view() == "center |...text....|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:^^10}|", "text"); REQUIRE(bw.view() == "center |^^^text^^^|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:%3A^10}|", "text"); REQUIRE(bw.view() == "center |:::text:::|"); - bw.reduce(0); + bw.clear(); bw.print("left >{0:<9}< right >{0:>9}< center >{0:^9}<", 956); REQUIRE(bw.view() == "left >956 < right > 956< center > 956 <"); - bw.reduce(0); + bw.clear(); bw.print("Format |{:>#010x}|", -956); REQUIRE(bw.view() == "Format |0000-0x3bc|"); - bw.reduce(0); + bw.clear(); bw.print("Format |{:<#010x}|", -956); REQUIRE(bw.view() == "Format |-0x3bc0000|"); - bw.reduce(0); + bw.clear(); bw.print("Format |{:#010x}|", -956); REQUIRE(bw.view() == "Format |-0x00003bc|"); - bw.reduce(0); + bw.clear(); bw.print("{{BAD_ARG_INDEX:{} of {}}}", 17, 23); REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}"); - bw.reduce(0); - bw.print("Arg {0} Arg {3}", 1, 2); - REQUIRE(bw.view() == "Arg 1 Arg {BAD_ARG_INDEX:3 of 2}"); + bw.clear(); + bw.print("Arg {0} Arg {3}", 0, 1); + REQUIRE(bw.view() == "Arg 0 Arg {BAD_ARG_INDEX:3 of 2}"); - bw.reduce(0); - bw.print("{{stuff}} Arg {0} Arg {}", 1, 2); - REQUIRE(bw.view() == "{stuff} Arg 1 Arg 2"); - bw.reduce(0); + bw.clear().print("{{stuff}} Arg {0} Arg {}", 0, 1, 2); + REQUIRE(bw.view() == "{stuff} Arg 0 Arg 0"); + bw.clear().print("{{stuff}} Arg {0} Arg {} {}", 0, 1, 2); + REQUIRE(bw.view() == "{stuff} Arg 0 Arg 0 1"); + bw.clear(); bw.print("Arg {0} Arg {} and {{stuff}}", 3, 4); - REQUIRE(bw.view() == "Arg 3 Arg 4 and {stuff}"); - bw.reduce(0); - bw.print("Arg {{{0}}} Arg {} and {{stuff}}", 5, 6); - REQUIRE(bw.view() == "Arg {5} Arg 6 and {stuff}"); - bw.reduce(0); - bw.print("Arg {0} Arg {{}}{{}} {} and {{stuff}}", 7, 8); - REQUIRE(bw.view() == "Arg 7 Arg {}{} 8 and {stuff}"); - bw.reduce(0); - bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10); + REQUIRE(bw.view() == "Arg 3 Arg 3 and {stuff}"); + bw.clear().print("Arg {{{0}}} Arg {} and {{stuff}}", 5, 6); + REQUIRE(bw.view() == "Arg {5} Arg 5 and {stuff}"); + bw.clear().print("Arg {{{0}}} Arg {} {1} {} {0} and {{stuff}}", 5, 6); + REQUIRE(bw.view() == "Arg {5} Arg 5 6 6 5 and {stuff}"); + bw.clear(); + bw.print("Arg {0} Arg {{}}{{}} {} and {} {{stuff}}", 7, 8); + REQUIRE(bw.view() == "Arg 7 Arg {}{} 7 and 8 {stuff}"); + bw.clear(); + bw.print("Arg {} Arg {{{{}}}} {} {1} {0}", 9, 10); + REQUIRE(bw.view() == "Arg 9 Arg {{}} 10 10 9"); + + bw.clear(); + bw.print("Arg {} Arg {{{{}}}} {}", 9, 10); REQUIRE(bw.view() == "Arg 9 Arg {{}} 10"); + bw.clear(); - bw.reduce(0); - bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10); - REQUIRE(bw.view() == "Arg 9 Arg {{}} 10"); - bw.reduce(0); - - bw.reset().print("{leif}"); + bw.clear().print("{leif}"); REQUIRE(bw.view() == "{~leif~}"); // expected to be missing. - bw.reset().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}\n", 31267); - // std::cout << bw; - /* - std::cout << ts::LocalBufferWriter<256>().print( - "Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}{}", 31267, '\n'); - */ + // bw.clear().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} !{0}", 31267); + // REQUIRE(ts::TextView(bw.view()).take_suffix_at('!') == "31267"); } TEST_CASE("BWFormat numerics", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<"); + ts::bwf::Format fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<"); std::string_view text{"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"}; - bw.reduce(0); - static const ts::BWFormat bad_arg_fmt{"{{BAD_ARG_INDEX:{} of {}}}"}; + bw.clear(); + static const ts::bwf::Format bad_arg_fmt{"{{BAD_ARG_INDEX:{} of {}}}"}; bw.print(bad_arg_fmt, 17, 23); REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}"); - bw.reduce(0); + bw.clear(); bw.print(fmt, 956); REQUIRE(bw.view() == "left >956 < right > 956< center > 956 <"); - bw.reduce(0); - bw.print("Text: _{0:.10,20}_", text); - REQUIRE(bw.view() == "Text: _abcdefghijklmnopqrst_"); - bw.reduce(0); - bw.print("Text: _{0:-<20.52,20}_", text); + bw.clear().print("Text: _{0:20.10}_", text); + REQUIRE(bw.view() == "Text: _0123456789 _"); + bw.clear().print("Text: _{0:>20.10}_", text); + REQUIRE(bw.view() == "Text: _ 0123456789_"); + bw.clear().print("Text: _{0:-<20.10,20}_", text.substr(52)); REQUIRE(bw.view() == "Text: _QRSTUVWXYZ----------_"); void *ptr = reinterpret_cast(0XBADD0956); - bw.reduce(0); + bw.clear(); bw.print("{}", ptr); REQUIRE(bw.view() == "0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{:X}", ptr); REQUIRE(bw.view() == "0XBADD0956"); int *int_ptr = static_cast(ptr); - bw.reduce(0); + bw.clear(); bw.print("{}", int_ptr); REQUIRE(bw.view() == "0xbadd0956"); auto char_ptr = "good"; - bw.reduce(0); + bw.clear(); bw.print("{:x}", static_cast(ptr)); REQUIRE(bw.view() == "0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{}", char_ptr); REQUIRE(bw.view() == "good"); ts::MemSpan span{ptr, 0x200}; - bw.reduce(0); + bw.clear(); bw.print("{}", span); REQUIRE(bw.view() == "0x200@0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{::d}", ts::MemSpan(const_cast(char_ptr), 4)); REQUIRE(bw.view() == "676f6f64"); - bw.reduce(0); + bw.clear(); bw.print("{:#:d}", ts::MemSpan(const_cast(char_ptr), 4)); REQUIRE(bw.view() == "0x676f6f64"); std::string_view sv{"abc123"}; - bw.reduce(0); + bw.clear(); bw.print("{}", sv); REQUIRE(bw.view() == sv); - bw.reduce(0); + bw.clear(); bw.print("{:x}", sv); REQUIRE(bw.view() == "616263313233"); - bw.reduce(0); + bw.clear(); bw.print("{:#x}", sv); REQUIRE(bw.view() == "0x616263313233"); - bw.reduce(0); + bw.clear(); bw.print("|{:16x}|", sv); REQUIRE(bw.view() == "|616263313233 |"); - bw.reduce(0); + bw.clear(); bw.print("|{:>16x}|", sv); REQUIRE(bw.view() == "| 616263313233|"); - bw.reduce(0); + bw.clear(); bw.print("|{:^16x}|", sv); REQUIRE(bw.view() == "| 616263313233 |"); - bw.reduce(0); + bw.clear(); bw.print("|{:>16.2x}|", sv); - REQUIRE(bw.view() == "| 63313233|"); - bw.reduce(0); - bw.print("|{:<0.2,5x}|", sv); - REQUIRE(bw.view() == "|63313|"); - bw.reset().print("|{:<.2,5x}|", sv); - REQUIRE(bw.view() == "|63313|"); - - bw.reduce(0); + REQUIRE(bw.view() == "| 6162|"); + bw.clear().print("|{:<0.4,7x}|", sv); + REQUIRE(bw.view() == "|6162633|"); + bw.clear().print("|{:<5.2,7x}|", sv); + REQUIRE(bw.view() == "|6162 |"); + bw.clear().print("|{:<5.3,7x}|", sv); + REQUIRE(bw.view() == "|616263|"); + bw.clear().print("|{:<7.3x}|", sv); + REQUIRE(bw.view() == "|616263 |"); + + bw.clear(); bw.print("|{}|", true); REQUIRE(bw.view() == "|1|"); - bw.reduce(0); + bw.clear(); bw.print("|{}|", false); REQUIRE(bw.view() == "|0|"); - bw.reduce(0); + bw.clear(); bw.print("|{:s}|", true); REQUIRE(bw.view() == "|true|"); - bw.reduce(0); + bw.clear(); bw.print("|{:S}|", false); REQUIRE(bw.view() == "|FALSE|"); - bw.reduce(0); + bw.clear(); bw.print("|{:>9s}|", false); REQUIRE(bw.view() == "| false|"); - bw.reduce(0); + bw.clear(); bw.print("|{:^10s}|", true); REQUIRE(bw.view() == "| true |"); @@ -241,22 +242,13 @@ TEST_CASE("BWFormat numerics", "[bwprint][bwformat]") ts::LocalBufferWriter<20> bw20; bw20.print("0123456789abc|{:^10s}|", true); REQUIRE(bw20.view() == "0123456789abc| tru"); - bw20.reduce(0); + bw20.clear(); bw20.print("012345|{:^10s}|6789abc", true); REQUIRE(bw20.view() == "012345| true |67"); - INK_MD5 md5; - bw.reduce(0); - bw.print("{}", md5); - REQUIRE(bw.view() == "00000000000000000000000000000000"); - CryptoContext().hash_immediate(md5, sv.data(), sv.size()); - bw.reduce(0); - bw.print("{}", md5); - REQUIRE(bw.view() == "e99a18c428cb38d5f260853678922e03"); - - bw.reset().print("Char '{}'", 'a'); + bw.clear().print("Char '{}'", 'a'); REQUIRE(bw.view() == "Char 'a'"); - bw.reset().print("Byte '{}'", uint8_t{'a'}); + bw.clear().print("Byte '{}'", uint8_t{'a'}); REQUIRE(bw.view() == "Byte '97'"); } @@ -280,7 +272,7 @@ TEST_CASE("bwstring", "[bwprint][bwstring]") char buff[128]; snprintf(buff, sizeof(buff), "|%s|", bw.print("Deep Silent Complete by {}\0", "Nightwish"sv).data()); REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|"); - snprintf(buff, sizeof(buff), "|%s|", bw.reset().print("Deep Silent Complete by {}\0elided junk", "Nightwish"sv).data()); + snprintf(buff, sizeof(buff), "|%s|", bw.clear().print("Deep Silent Complete by {}\0elided junk", "Nightwish"sv).data()); REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|"); // Special tests for clang analyzer failures - special asserts are needed to make it happy but @@ -311,51 +303,51 @@ TEST_CASE("bwstring", "[bwprint][bwstring]") TEST_CASE("BWFormat integral", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; uint32_t num = 30; int num_neg = -30; // basic bwformat(bw, spec, num); REQUIRE(bw.view() == "30"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, num_neg); REQUIRE(bw.view() == "-30"); - bw.reduce(0); + bw.clear(); // radix - ts::BWFSpec spec_hex; + ts::bwf::Spec spec_hex; spec_hex._radix_lead_p = true; spec_hex._type = 'x'; bwformat(bw, spec_hex, num); REQUIRE(bw.view() == "0x1e"); - bw.reduce(0); + bw.clear(); - ts::BWFSpec spec_dec; + ts::bwf::Spec spec_dec; spec_dec._type = '0'; bwformat(bw, spec_dec, num); REQUIRE(bw.view() == "30"); - bw.reduce(0); + bw.clear(); - ts::BWFSpec spec_bin; + ts::bwf::Spec spec_bin; spec_bin._radix_lead_p = true; spec_bin._type = 'b'; bwformat(bw, spec_bin, num); REQUIRE(bw.view() == "0b11110"); - bw.reduce(0); + bw.clear(); int one = 1; int two = 2; int three_n = -3; // alignment - ts::BWFSpec left; - left._align = ts::BWFSpec::Align::LEFT; + ts::bwf::Spec left; + left._align = ts::bwf::Spec::Align::LEFT; left._min = 5; - ts::BWFSpec right; - right._align = ts::BWFSpec::Align::RIGHT; + ts::bwf::Spec right; + right._align = ts::bwf::Spec::Align::RIGHT; right._min = 5; - ts::BWFSpec center; - center._align = ts::BWFSpec::Align::CENTER; + ts::bwf::Spec center; + center._align = ts::bwf::Spec::Align::CENTER; center._min = 5; bwformat(bw, left, one); @@ -367,105 +359,105 @@ TEST_CASE("BWFormat integral", "[bwprint][bwformat]") REQUIRE(bw.view() == "1 2 2 -3 "); std::atomic ax{0}; - bw.reset().print("ax == {}", ax); + bw.clear().print("ax == {}", ax); REQUIRE(bw.view() == "ax == 0"); ++ax; - bw.reset().print("ax == {}", ax); + bw.clear().print("ax == {}", ax); REQUIRE(bw.view() == "ax == 1"); } TEST_CASE("BWFormat floating", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; - bw.reduce(0); + bw.clear(); bw.print("{}", 3.14); REQUIRE(bw.view() == "3.14"); - bw.reduce(0); + bw.clear(); bw.print("{} {:.2} {:.0} ", 32.7, 32.7, 32.7); REQUIRE(bw.view() == "32.70 32.70 32 "); - bw.reduce(0); + bw.clear(); bw.print("{} neg {:.3}", -123.2, -123.2); REQUIRE(bw.view() == "-123.20 neg -123.200"); - bw.reduce(0); + bw.clear(); bw.print("zero {} quarter {} half {} 3/4 {}", 0, 0.25, 0.50, 0.75); REQUIRE(bw.view() == "zero 0 quarter 0.25 half 0.50 3/4 0.75"); - bw.reduce(0); + bw.clear(); bw.print("long {:.11}", 64.9); REQUIRE(bw.view() == "long 64.90000000000"); - bw.reduce(0); + bw.clear(); double n = 180.278; double neg = -238.47; bwformat(bw, spec, n); REQUIRE(bw.view() == "180.28"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, neg); REQUIRE(bw.view() == "-238.47"); - bw.reduce(0); + bw.clear(); spec._prec = 5; bwformat(bw, spec, n); REQUIRE(bw.view() == "180.27800"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, neg); REQUIRE(bw.view() == "-238.47000"); - bw.reduce(0); + bw.clear(); float f = 1234; float fneg = -1; bwformat(bw, spec, f); REQUIRE(bw.view() == "1234"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, fneg); REQUIRE(bw.view() == "-1"); - bw.reduce(0); + bw.clear(); f = 1234.5667; spec._prec = 4; bwformat(bw, spec, f); REQUIRE(bw.view() == "1234.5667"); - bw.reduce(0); + bw.clear(); bw << 1234 << .567; REQUIRE(bw.view() == "12340.57"); - bw.reduce(0); + bw.clear(); bw << f; REQUIRE(bw.view() == "1234.57"); - bw.reduce(0); + bw.clear(); bw << n; REQUIRE(bw.view() == "180.28"); - bw.reduce(0); + bw.clear(); bw << f << n; REQUIRE(bw.view() == "1234.57180.28"); - bw.reduce(0); + bw.clear(); double edge = 0.345; spec._prec = 3; bwformat(bw, spec, edge); REQUIRE(bw.view() == "0.345"); - bw.reduce(0); + bw.clear(); edge = .1234; bwformat(bw, spec, edge); REQUIRE(bw.view() == "0.123"); - bw.reduce(0); + bw.clear(); edge = 1.0; bwformat(bw, spec, edge); REQUIRE(bw.view() == "1"); - bw.reduce(0); + bw.clear(); // alignment double first = 1.23; double second = 2.35; double third = -3.5; - ts::BWFSpec left; - left._align = ts::BWFSpec::Align::LEFT; + ts::bwf::Spec left; + left._align = ts::bwf::Spec::Align::LEFT; left._min = 5; - ts::BWFSpec right; - right._align = ts::BWFSpec::Align::RIGHT; + ts::bwf::Spec right; + right._align = ts::bwf::Spec::Align::RIGHT; right._min = 5; - ts::BWFSpec center; - center._align = ts::BWFSpec::Align::CENTER; + ts::bwf::Spec center; + center._align = ts::bwf::Spec::Align::CENTER; center._min = 5; bwformat(bw, left, first); @@ -475,99 +467,209 @@ TEST_CASE("BWFormat floating", "[bwprint][bwformat]") REQUIRE(bw.view() == "1.23 2.35 2.35"); bwformat(bw, center, third); REQUIRE(bw.view() == "1.23 2.35 2.35-3.50"); - bw.reduce(0); + bw.clear(); double over = 1.4444444; - ts::BWFSpec over_min; + ts::bwf::Spec over_min; over_min._prec = 7; over_min._min = 5; bwformat(bw, over_min, over); REQUIRE(bw.view() == "1.4444444"); - bw.reduce(0); + bw.clear(); // Edge bw.print("{}", (1.0 / 0.0)); REQUIRE(bw.view() == "Inf"); - bw.reduce(0); + bw.clear(); double inf = std::numeric_limits::infinity(); bw.print(" {} ", inf); REQUIRE(bw.view() == " Inf "); - bw.reduce(0); + bw.clear(); double nan_1 = std::nan("1"); bw.print("{} {}", nan_1, nan_1); REQUIRE(bw.view() == "NaN NaN"); - bw.reduce(0); + bw.clear(); double z = 0.0; bw.print("{} ", z); REQUIRE(bw.view() == "0 "); - bw.reduce(0); + bw.clear(); } -TEST_CASE("bwstring std formats", "[libts][bwprint]") +TEST_CASE("bwstring std formats", "[[libtscpputil]][bwprint]") { ts::LocalBufferWriter<120> w; w.print("{}", ts::bwf::Errno(13)); REQUIRE(w.view() == "EACCES: Permission denied [13]"sv); - w.reset().print("{}", ts::bwf::Errno(134)); + w.clear().print("{}", ts::bwf::Errno(134)); REQUIRE(w.view().substr(0, 22) == "Unknown: Unknown error"sv); time_t t = 1528484137; // default is GMT - w.reset().print("{} is {}", t, ts::bwf::Date(t)); + w.clear().print("{} is {}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37"); - w.reset().print("{} is {}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37"); // OK to be explicit - w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t)); + w.clear().print("{} is {::gmt}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37"); - w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {::gmt}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37"); // Local time - set it to something specific or the test will be geographically sensitive. setenv("TZ", "CST6", 1); tzset(); - w.reset().print("{} is {::local}", t, ts::bwf::Date(t)); + w.clear().print("{} is {::local}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 12:55:37"); - w.reset().print("{} is {::local}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {::local}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 12.55.37"); // Verify these compile and run, not really much hope to check output. - w.reset().print("|{}| |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y")); + w.clear().print("|{}| |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y")); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("", "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf("", "Evil Dave")); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave")); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif")); REQUIRE(w.view() == "name = Leif"); const char *empty{nullptr}; std::string s1{"Persia"}; std::string_view s2{"Evil Dave"}; ts::TextView s3{"Leif"}; - w.reset().print("name = {}", ts::bwf::FirstOf(empty, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, s3)); REQUIRE(w.view() == "name = Leif"); - w.reset().print("name = {}", ts::bwf::FirstOf(s2, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(s2, s3)); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(s1, empty, s2)); + w.clear().print("name = {}", ts::bwf::FirstOf(s1, empty, s2)); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3)); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1)); REQUIRE(w.view() == "name = Leif"); } +// Test alternate format parsing. +struct AltFormatEx { + AltFormatEx(TextView fmt); + + explicit operator bool() const; + bool operator()(std::string_view &literal, ts::bwf::Spec &spec); + TextView _fmt; +}; + +AltFormatEx::AltFormatEx(TextView fmt) : _fmt{fmt} {} + +AltFormatEx::operator bool() const +{ + return !_fmt.empty(); +} + +bool +AltFormatEx::operator()(std::string_view &literal, ts::bwf::Spec &spec) +{ + if (_fmt.size()) { + literal = _fmt.split_prefix_at('%'); + if (literal.empty()) { + literal = _fmt; + _fmt.clear(); + return false; + } + + if (_fmt.size() >= 1) { + char c = _fmt[0]; + if (c == '%') { + literal = {literal.data(), literal.size() + 1}; + ++_fmt; + } else if (c == '<') { + size_t off = 0; + do { + off = _fmt.find('>', off + 1); + if (off == TextView::npos) { + throw std::invalid_argument("Unclosed leading angle bracket"); + } + } while (':' == _fmt[off - 1]); + spec.parse(_fmt.substr(1, off - 1)); + if (spec._name.empty()) { + throw std::invalid_argument("No name in specifier"); + } + _fmt.remove_prefix(off + 1); + return true; + } + } + } + return false; +} + +struct Header { + TextView + proto() const + { + return "ipv4"; + } + TextView + chi() const + { + return "10.56.128.96"; + } +}; + +using AltNames = ts::bwf::ContextNames
; + +TEST_CASE("bwf alternate", "[[libtscpputil]][bwf]") +{ + using BW = ts::BufferWriter; + using Spec = ts::bwf::Spec; + AltNames names; + Header hdr; + names.assign("proto", [](BW &w, Spec const &spec, Header &hdr) -> BW & { return ts::bwformat(w, spec, hdr.proto()); }); + names.assign("chi", [](BW &w, Spec const &spec, Header &hdr) -> BW & { return ts::bwformat(w, spec, hdr.chi()); }); + + ts::LocalBufferWriter<256> w; + w.print_nv(names.bind(hdr), AltFormatEx("This is chi - %")); + REQUIRE(w.view() == "This is chi - 10.56.128.96"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Use %% for a single")); + REQUIRE(w.view() == "Use % for a single"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Use %% for %, dig?")); + REQUIRE(w.view() == "Use % for ipv4, dig?"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Width |%| dig?")); + REQUIRE(w.view() == "Width |ipv4 | dig?"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Width |%10>| dig?")); + REQUIRE(w.view() == "Width | ipv4| dig?"); + + ts::bwprintf(w.clear(), "Fifty Six = %d", 56); + REQUIRE(w.view() == "Fifty Six = 56"); + ts::bwprintf(w.clear(), "int is %i", 101); + REQUIRE(w.view() == "int is 101"); + ts::bwprintf(w.clear(), "int is %zd", 102); + REQUIRE(w.view() == "int is 102"); + ts::bwprintf(w.clear(), "int is %ld", 103); + REQUIRE(w.view() == "int is 103"); + ts::bwprintf(w.clear(), "int is %s", 104); + REQUIRE(w.view() == "int is 104"); + ts::bwprintf(w.clear(), "int is %ld", -105); + REQUIRE(w.view() == "int is -105"); + + TextView digits{"0123456789"}; + ts::bwprintf(w.clear(), "Chars |%*s|", 12, digits); + REQUIRE(w.view() == "Chars | 0123456789|"); + ts::bwprintf(w.clear(), "Chars %.*s", 4, digits); + REQUIRE(w.view() == "Chars 0123"); + ts::bwprintf(w.clear(), "Chars |%*.*s|", 12, 5, digits); + REQUIRE(w.view() == "Chars | 01234|"); +} + // Normally there's no point in running the performance tests, but it's worth keeping the code // for when additional testing needs to be done. #if 0 @@ -583,25 +685,25 @@ TEST_CASE("bwperf", "[bwprint][performance]") static constexpr std::string_view text{"e99a18c428cb38d5f260853678922e03"sv}; ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; - bw.reduce(0); + bw.clear(); bw.print(fmt, -956, text); REQUIRE(bw.view() == "Format |-0x00003bc| 'e99a18c428cb38d5f260853678922e03'"); start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N_LOOPS; ++i) { - bw.reduce(0); + bw.clear(); bw.print(fmt, -956, text); } delta = std::chrono::high_resolution_clock::now() - start; std::cout << "bw.print() " << delta.count() << "ns or " << std::chrono::duration_cast(delta).count() << "ms" << std::endl; - ts::BWFormat pre_fmt(fmt); + ts::bwf::Format pre_fmt(fmt); start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N_LOOPS; ++i) { - bw.reduce(0); + bw.clear(); bw.print(pre_fmt, -956, text); } delta = std::chrono::high_resolution_clock::now() - start; From 994f088367b4e541fdde7edbc583d741acd18b72 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Wed, 24 Oct 2018 09:12:24 -0500 Subject: [PATCH 6/7] TextView: Add TransformView for doing in place transforms on string views. --- include/tscpp/util/TextView.h | 230 ++++++++++++++++++++- src/tscpp/util/unit_tests/test_TextView.cc | 34 +++ 2 files changed, 263 insertions(+), 1 deletion(-) diff --git a/include/tscpp/util/TextView.h b/include/tscpp/util/TextView.h index 0ac740d6517..ee00a51e211 100644 --- a/include/tscpp/util/TextView.h +++ b/include/tscpp/util/TextView.h @@ -27,6 +27,7 @@ #pragma once #include #include +#include #include #include #include @@ -1168,7 +1169,227 @@ TextView::stream_write(Stream &os, const TextView &b) const // Provide an instantiation for @c std::ostream as it's likely this is the only one ever used. extern template std::ostream &TextView::stream_write(std::ostream &, const TextView &) const; -} // namespace ts +/** A transform view. + * + * @tparam X Transform functor type. + * @tparam V Source view type. + * + * A transform view acts like a view on the original source view @a V with each element transformed by + * @a X. + * + * This is used most commonly with @c std::string_view. For example, if the goal is to handle a + * piece of text as if it were lower case without changing the actual text, the following would + * make that possible. + * @code + * std:::string_view source; // original text. + * TransformView xv(&tolower, source); + * @endcode + * + * To avoid having to figure out the exact signature of the transform, the convenience function + * @c transform_view_of is provide. + * @code + * std::string_view source; // original text. + * auto xv = transform_view_of(&tolower, source); + * @endcode + */ +template class TransformView +{ + using self_type = TransformView; ///< Self reference type. + using iter = decltype(static_cast(nullptr)->begin()); + +public: + using transform_type = X; ///< Export transform functor type. + using source_view_type = V; ///< Export source view type. + using source_value_type = decltype(**static_cast(nullptr)); + /// Result type of calling the transform on an element of the source view. + using value_type = decltype( + (*static_cast(nullptr))(*static_cast::type *>(nullptr))); + + /** Construct a transform view using transform @a xf on source view @a v. + * + * @param xf Transform instance. + * @param v Source view. + */ + TransformView(transform_type &&xf, source_view_type const &v); + + /** Construct a transform view using transform @a xf on source view @a v. + * + * @param xf Transform instance. + * @param v Source view. + */ + TransformView(transform_type const &xf, source_view_type const &v); + + /// Copy constructor. + TransformView(self_type const &that) = default; + /// Move constructor. + TransformView(self_type &&that) = default; + + /// Copy assignment. + self_type &operator=(self_type const &that) = default; + /// Move assignment. + self_type &operator=(self_type &&that) = default; + + /// Equality. + bool operator==(self_type const &that) const; + /// Inequality. + bool operator!=(self_type const &that) const; + + /// Get the current element. + value_type operator*() const; + /// Move to next element. + self_type &operator++(); + /// Move to next element. + self_type operator++(int); + + /// Check if view is empty. + bool empty() const; + /// Check if bool is not empty. + explicit operator bool() const; + +protected: + transform_type _xf; + iter _spot; + iter _limit; +}; + +template +TransformView::TransformView(transform_type &&xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end()) +{ +} + +template +TransformView::TransformView(transform_type const &xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end()) +{ +} + +template auto TransformView::operator*() const -> value_type +{ + return _xf(*_spot); +} + +template +auto +TransformView::operator++() -> self_type & +{ + ++_spot; + return *this; +} + +template +auto +TransformView::operator++(int) -> self_type +{ + self_type zret{*this}; + ++_spot; + return zret; +} + +template +bool +TransformView::empty() const +{ + return _spot == _limit; +} + +template TransformView::operator bool() const +{ + return _spot != _limit; +} + +template +bool +TransformView::operator==(self_type const &that) const +{ + return _spot == that._spot && _limit == that._limit; +} + +template +bool +TransformView::operator!=(self_type const &that) const +{ + return _spot != that._spot || _limit != that._limit; +} + +template +TransformView +transform_view_of(X const &xf, V const &v) +{ + return TransformView(xf, v); +} + +// Specialization for identity transform. +template class TransformView +{ + using self_type = TransformView; ///< Self reference type. + using iter = decltype(static_cast(nullptr)->begin()); + +public: + using source_view_type = V; ///< Export source view type. + using source_value_type = decltype(**static_cast(nullptr)); + /// Result type of calling the transform on an element of the source view. + using value_type = source_value_type; + + /** Construct identity transform view from @a v. + * + * @param v Source view. + */ + TransformView(source_view_type const &v) : _spot(v.begin()), _limit(v.end()) {} + + /// Copy constructor. + TransformView(self_type const &that) = default; + /// Move constructor. + TransformView(self_type &&that) = default; + + /// Copy assignment. + self_type &operator=(self_type const &that) = default; + /// Move assignment. + self_type &operator=(self_type &&that) = default; + + /// Equality. + bool operator==(self_type const &that) const; + /// Inequality. + bool operator!=(self_type const &that) const; + + /// Get the current element. + value_type operator*() const { return *_spot; } + /// Move to next element. + self_type & + operator++() + { + ++_spot; + return *this; + } + /// Move to next element. + self_type + operator++(int) + { + auto zret{*this}; + ++*this; + return zret; + } + + /// Check if view is empty. + bool + empty() const + { + return _spot == _limit; + } + /// Check if bool is not empty. + explicit operator bool() const { return _spot != _limit; } + +protected: + iter _spot; + iter _limit; +}; + +template +TransformView +transform_view_of(V const &v) +{ + return TransformView(v); +} + +}; // namespace ts namespace std { @@ -1187,6 +1408,13 @@ template <> struct iterator_traits { using iterator_category = forward_iterator_tag; }; +template struct iterator_traits> { + using value_type = typename ts::TransformView::value_type; + using pointer_type = const value_type *; + using reference_type = const value_type &; + using difference_type = ssize_t; + using iterator_category = forward_iterator_tag; +}; } // namespace std // @c constexpr literal constructor for @c std::string_view diff --git a/src/tscpp/util/unit_tests/test_TextView.cc b/src/tscpp/util/unit_tests/test_TextView.cc index d9ce540b1c8..f36776b00c6 100644 --- a/src/tscpp/util/unit_tests/test_TextView.cc +++ b/src/tscpp/util/unit_tests/test_TextView.cc @@ -315,3 +315,37 @@ TEST_CASE("TextView Conversions", "[libts][TextView]") REQUIRE(25 == svtoi(n3)); REQUIRE(31 == svtoi(n3, nullptr, 10)); } + +TEST_CASE("TransformView", "[libts][TransformView]") +{ + std::string_view source{"Evil Dave Rulz"}; + ts::TransformView xv1(&tolower, source); + auto xv2 = ts::transform_view_of(&tolower, source); + TextView tv{source}; + + REQUIRE(xv1 == xv2); + + bool match_p = true; + while (xv1) { + if (*xv1 != tolower(*tv)) { + match_p = false; + break; + } + ++xv1; + ++tv; + } + REQUIRE(match_p); + REQUIRE(xv1 != xv2); + + tv = source; + match_p = true; + while (xv2) { + if (*xv2 != tolower(*tv)) { + match_p = false; + break; + } + ++xv2; + ++tv; + } + REQUIRE(match_p); +}; From dfdfdc1e7004ef3c6f61261c0cc237d90dd4bfdf Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Sat, 8 Sep 2018 20:48:12 -0500 Subject: [PATCH 7/7] Hash: Refresh for external use. This updates the API for hash functions in ATS to make it suitable for external (plugin) use. The basic hashes are moved to the extern C++ area. --- include/tscore/ConsistentHash.h | 13 +- include/tscore/EnumDescriptor.h | 6 +- include/tscore/Hash.h | 65 --------- include/tscore/HashFNV.h | 93 ------------ include/tscore/HashMD5.h | 34 +++-- include/tscpp/util/Hash.h | 153 ++++++++++++++++++++ include/tscpp/util/HashFNV.h | 177 +++++++++++++++++++++++ include/{tscore => tscpp/util}/HashSip.h | 53 ++++--- include/tscpp/util/Makefile.am | 9 +- iocore/hostdb/I_HostDBProcessor.h | 8 +- proxy/ParentConsistentHash.cc | 26 ++-- proxy/ParentConsistentHash.h | 6 +- proxy/ParentSelection.cc | 2 + proxy/hdrs/HdrToken.cc | 17 +-- src/traffic_logstats/logstats.cc | 9 +- src/tscore/ConsistentHash.cc | 35 ++--- src/tscore/HashFNV.cc | 58 -------- src/tscore/HashMD5.cc | 46 +++--- src/tscore/Makefile.am | 6 - src/{tscore => tscpp/util}/Hash.cc | 36 +++-- src/{tscore => tscpp/util}/HashSip.cc | 65 +++++---- src/tscpp/util/Makefile.am | 5 + 22 files changed, 533 insertions(+), 389 deletions(-) delete mode 100644 include/tscore/Hash.h delete mode 100644 include/tscore/HashFNV.h create mode 100644 include/tscpp/util/Hash.h create mode 100644 include/tscpp/util/HashFNV.h rename include/{tscore => tscpp/util}/HashSip.h (51%) delete mode 100644 src/tscore/HashFNV.cc rename src/{tscore => tscpp/util}/Hash.cc (65%) rename src/{tscore => tscpp/util}/HashSip.cc (68%) diff --git a/include/tscore/ConsistentHash.h b/include/tscore/ConsistentHash.h index 9946776cc61..692672bd7ec 100644 --- a/include/tscore/ConsistentHash.h +++ b/include/tscore/ConsistentHash.h @@ -21,10 +21,11 @@ #pragma once -#include "Hash.h" +#include "tscpp/util/Hash.h" #include #include #include +#include /* Helper class to be extended to make ring nodes. @@ -46,17 +47,17 @@ typedef std::map::iterator ATSConsistentHashI */ struct ATSConsistentHash { - ATSConsistentHash(int r = 1024, ATSHash64 *h = nullptr); - void insert(ATSConsistentHashNode *node, float weight = 1.0, ATSHash64 *h = nullptr); + ATSConsistentHash(int r = 1024, ts::Hash64Functor *h = nullptr); + void insert(ATSConsistentHashNode *node, float weight = 1.0, ts::Hash64Functor *h = nullptr); ATSConsistentHashNode *lookup(const char *url = nullptr, ATSConsistentHashIter *i = nullptr, bool *w = nullptr, - ATSHash64 *h = nullptr); + ts::Hash64Functor *h = nullptr); ATSConsistentHashNode *lookup_available(const char *url = nullptr, ATSConsistentHashIter *i = nullptr, bool *w = nullptr, - ATSHash64 *h = nullptr); + ts::Hash64Functor *h = nullptr); ATSConsistentHashNode *lookup_by_hashval(uint64_t hashval, ATSConsistentHashIter *i = nullptr, bool *w = nullptr); ~ATSConsistentHash(); private: int replicas; - ATSHash64 *hash; + std::unique_ptr hash; std::map NodeMap; }; diff --git a/include/tscore/EnumDescriptor.h b/include/tscore/EnumDescriptor.h index 485a5c3db59..446ee5785ed 100644 --- a/include/tscore/EnumDescriptor.h +++ b/include/tscore/EnumDescriptor.h @@ -22,15 +22,13 @@ #include #include -#include "tscore/HashFNV.h" +#include "tscpp/util/HashFNV.h" /// Hash functor for @c string_view inline size_t TsLuaConfigSVHash(std::string_view const &sv) { - ATSHash64FNV1a h; - h.update(sv.data(), sv.size()); - return h.get(); + return ts::Hash64FNV1a().hash_immediate(sv); } class TsEnumDescriptor diff --git a/include/tscore/Hash.h b/include/tscore/Hash.h deleted file mode 100644 index 68879a93951..00000000000 --- a/include/tscore/Hash.h +++ /dev/null @@ -1,65 +0,0 @@ -/** @file - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include -#include - -struct ATSHashBase { - virtual void update(const void *, size_t) = 0; - virtual void final(void) = 0; - virtual void clear(void) = 0; - virtual ~ATSHashBase(); -}; - -struct ATSHash : ATSHashBase { - struct nullxfrm { - uint8_t - operator()(uint8_t byte) const - { - return byte; - } - }; - - struct nocase { - uint8_t - operator()(uint8_t byte) const - { - return toupper(byte); - } - }; - - virtual const void *get(void) const = 0; - virtual size_t size(void) const = 0; - virtual bool operator==(const ATSHash &) const; -}; - -struct ATSHash32 : ATSHashBase { - virtual uint32_t get(void) const = 0; - virtual bool operator==(const ATSHash32 &) const; -}; - -struct ATSHash64 : ATSHashBase { - virtual uint64_t get(void) const = 0; - virtual bool operator==(const ATSHash64 &) const; -}; diff --git a/include/tscore/HashFNV.h b/include/tscore/HashFNV.h deleted file mode 100644 index e113d0cd012..00000000000 --- a/include/tscore/HashFNV.h +++ /dev/null @@ -1,93 +0,0 @@ -/** @file - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -/* - http://www.isthe.com/chongo/tech/comp/fnv/ - - Currently implemented FNV-1a 32bit and FNV-1a 64bit - */ - -#pragma once - -#include "tscore/Hash.h" -#include - -struct ATSHash32FNV1a : ATSHash32 { - ATSHash32FNV1a(void); - - template void update(const void *data, size_t len, Transform xfrm); - void - update(const void *data, size_t len) override - { - update(data, len, ATSHash::nullxfrm()); - } - - void final(void) override; - uint32_t get(void) const override; - void clear(void) override; - -private: - uint32_t hval; -}; - -template -void -ATSHash32FNV1a::update(const void *data, size_t len, Transform xfrm) -{ - uint8_t *bp = (uint8_t *)data; - uint8_t *be = bp + len; - - for (; bp < be; ++bp) { - hval ^= (uint32_t)xfrm(*bp); - hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); - } -} - -struct ATSHash64FNV1a : ATSHash64 { - ATSHash64FNV1a(void); - - template void update(const void *data, size_t len, Transform xfrm); - void - update(const void *data, size_t len) override - { - update(data, len, ATSHash::nullxfrm()); - } - - void final(void) override; - uint64_t get(void) const override; - void clear(void) override; - -private: - uint64_t hval; -}; - -template -void -ATSHash64FNV1a::update(const void *data, size_t len, Transform xfrm) -{ - uint8_t *bp = (uint8_t *)data; - uint8_t *be = bp + len; - - for (; bp < be; ++bp) { - hval ^= (uint64_t)xfrm(*bp); - hval += (hval << 1) + (hval << 4) + (hval << 5) + (hval << 7) + (hval << 8) + (hval << 40); - } -} diff --git a/include/tscore/HashMD5.h b/include/tscore/HashMD5.h index bad5a90cc80..72ffd2e5e59 100644 --- a/include/tscore/HashMD5.h +++ b/include/tscore/HashMD5.h @@ -21,21 +21,31 @@ #pragma once -#include "tscore/Hash.h" +#include "tscpp/util/Hash.h" #include -struct ATSHashMD5 : ATSHash { - ATSHashMD5(void); - void update(const void *data, size_t len) override; - void final(void) override; - const void *get(void) const override; - size_t size(void) const override; - void clear(void) override; - ~ATSHashMD5() override; +namespace ts +{ +struct HashMD5 : HashFunctor { + HashMD5(); + + HashMD5 &update(std::string_view const &data) override; + + HashMD5 & final() override; + + bool get(MemSpan dst) const override; + + size_t size() const override; + + HashMD5 &clear() override; + + ~HashMD5() override; private: - EVP_MD_CTX *ctx; + EVP_MD_CTX *ctx{nullptr}; unsigned char md_value[EVP_MAX_MD_SIZE]; - unsigned int md_len; - bool finalized; + unsigned int md_len{0}; + bool finalized{false}; }; + +} // namespace ts diff --git a/include/tscpp/util/Hash.h b/include/tscpp/util/Hash.h new file mode 100644 index 00000000000..5a2829f3e08 --- /dev/null +++ b/include/tscpp/util/Hash.h @@ -0,0 +1,153 @@ +/** @file Basic hash function support. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include "tscpp/util/MemSpan.h" +#include "tscpp/util/TextView.h" + +namespace ts +{ +/** Base protocol class for hash functors. + * + * Each specific hash function embedded in a hash functor is a subclass of this class and + * follows this API. Subclasses should override the return type to return the subclass type. + * + * The main purpose of this is to allow run time changes in hashing, which is required in various + * circumstances. + */ +struct HashFunctor { + using self_type = HashFunctor; ///< Self reference type. + + /// Pass @a data to the hashing function. + virtual self_type &update(std::string_view const &data) = 0; + + /// Finalize the hash function output. + virtual self_type & final() = 0; + + /// Reset the hash function state. + virtual self_type &clear() = 0; + + /// Get the size of the resulting hash value. + virtual size_t size() const = 0; + + /// Copy the result to @a dst. + /// @a dst must be at least @c result_size bytes long. + /// @return @c true if the result was copied to @a data, @c false otherwise. + virtual bool get(MemSpan dst) const = 0; + + virtual ~HashFunctor() = default; ///< Force virtual destructor. +}; + +/// A hash function that returns a 32 bit result. +struct Hash32Functor : HashFunctor { +protected: + using self_type = Hash32Functor; + +public: + using value_type = uint32_t; + + // Co-vary the return type. + self_type &update(std::string_view const &data) override = 0; + + self_type & final() override = 0; + + self_type &clear() override = 0; + + virtual value_type get() const = 0; + + /// Get the size of the resulting hash value. + size_t size() const override; + + bool get(MemSpan dst) const override; + + /** Immediately produce a hash value from @a data. + * + * @param data Hash input + * @return Hash value. + * + * This is a convenience method for when all the data to hash is already available. + */ + value_type hash_immediate(std::string_view const &data); +}; + +struct Hash64Functor : HashFunctor { +protected: + using self_type = Hash64Functor; + +public: + using value_type = uint64_t; + + // Co-vary the return type. + self_type &update(std::string_view const &data) override = 0; + + self_type & final() override = 0; + + self_type &clear() override = 0; + + virtual value_type get() const = 0; + + /// Get the size of the resulting hash value. + size_t size() const override; + + bool get(MemSpan dst) const override; + + /** Immediately produce a hash value from @a data. + * + * @param data hash input + * @return Hash value. + * + * This is a convenience method for when all the data to hash is already available. + */ + value_type hash_immediate(std::string_view const &data); +}; + +// ---- +// Implementation + +inline auto +Hash32Functor::hash_immediate(std::string_view const &data) -> value_type +{ + return this->update(data).final().get(); +} + +inline size_t +Hash32Functor::size() const +{ + return sizeof(value_type); +} + +inline auto +Hash64Functor::hash_immediate(std::string_view const &data) -> value_type +{ + return this->update(data).final().get(); +} + +inline size_t +Hash64Functor::size() const +{ + return sizeof(value_type); +} + +} // namespace ts diff --git a/include/tscpp/util/HashFNV.h b/include/tscpp/util/HashFNV.h new file mode 100644 index 00000000000..1ffc1607d77 --- /dev/null +++ b/include/tscpp/util/HashFNV.h @@ -0,0 +1,177 @@ +/** @file + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. + See the NOTICE file distributed with this work for additional information regarding copyright + ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance with the License. You may obtain a + copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the License + is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing permissions and limitations under + the License. + */ + +/* + http://www.isthe.com/chongo/tech/comp/fnv/ + + Currently implemented FNV-1a 32bit and FNV-1a 64bit + */ + +#pragma once + +#include +#include "tscpp/util/Hash.h" + +namespace ts +{ +struct Hash32FNV1a : Hash32Functor { +protected: + using self_type = Hash32FNV1a; + using super_type = Hash32Functor; + static constexpr uint32_t INIT = 0x811c9dc5u; + +public: + Hash32FNV1a() = default; + + self_type &update(std::string_view const &data) override; + + self_type & final() override; + + value_type get() const override; + + self_type &clear() override; + + template self_type &update(TransformView view); + + template value_type hash_immediate(TransformView const &view); + using super_type::hash_immediate; + +private: + value_type hval{INIT}; +}; + +struct Hash64FNV1a : Hash64Functor { +protected: + using self_type = Hash64FNV1a; + using super_type = Hash64Functor; + static constexpr uint64_t INIT = 0xcbf29ce484222325ull; + +public: + Hash64FNV1a() = default; + + self_type &update(std::string_view const &data) override; + + self_type & final() override; + + value_type get() const override; + + self_type &clear() override; + + template self_type &update(TransformView view); + + template value_type hash_immediate(TransformView const &view); + using super_type::hash_immediate; + +private: + value_type hval{INIT}; +}; + +// ---------- +// Implementation + +// -- 32 -- + +inline auto +Hash32FNV1a::clear() -> self_type & +{ + hval = INIT; + return *this; +} + +template +auto +Hash32FNV1a::update(TransformView view) -> self_type & +{ + for (; view; ++view) { + hval ^= static_cast(*view); + hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); + } + return *this; +} + +inline auto +Hash32FNV1a::update(std::string_view const &data) -> self_type & +{ + return this->update(transform_view_of(data)); +} + +inline auto +Hash32FNV1a::final() -> self_type & +{ + return *this; +} + +inline auto +Hash32FNV1a::get() const -> value_type +{ + return hval; +} + +template +auto +Hash32FNV1a::hash_immediate(ts::TransformView const &view) -> value_type +{ + return this->update(view).get(); +} + +// -- 64 -- + +inline auto +Hash64FNV1a::clear() -> self_type & +{ + hval = INIT; + return *this; +} + +template +auto +Hash64FNV1a::update(TransformView view) -> self_type & +{ + for (; view; ++view) { + hval ^= static_cast(*view); + hval += (hval << 1) + (hval << 4) + (hval << 5) + (hval << 7) + (hval << 8) + (hval << 40); + } + return *this; +} + +inline auto +Hash64FNV1a::update(std::string_view const &data) -> self_type & +{ + return this->update(transform_view_of(data)); +} + +inline auto +Hash64FNV1a::final() -> self_type & +{ + return *this; +} + +inline auto +Hash64FNV1a::get() const -> value_type +{ + return hval; +} + +template +auto +Hash64FNV1a::hash_immediate(ts::TransformView const &view) -> value_type +{ + return this->update(view).final().get(); +} + +} // namespace ts diff --git a/include/tscore/HashSip.h b/include/tscpp/util/HashSip.h similarity index 51% rename from include/tscore/HashSip.h rename to include/tscpp/util/HashSip.h index 837289848dc..dd780b5595d 100644 --- a/include/tscore/HashSip.h +++ b/include/tscpp/util/HashSip.h @@ -21,9 +21,11 @@ #pragma once -#include "tscore/Hash.h" +#include "tscpp/util/Hash.h" #include +namespace ts +{ /* Siphash is a Hash Message Authentication Code and can take a key. @@ -31,25 +33,36 @@ a zero key for you. */ -struct ATSHash64Sip24 : ATSHash64 { - ATSHash64Sip24(void); - ATSHash64Sip24(const unsigned char key[16]); - ATSHash64Sip24(std::uint64_t key0, std::uint64_t key1); - void update(const void *data, std::size_t len) override; - void final(void) override; - std::uint64_t get(void) const override; - void clear(void) override; +struct Hash64Sip24 : public Hash64Functor { + static constexpr size_t KEY_SIZE = 16; + + Hash64Sip24(); + + Hash64Sip24(const unsigned char key[KEY_SIZE]); + + Hash64Sip24(std::uint64_t key0, std::uint64_t key1); + + Hash64Sip24 &update(std::string_view const &data) override; + + Hash64Sip24 & final() override; + + value_type get() const override; + + Hash64Sip24 &clear() override; private: - unsigned char block_buffer[8] = {0}; - std::uint8_t block_buffer_len = 0; - std::uint64_t k0 = 0; - std::uint64_t k1 = 0; - std::uint64_t v0 = 0; - std::uint64_t v1 = 0; - std::uint64_t v2 = 0; - std::uint64_t v3 = 0; - std::uint64_t hfinal = 0; - std::size_t total_len = 0; - bool finalized = false; + static constexpr size_t BLOCK_SIZE = 8; + unsigned char block_buffer[BLOCK_SIZE] = {0}; + std::uint8_t block_buffer_len = 0; + std::uint64_t k0 = 0; + std::uint64_t k1 = 0; + std::uint64_t v0 = 0; + std::uint64_t v1 = 0; + std::uint64_t v2 = 0; + std::uint64_t v3 = 0; + std::uint64_t hfinal = 0; + std::size_t total_len = 0; + bool finalized = false; }; + +} // namespace ts diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index 51fd7876f9f..df185779993 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -24,9 +24,12 @@ library_include_HEADERS = \ bwf_ex.h \ bwf_base.h \ bwf_std.h \ - IntrusiveDList.h \ + Hash.h \ + HashFNV.h \ + HashSip.h \ + IntrusiveDList.h \ IntrusiveHashMap.h \ MemArena.h \ MemSpan.h \ - PostScript.h \ - TextView.h + PostScript.h \ + TextView.h diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h index 3c4c64263cd..761b72cd403 100644 --- a/iocore/hostdb/I_HostDBProcessor.h +++ b/iocore/hostdb/I_HostDBProcessor.h @@ -23,7 +23,8 @@ #pragma once -#include "tscore/HashFNV.h" +#include +#include "tscpp/util/HashFNV.h" #include "tscore/ink_time.h" #include "tscore/CryptoHash.h" #include "tscore/ink_align.h" @@ -68,10 +69,7 @@ makeHostHash(const char *string) ink_assert(string && *string); if (string && *string) { - ATSHash32FNV1a fnv; - fnv.update(string, strlen(string), ATSHash::nocase()); - fnv.final(); - return fnv.get(); + return ts::Hash32FNV1a().hash_immediate(ts::transform_view_of(&tolower, std::string_view{string})); } return 0; diff --git a/proxy/ParentConsistentHash.cc b/proxy/ParentConsistentHash.cc index b7466ca5869..b31dce42be4 100644 --- a/proxy/ParentConsistentHash.cc +++ b/proxy/ParentConsistentHash.cc @@ -20,8 +20,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "HostStatus.h" #include "ParentConsistentHash.h" +#include "HostStatus.h" ParentConsistentHash::ParentConsistentHash(ParentRecord *parent_record) { @@ -37,7 +37,7 @@ ParentConsistentHash::ParentConsistentHash(ParentRecord *parent_record) chash[PRIMARY] = new ATSConsistentHash(); for (i = 0; i < parent_record->num_parents; i++) { - chash[PRIMARY]->insert(&(parent_record->parents[i]), parent_record->parents[i].weight, (ATSHash64 *)&hash[PRIMARY]); + chash[PRIMARY]->insert(&(parent_record->parents[i]), parent_record->parents[i].weight, (ts::Hash64Functor *)&hash[PRIMARY]); } if (parent_record->num_secondary_parents > 0) { @@ -46,7 +46,7 @@ ParentConsistentHash::ParentConsistentHash(ParentRecord *parent_record) for (i = 0; i < parent_record->num_secondary_parents; i++) { chash[SECONDARY]->insert(&(parent_record->secondary_parents[i]), parent_record->secondary_parents[i].weight, - (ATSHash64 *)&hash[SECONDARY]); + (ts::Hash64Functor *)&hash[SECONDARY]); } } else { chash[SECONDARY] = nullptr; @@ -62,7 +62,7 @@ ParentConsistentHash::~ParentConsistentHash() } uint64_t -ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ATSHash64 *h) +ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ts::Hash64Functor *h) { const char *url_string_ref = nullptr; int len; @@ -77,26 +77,24 @@ ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ATSHash64 *h) if (url_string_ref && len > 0) { // Print the over-ride URL Debug("parent_select", "Using Over-Ride String='%.*s'.", len, url_string_ref); - h->update(url_string_ref, len); - h->final(); - return h->get(); + return h->update(std::string_view(url_string_ref, len)).final().get(); } } } // Always hash on '/' because paths returned by ATS are always stripped of it - h->update("/", 1); + h->update("/"_sv); url_string_ref = url->path_get(&len); if (url_string_ref) { - h->update(url_string_ref, len); + h->update(std::string_view(url_string_ref, len)); } if (!ignore_query) { url_string_ref = url->query_get(&len); if (url_string_ref) { - h->update("?", 1); - h->update(url_string_ref, len); + h->update("?"_sv); + h->update(std::string_view(url_string_ref, len)); } } @@ -108,7 +106,7 @@ ParentConsistentHash::getPathHash(HttpRequestData *hrdata, ATSHash64 *h) // Helper function to abstract calling ATSConsistentHash lookup_by_hashval() vs lookup(). static pRecord * chash_lookup(ATSConsistentHash *fhash, uint64_t path_hash, ATSConsistentHashIter *chashIter, bool *wrap_around, - ATSHash64Sip24 *hash, bool *chash_init) + ts::Hash64Sip24 *hash, bool *chash_init) { pRecord *prtmp; @@ -125,7 +123,7 @@ void ParentConsistentHash::selectParent(bool first_call, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int retry_time) { - ATSHash64Sip24 hash; + ts::Hash64Sip24 hash; ATSConsistentHash *fhash; HttpRequestData *request_info = static_cast(rdata); bool firstCall = first_call; @@ -184,7 +182,7 @@ ParentConsistentHash::selectParent(bool first_call, ParentResult *result, Reques } // Do the initial parent look-up. - path_hash = getPathHash(request_info, (ATSHash64 *)&hash); + path_hash = getPathHash(request_info, (ts::Hash64Functor *)&hash); fhash = chash[last_lookup]; do { // search until we've selected a different parent if !firstCall prtmp = (pRecord *)chash_lookup(fhash, path_hash, &result->chashIter[last_lookup], &wrap_around[last_lookup], &hash, diff --git a/proxy/ParentConsistentHash.h b/proxy/ParentConsistentHash.h index 3a8ee582789..14c962d9645 100644 --- a/proxy/ParentConsistentHash.h +++ b/proxy/ParentConsistentHash.h @@ -29,7 +29,7 @@ #pragma once -#include "tscore/HashSip.h" +#include "tscpp/util/HashSip.h" #include "ParentSelection.h" // @@ -40,7 +40,7 @@ class ParentConsistentHash : public ParentSelectionStrategy { // there are two hashes PRIMARY parents // and SECONDARY parents. - ATSHash64Sip24 hash[2]; + ts::Hash64Sip24 hash[2]; ATSConsistentHash *chash[2]; pRecord *parents[2]; bool foundParents[2][MAX_PARENTS]; @@ -57,7 +57,7 @@ class ParentConsistentHash : public ParentSelectionStrategy { return parents[result->last_lookup]; } - uint64_t getPathHash(HttpRequestData *hrdata, ATSHash64 *h); + uint64_t getPathHash(HttpRequestData *hrdata, ts::Hash64Functor *h); void selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int retry_time) override; void markParentDown(ParentResult *result, unsigned int fail_threshold, unsigned int retry_time); diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc index df3f368ee95..72fee498370 100644 --- a/proxy/ParentSelection.cc +++ b/proxy/ParentSelection.cc @@ -20,6 +20,8 @@ See the License for the specific language governing permissions and limitations under the License. */ + +#include "tscpp/util/HashSip.h" #include "P_EventSystem.h" #include "ParentSelection.h" #include "ParentConsistentHash.h" diff --git a/proxy/hdrs/HdrToken.cc b/proxy/hdrs/HdrToken.cc index 55fe5200a6f..b4be7f9a437 100644 --- a/proxy/hdrs/HdrToken.cc +++ b/proxy/hdrs/HdrToken.cc @@ -22,7 +22,7 @@ */ #include "tscore/ink_platform.h" -#include "tscore/HashFNV.h" +#include "tscpp/util/HashFNV.h" #include "tscore/Diags.h" #include "tscore/ink_memory.h" #include @@ -282,12 +282,9 @@ hash_to_slot(uint32_t hash) } inline uint32_t -hdrtoken_hash(const unsigned char *string, unsigned int length) +hdrtoken_hash(const char *string, unsigned int length) { - ATSHash32FNV1a fnv; - fnv.update(string, length, ATSHash::nocase()); - fnv.final(); - return fnv.get(); + return ts::Hash32FNV1a().hash_immediate(ts::transform_view_of(&toupper, std::string_view(string, length))); } /*------------------------------------------------------------------------- @@ -375,9 +372,9 @@ hdrtoken_hash_init() for (i = 0; i < (int)SIZEOF(_hdrtoken_commonly_tokenized_strs); i++) { // convert the common string to the well-known token - unsigned const char *wks; - int wks_idx = hdrtoken_tokenize_dfa(_hdrtoken_commonly_tokenized_strs[i], (int)strlen(_hdrtoken_commonly_tokenized_strs[i]), - (const char **)&wks); + const char *wks; + int wks_idx = + hdrtoken_tokenize_dfa(_hdrtoken_commonly_tokenized_strs[i], int(strlen(_hdrtoken_commonly_tokenized_strs[i])), &wks); ink_release_assert(wks_idx >= 0); uint32_t hash = hdrtoken_hash(wks, hdrtoken_str_lengths[wks_idx]); @@ -558,7 +555,7 @@ hdrtoken_tokenize(const char *string, int string_len, const char **wks_string_ou return wks_idx; } - uint32_t hash = hdrtoken_hash((const unsigned char *)string, (unsigned int)string_len); + uint32_t hash = hdrtoken_hash(string, string_len); uint32_t slot = hash_to_slot(hash); bucket = &(hdrtoken_hash_table[slot]); diff --git a/src/traffic_logstats/logstats.cc b/src/traffic_logstats/logstats.cc index 72885e1bc65..b2a86b740f4 100644 --- a/src/traffic_logstats/logstats.cc +++ b/src/traffic_logstats/logstats.cc @@ -26,7 +26,7 @@ #include "tscore/ink_file.h" #include "tscore/I_Layout.h" #include "tscore/I_Version.h" -#include "tscore/HashFNV.h" +#include "tscpp/util/HashFNV.h" #include "tscore/ink_args.h" #include "tscore/MatcherUtils.h" #include "tscore/runroot.h" @@ -325,14 +325,13 @@ struct hash_fnv32 { inline uint32_t operator()(const char *s) const { - ATSHash32FNV1a fnv; + ts::Hash32FNV1a fnv; if (s) { - fnv.update(s, strlen(s)); + fnv.update(s); } - fnv.final(); - return fnv.get(); + return fnv.final().get(); } }; diff --git a/src/tscore/ConsistentHash.cc b/src/tscore/ConsistentHash.cc index 3d060d8cbd1..001634acb78 100644 --- a/src/tscore/ConsistentHash.cc +++ b/src/tscore/ConsistentHash.cc @@ -33,21 +33,21 @@ operator<<(std::ostream &os, ATSConsistentHashNode &thing) return os << thing.name; } -ATSConsistentHash::ATSConsistentHash(int r, ATSHash64 *h) : replicas(r), hash(h) {} +ATSConsistentHash::ATSConsistentHash(int r, ts::Hash64Functor *h) : replicas(r), hash(h) {} void -ATSConsistentHash::insert(ATSConsistentHashNode *node, float weight, ATSHash64 *h) +ATSConsistentHash::insert(ATSConsistentHashNode *node, float weight, ts::Hash64Functor *h) { int i; char numstr[256]; - ATSHash64 *thash; + ts::Hash64Functor *thash; std::ostringstream string_stream; std::string std_string; if (h) { thash = h; } else if (hash) { - thash = hash; + thash = hash.get(); } else { return; } @@ -57,26 +57,24 @@ ATSConsistentHash::insert(ATSConsistentHashNode *node, float weight, ATSHash64 * for (i = 0; i < (int)roundf(replicas * weight); i++) { snprintf(numstr, 256, "%d-", i); - thash->update(numstr, strlen(numstr)); - thash->update(std_string.c_str(), strlen(std_string.c_str())); - thash->final(); + thash->update(numstr).update(std_string).final(); NodeMap.insert(std::pair(thash->get(), node)); thash->clear(); } } ATSConsistentHashNode * -ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) +ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, ts::Hash64Functor *h) { uint64_t url_hash; ATSConsistentHashIter NodeMapIterUp, *iter; - ATSHash64 *thash; + ts::Hash64Functor *thash; bool *wptr, wrapped = false; if (h) { thash = h; } else if (hash) { - thash = hash; + thash = hash.get(); } else { return nullptr; } @@ -94,7 +92,7 @@ ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, AT } if (url) { - thash->update(url, strlen(url)); + thash->update(url); thash->final(); url_hash = thash->get(); thash->clear(); @@ -122,17 +120,17 @@ ATSConsistentHash::lookup(const char *url, ATSConsistentHashIter *i, bool *w, AT } ATSConsistentHashNode * -ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, bool *w, ATSHash64 *h) +ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, bool *w, ts::Hash64Functor *h) { uint64_t url_hash; ATSConsistentHashIter NodeMapIterUp, *iter; - ATSHash64 *thash; + ts::Hash64Functor *thash; bool *wptr, wrapped = false; if (h) { thash = h; } else if (hash) { - thash = hash; + thash = hash.get(); } else { return nullptr; } @@ -150,7 +148,7 @@ ATSConsistentHash::lookup_available(const char *url, ATSConsistentHashIter *i, b } if (url) { - thash->update(url, strlen(url)); + thash->update(url); thash->final(); url_hash = thash->get(); thash->clear(); @@ -205,9 +203,4 @@ ATSConsistentHash::lookup_by_hashval(uint64_t hashval, ATSConsistentHashIter *i, return (*iter)->second; } -ATSConsistentHash::~ATSConsistentHash() -{ - if (hash) { - delete hash; - } -} +ATSConsistentHash::~ATSConsistentHash() {} diff --git a/src/tscore/HashFNV.cc b/src/tscore/HashFNV.cc deleted file mode 100644 index b060ab4205a..00000000000 --- a/src/tscore/HashFNV.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* - This algorithm is in the public domain. This code was - derived from code in the public domain. - - http://www.isthe.com/chongo/tech/comp/fnv/ - - Currently implemented FNV-1a 32bit and FNV-1a 64bit - */ - -#include "tscore/HashFNV.h" - -static const uint32_t FNV_INIT_32 = 0x811c9dc5u; -static const uint64_t FNV_INIT_64 = 0xcbf29ce484222325ull; - -// FNV-1a 64bit -ATSHash32FNV1a::ATSHash32FNV1a() -{ - this->clear(); -} - -void -ATSHash32FNV1a::final() -{ -} - -uint32_t -ATSHash32FNV1a::get() const -{ - return hval; -} - -void -ATSHash32FNV1a::clear() -{ - hval = FNV_INIT_32; -} - -// FNV-1a 64bit -ATSHash64FNV1a::ATSHash64FNV1a() -{ - this->clear(); -} -void -ATSHash64FNV1a::final() -{ -} - -uint64_t -ATSHash64FNV1a::get() const -{ - return hval; -} - -void -ATSHash64FNV1a::clear() -{ - hval = FNV_INIT_64; -} diff --git a/src/tscore/HashMD5.cc b/src/tscore/HashMD5.cc index 7f1dedc1b80..770a1f6b5a1 100644 --- a/src/tscore/HashMD5.cc +++ b/src/tscore/HashMD5.cc @@ -23,66 +23,74 @@ #include "tscore/ink_config.h" #include "tscore/HashMD5.h" -ATSHashMD5::ATSHashMD5() : md_len(0), finalized(false) +namespace ts +{ +HashMD5::HashMD5() : ctx(EVP_MD_CTX_new()) { - ctx = EVP_MD_CTX_new(); int ret = EVP_DigestInit_ex(ctx, EVP_md5(), nullptr); ink_assert(ret == 1); } -void -ATSHashMD5::update(const void *data, size_t len) +HashMD5 & +HashMD5::update(std::string_view const &data) { if (!finalized) { - int ret = EVP_DigestUpdate(ctx, data, len); + int ret = EVP_DigestUpdate(ctx, data.data(), data.size()); ink_assert(ret == 1); } + return *this; } -void -ATSHashMD5::final() +HashMD5 & +HashMD5::final() { if (!finalized) { int ret = EVP_DigestFinal_ex(ctx, md_value, &md_len); ink_assert(ret == 1); finalized = true; } + return *this; } -const void * -ATSHashMD5::get() const +bool +HashMD5::get(MemSpan dst) const { - if (finalized) { - return (void *)md_value; + bool zret = true; + if (finalized && dst.size() > this->size()) { + memcpy(dst.data(), md_value, this->size()); } else { - return nullptr; + zret = false; } + return zret; } size_t -ATSHashMD5::size() const +HashMD5::size() const { return EVP_MD_CTX_size(ctx); } -void -ATSHashMD5::clear() +HashMD5 & +HashMD5::clear() { + int ret = 1; #ifndef OPENSSL_IS_BORINGSSL - int ret = EVP_MD_CTX_reset(ctx); + ret = EVP_MD_CTX_reset(ctx); + ink_assert(ret == 1); #else // OpenSSL's EVP_MD_CTX_reset always returns 1 - int ret = 1; EVP_MD_CTX_reset(ctx); #endif - ink_assert(ret == 1); ret = EVP_DigestInit_ex(ctx, EVP_md5(), nullptr); ink_assert(ret == 1); md_len = 0; finalized = false; + return *this; } -ATSHashMD5::~ATSHashMD5() +HashMD5::~HashMD5() { EVP_MD_CTX_free(ctx); } + +} // namespace ts diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index 57fcac40bf2..5eef541bf69 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -75,14 +75,8 @@ libtscore_la_SOURCES = \ Extendible.h \ fastlz.c \ fastlz.h \ - Hash.cc \ - HashFNV.cc \ - HashFNV.h \ - Hash.h \ HashMD5.cc \ HashMD5.h \ - HashSip.cc \ - HashSip.h \ History.h \ HostLookup.cc \ HostLookup.h \ diff --git a/src/tscore/Hash.cc b/src/tscpp/util/Hash.cc similarity index 65% rename from src/tscore/Hash.cc rename to src/tscpp/util/Hash.cc index 5a34377b133..31df14c47dc 100644 --- a/src/tscore/Hash.cc +++ b/src/tscpp/util/Hash.cc @@ -19,32 +19,30 @@ limitations under the License. */ -#include "tscore/Hash.h" -#include - -ATSHashBase::~ATSHashBase() {} +#include "tscpp/util/Hash.h" bool -ATSHash::operator==(const ATSHash &other) const +ts::Hash32Functor::get(MemSpan dst) const { - if (this->size() != other.size()) { - return false; - } - if (memcmp(this->get(), other.get(), this->size()) == 0) { - return true; + bool zret = true; + if (dst.size() >= this->size()) { + auto v{this->get()}; + memcpy(dst.data(), &v, sizeof(v)); } else { - return false; + zret = false; } + return zret; } bool -ATSHash32::operator==(const ATSHash32 &other) const -{ - return this->get() == other.get(); -} - -bool -ATSHash64::operator==(const ATSHash64 &other) const +ts::Hash64Functor::get(MemSpan dst) const { - return this->get() == other.get(); + bool zret = true; + if (dst.size() >= this->size()) { + auto v{this->get()}; + memcpy(dst.data(), &v, sizeof(v)); + } else { + zret = false; + } + return zret; } diff --git a/src/tscore/HashSip.cc b/src/tscpp/util/HashSip.cc similarity index 68% rename from src/tscore/HashSip.cc rename to src/tscpp/util/HashSip.cc index bc0db100556..b5a03fd6fd3 100644 --- a/src/tscore/HashSip.cc +++ b/src/tscpp/util/HashSip.cc @@ -8,16 +8,24 @@ Based off of implementation: */ -#include "tscore/HashSip.h" +#include "tscpp/util/HashSip.h" #include using namespace std; -#define SIP_BLOCK_SIZE 8 - -#define ROTL64(a, b) (((a) << (b)) | ((a) >> (64 - b))) +namespace +{ +inline uint64_t +ROTL64(uint64_t a, uint64_t b) +{ + return ((a) << (b)) | ((a) >> (64 - b)); +} -#define U8TO64_LE(p) *(const uint64_t *)(p) +inline uint64_t +U8TO64_LE(unsigned char const *p) +{ + return *reinterpret_cast(p); +} #define SIPCOMPRESS(x0, x1, x2, x3) \ x0 += x1; \ @@ -35,39 +43,43 @@ using namespace std; x3 ^= x0; \ x2 = ROTL64(x2, 32); -ATSHash64Sip24::ATSHash64Sip24() +} // namespace + +namespace ts +{ +Hash64Sip24::Hash64Sip24() { this->clear(); } -ATSHash64Sip24::ATSHash64Sip24(const unsigned char key[16]) : k0(U8TO64_LE(key)), k1(U8TO64_LE(key + sizeof(k0))) +Hash64Sip24::Hash64Sip24(const unsigned char key[KEY_SIZE]) : k0(U8TO64_LE(key)), k1(U8TO64_LE(key + sizeof(k0))) { this->clear(); } -ATSHash64Sip24::ATSHash64Sip24(uint64_t key0, uint64_t key1) : k0(key0), k1(key1) +Hash64Sip24::Hash64Sip24(uint64_t key0, uint64_t key1) : k0(key0), k1(key1) { this->clear(); } -void -ATSHash64Sip24::update(const void *data, size_t len) +Hash64Sip24 & +Hash64Sip24::update(std::string_view const &data) { size_t i, blocks; - unsigned char *m; uint64_t mi; uint8_t block_off = 0; if (!finalized) { - m = (unsigned char *)data; + auto len = data.size(); + auto m = reinterpret_cast(data.data()); total_len += len; - if (len + block_buffer_len < SIP_BLOCK_SIZE) { + if (len + block_buffer_len < BLOCK_SIZE) { memcpy(block_buffer + block_buffer_len, m, len); block_buffer_len += len; } else { if (block_buffer_len > 0) { - block_off = SIP_BLOCK_SIZE - block_buffer_len; + block_off = BLOCK_SIZE - block_buffer_len; memcpy(block_buffer + block_buffer_len, m, block_off); mi = U8TO64_LE(block_buffer); @@ -77,7 +89,7 @@ ATSHash64Sip24::update(const void *data, size_t len) v0 ^= mi; } - for (i = block_off, blocks = ((len - block_off) & ~(SIP_BLOCK_SIZE - 1)); i < blocks; i += SIP_BLOCK_SIZE) { + for (i = block_off, blocks = ((len - block_off) & ~(BLOCK_SIZE - 1)); i < blocks; i += BLOCK_SIZE) { mi = U8TO64_LE(m + i); v3 ^= mi; SIPCOMPRESS(v0, v1, v2, v3); @@ -85,14 +97,15 @@ ATSHash64Sip24::update(const void *data, size_t len) v0 ^= mi; } - block_buffer_len = (len - block_off) & (SIP_BLOCK_SIZE - 1); + block_buffer_len = (len - block_off) & (BLOCK_SIZE - 1); memcpy(block_buffer, m + block_off + blocks, block_buffer_len); } } + return *this; } -void -ATSHash64Sip24::final() +Hash64Sip24 & +Hash64Sip24::final() { uint64_t last7; int i; @@ -116,20 +129,17 @@ ATSHash64Sip24::final() hfinal = v0 ^ v1 ^ v2 ^ v3; finalized = true; } + return *this; } uint64_t -ATSHash64Sip24::get() const +Hash64Sip24::get() const { - if (finalized) { - return hfinal; - } else { - return 0; - } + return finalized ? hfinal : 0; } -void -ATSHash64Sip24::clear() +Hash64Sip24 & +Hash64Sip24::clear() { v0 = k0 ^ 0x736f6d6570736575ull; v1 = k1 ^ 0x646f72616e646f6dull; @@ -138,4 +148,7 @@ ATSHash64Sip24::clear() finalized = false; total_len = 0; block_buffer_len = 0; + return *this; } + +} // namespace ts diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index 782b7ec0d91..cad5679cb66 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -30,6 +30,11 @@ libtscpputil_la_SOURCES = \ bw_format.cc \ IntrusiveDList.h \ IntrusiveHashMap.h \ + Hash.cc \ + Hash.h \ + HashFNV.h \ + HashSip.cc \ + HashSip.h \ MemArena.h \ MemArena.cc \ MemSpan.h \