├── .gitmodules ├── proto ├── Makefile ├── med.proto └── main_proto.cpp ├── med ├── med.hpp ├── config.hpp ├── macros_off.hpp ├── sl │ ├── octet_info.hpp │ └── field_copy.hpp ├── update.hpp ├── tag.hpp ├── snapshot.hpp ├── units.hpp ├── debug.hpp ├── protobuf │ ├── protobuf.hpp │ ├── encoder.hpp │ └── decoder.hpp ├── hash.hpp ├── accessor.hpp ├── name.hpp ├── traits.hpp ├── decoder_context.hpp ├── state.hpp ├── count.hpp ├── bytes.hpp ├── macros_on.hpp ├── asn │ ├── ber │ │ ├── ber_info.hpp │ │ ├── ber_length.hpp │ │ ├── ber_tag.hpp │ │ └── ber_decoder.hpp │ ├── ids.hpp │ └── asn.hpp ├── ie_type.hpp ├── value_traits.hpp ├── encoder_context.hpp ├── meta │ ├── unique.hpp │ ├── foreach.hpp │ └── typelist.hpp ├── exception.hpp ├── octet_decoder.hpp ├── padding.hpp ├── allocator.hpp ├── octet_encoder.hpp ├── concepts.hpp ├── encode.hpp ├── length.hpp ├── printer.hpp ├── bit_string.hpp ├── value.hpp └── container.hpp ├── doc ├── Structural-Layer.md ├── Physical-Layer.md ├── Overview.md └── Representation-Layer.md ├── TODO.md ├── .gitignore ├── cmake └── FindBENCHMARK.cmake ├── .github └── workflows │ ├── coverage.yml │ ├── linux.yml │ └── codeql-analysis.yml ├── benchmark ├── results.txt └── bm.cpp ├── LICENSE ├── ut ├── protobuf.cpp ├── unique.cpp ├── meta.cpp ├── bits.cpp ├── gtpc.cpp ├── multi.cpp ├── copy.cpp ├── choice.cpp └── print.cpp ├── README.md └── CMakeLists.txt /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /proto/Makefile: -------------------------------------------------------------------------------- 1 | all: med.pb.h 2 | 3 | med.pb.h: med.proto 4 | protoc --cpp_out=./ $< 5 | 6 | clean: 7 | rm med.pb.* 8 | 9 | -------------------------------------------------------------------------------- /med/med.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "value.hpp" 5 | #include "mandatory.hpp" 6 | #include "optional.hpp" 7 | #include "sequence.hpp" 8 | #include "set.hpp" 9 | #include "choice.hpp" 10 | #include "octet_string.hpp" 11 | 12 | -------------------------------------------------------------------------------- /med/config.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | global configration defines 4 | 5 | @copyright Denis Priyomov 2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | //#ifndef CODEC_TRACE_ENABLE 13 | //#endif 14 | 15 | -------------------------------------------------------------------------------- /doc/Structural-Layer.md: -------------------------------------------------------------------------------- 1 | # 2. Structural layer 2 | 3 | This is the most fuzzy and spread layer without explicit structure and only part of it is defined within its own sub-namespace `med::sl`. However this layer is only needed changes when a new container or a new conditional added to the library. Thus a user of the library doesn't have to gain any knowledge of it. 4 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Most important 2 | ## All IEs as value types 3 | use offsets instead of pointers to reduce memory footprint and allow simple copy 4 | ## Review code to reduce code-bloat 5 | * Remove excessive template parameters 6 | * Common part of buffer via type-erasure + trampolines 7 | * Non-templated allocator 8 | * Introduce .cpp files? 9 | 10 | # Medium importance 11 | ## More concepts to improve errors 12 | 13 | # Minor importance 14 | ## Independent decode/encode in UTs 15 | it's much simpler to debug encode independent of decode 16 | ## accumulating print into span 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Compiled Static libraries 17 | *.lai 18 | *.la 19 | *.a 20 | *.lib 21 | 22 | # Executables 23 | *.exe 24 | *.out 25 | *.app 26 | 27 | #project files 28 | .codelite/ 29 | .autotools 30 | .cproject 31 | .settings/ 32 | Debug/ 33 | Release/ 34 | build/ 35 | *.workspace 36 | *.mk 37 | *.project 38 | *.txt 39 | *.config 40 | *.creator* 41 | *.includes 42 | *.files 43 | *.user 44 | *.cflags 45 | *.cxxflags 46 | .vscode/ 47 | 48 | 49 | -------------------------------------------------------------------------------- /med/macros_off.hpp: -------------------------------------------------------------------------------- 1 | //#pragma once 2 | #ifndef __MED_MACROS_OFF_HPP_INCLUDED__ 3 | #define __MED_MACROS_OFF_HPP_INCLUDED__ 4 | #undef __MED_MACROS_ON_HPP_INCLUDED__ 5 | 6 | #undef MED_BIT_MASK 7 | 8 | #undef MED_BIT_ACCESS_DEF 9 | #undef MED_BIT_ACCESS_DEF_1 10 | #undef MED_BIT_ACCESS_DEF_2 11 | #undef MED_BIT_ACCESS_DEF_3 12 | #undef MED_BIT_ACCESS_DEF_4 13 | #undef MED_BIT_ACCESSOR 14 | 15 | #undef MED_BITS_ACCESS_DEF 16 | #undef MED_BITS_ACCESS_DEF_2 17 | #undef MED_BITS_ACCESS_DEF_3 18 | #undef MED_BITS_ACCESS_DEF_4 19 | #undef MED_BITS_ACCESS_DEF_5 20 | #undef MED_BITS_ACCESSOR 21 | 22 | #undef MED_BITS_ACCESSOR_MASK 23 | 24 | 25 | #endif //__MED_MACROS_OFF_HPP_INCLUDED__ 26 | -------------------------------------------------------------------------------- /med/sl/octet_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../value_traits.hpp" 4 | #include "../tag.hpp" 5 | 6 | namespace med::sl { 7 | 8 | struct octet_info 9 | { 10 | template 11 | static constexpr std::size_t size_of() { return bits_to_bytes(IE::traits::bits); } 12 | 13 | template 14 | static constexpr auto produce_meta_info() 15 | { 16 | //TODO: process MI for multi_field itself? so far it's used only in ASN 17 | if constexpr (AMultiField) 18 | { 19 | return meta::wrap>{}; 20 | } 21 | else 22 | { 23 | return meta::wrap>{}; 24 | } 25 | } 26 | }; 27 | 28 | } //end: namespace med::sl 29 | -------------------------------------------------------------------------------- /proto/med.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message plain { 4 | 5 | enum Enum { 6 | ZERO = 0; 7 | ONE = 1; 8 | TWO = 2; 9 | } 10 | //VARINT=0 11 | int32 int_32 = 1; 12 | int64 int_64 = 2; 13 | uint32 uint_32 = 3; 14 | uint64 uint_64 = 4; 15 | sint32 sint_32 = 5; 16 | sint64 sint_64 = 6; 17 | bool bool_1 = 7; 18 | Enum enum_1 = 8; 19 | 20 | //BITS_64=1 21 | fixed64 fix_64 = 10; 22 | sfixed64 sfix_64 = 11; 23 | double dreal = 12; 24 | 25 | //BITS_32=5 26 | fixed32 fix_32 = 50; 27 | sfixed32 sfix_32 = 51; 28 | float real = 52; 29 | 30 | //LEN_DELIM = 2 31 | // string str = 20; 32 | // bytes octets = 21; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /med/update.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | snapshot updating entry point 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "concepts.hpp" 13 | #include "state.hpp" 14 | #include "encode.hpp" 15 | #include "debug.hpp" 16 | 17 | 18 | namespace med { 19 | 20 | template 21 | constexpr void update(FUNC&& func, IE const& ie) 22 | { 23 | static_assert(std::is_base_of(), "IE WITH med::with_snapshot IS EXPECTED"); 24 | CODEC_TRACE("update %s", name()); 25 | func(SET_STATE{}, ie); 26 | encode(func, ie); 27 | } 28 | 29 | } //end: namespace med 30 | -------------------------------------------------------------------------------- /cmake/FindBENCHMARK.cmake: -------------------------------------------------------------------------------- 1 | # find Google Benchmark Library and headers 2 | # 3 | # BENCHMARK_INCLUDE_DIRS = benchmark headers 4 | # BENCHMARK_LIBS = benchmark lib 5 | 6 | find_package(Threads REQUIRED) 7 | 8 | if (BENCHMARK_INCLUDE_DIRS AND BENCHMARK_LIBS) 9 | set(BENCHMARK_FIND_QUIETLY TRUE) 10 | endif (BENCHMARK_INCLUDE_DIRS AND BENCHMARK_LIBS) 11 | 12 | 13 | find_path(BENCHMARK_INCLUDE_DIRS NAMES benchmark/benchmark.h 14 | /usr/local/include 15 | /opt/local/include 16 | /usr/include 17 | /opt/include 18 | ) 19 | 20 | find_library(BENCHMARK_LIBS NAMES libbenchmark.so) 21 | 22 | 23 | include(FindPackageHandleStandardArgs) 24 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(BENCHMARK DEFAULT_MSG BENCHMARK_INCLUDE_DIRS BENCHMARK_LIBS) 25 | 26 | 27 | mark_as_advanced(BENCHMARK_INCLUDE_DIRS BENCHMARK_LIBS) 28 | -------------------------------------------------------------------------------- /med/sl/field_copy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | @file 5 | copy a field. 6 | 7 | @copyright Denis Priyomov 2016-2018 8 | Distributed under the MIT License 9 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 10 | */ 11 | 12 | #include "../field.hpp" 13 | 14 | namespace med::sl { 15 | 16 | template 17 | constexpr void field_copy(IE& to, IE const& from, ARGS&&... args) 18 | { 19 | if (from.is_set()) 20 | { 21 | if constexpr (AMultiField) 22 | { 23 | to.clear(); 24 | for (auto const& rhs : from) 25 | { 26 | auto* p = to.push_back(std::forward(args)...); 27 | p->copy(rhs, std::forward(args)...); 28 | } 29 | } 30 | else 31 | { 32 | return to.copy(from, std::forward(args)...); 33 | } 34 | } 35 | } 36 | 37 | } //namespace med::sl 38 | -------------------------------------------------------------------------------- /med/tag.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | tag related definitions 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "ie_type.hpp" 15 | #include "meta/typelist.hpp" 16 | #include "concepts.hpp" 17 | 18 | namespace med { 19 | 20 | template 21 | struct tag_getter 22 | { 23 | //TODO: skip if not tag? 24 | template 25 | using apply = meta::list_first_t>; 26 | }; 27 | 28 | template 29 | constexpr auto get_tag(T const& header) 30 | { 31 | if constexpr (AHasGetTag) 32 | { 33 | return header.get_tag(); 34 | } 35 | else 36 | { 37 | return header.get_encoded(); 38 | } 39 | } 40 | 41 | } //end: namespace med 42 | -------------------------------------------------------------------------------- /med/snapshot.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | snapshot of buffer state for inplace updating 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "length.hpp" 13 | #include "state.hpp" 14 | 15 | namespace med { 16 | 17 | //captures placement of IE in buffer during encoding to be updated in-place later in encoded data 18 | //used as attribute for IE via multiple inheritance 19 | //NOTE: requires IE to have name() defined! 20 | struct with_snapshot {}; 21 | 22 | template 23 | constexpr void put_snapshot(FUNC& func, IE& ie) 24 | { 25 | if constexpr (std::is_base_of_v) 26 | { 27 | func(SNAPSHOT{snapshot_id, sl::ie_length>(ie, func)}); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | code-coverage: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | compiler: 13 | - g++-12 14 | build_type: [Release] 15 | 16 | env: 17 | CXX: ${{ matrix.compiler }} 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - run: | 22 | sudo apt-get update 23 | sudo apt-get -y --no-upgrade install ${{ matrix.compiler }} 24 | sudo apt-get install -y libgtest-dev 25 | 26 | - run: cmake -E make_directory build 27 | 28 | - working-directory: build/ 29 | run: | 30 | cmake $GITHUB_WORKSPACE -DCMAKE_CXX_FLAGS="-coverage" -DCOVERAGE=ON 31 | cmake --build . 32 | ctest --output-on-failure 33 | bash <(curl -s https://codecov.io/bash) 34 | -------------------------------------------------------------------------------- /benchmark/results.txt: -------------------------------------------------------------------------------- 1 | model name : Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz 2 | 3 | 2022-09-06T13:04:40+03:00 4 | Running ./bm_med 5 | Run on (4 X 4200 MHz CPU s) 6 | CPU Caches: 7 | L1 Data 32 KiB (x4) 8 | L1 Instruction 32 KiB (x4) 9 | L2 Unified 256 KiB (x4) 10 | L3 Unified 8192 KiB (x1) 11 | Load Average: 0.73, 0.88, 0.82 12 | ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. 13 | --------------------------------------------------------- 14 | Benchmark Time CPU Iterations 15 | --------------------------------------------------------- 16 | BM_encode_ok 8.92 ns 8.91 ns 76065372 17 | BM_encode_fail 1167 ns 1166 ns 602014 18 | BM_decode_ok 20.6 ns 20.6 ns 33964187 19 | BM_decode_fail 1696 ns 1696 ns 411906 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'LICENSE' 7 | - 'README.md' 8 | - 'doc/**' 9 | pull_request: 10 | paths-ignore: 11 | - 'LICENSE' 12 | - 'README.md' 13 | - 'doc/**' 14 | 15 | jobs: 16 | linux: 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | compiler: 23 | - g++-12 24 | - clang++-15 25 | build_type: [Release] 26 | 27 | env: 28 | CXX: ${{ matrix.compiler }} 29 | 30 | steps: 31 | - uses: actions/checkout@v3 32 | - run: | 33 | sudo apt-get update 34 | sudo apt-get -y --no-upgrade install ${{ matrix.compiler }} 35 | sudo apt-get -y install libgtest-dev 36 | 37 | - run: cmake -E make_directory build 38 | 39 | - working-directory: build/ 40 | run: | 41 | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 42 | cmake --build . 43 | ctest --output-on-failure 44 | -------------------------------------------------------------------------------- /med/units.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | helper class to specify number of bits or bytes. 4 | 5 | @copyright Denis Priyomov 2016-2018 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace med { 17 | 18 | enum class nbits : std::size_t {}; 19 | enum class nbytes : std::size_t {}; 20 | 21 | template 22 | struct bits 23 | { 24 | static_assert(NUM_BITS > 0); 25 | static_assert(BIT_OFFSET < 8); 26 | }; 27 | 28 | template struct bytes {}; 29 | 30 | template struct min {}; 31 | 32 | template struct max : std::integral_constant {}; 33 | 34 | template struct arity : std::integral_constant {}; 35 | template struct pmax : std::integral_constant {}; 36 | using inf = pmax< std::numeric_limits::max() >; 37 | 38 | } //namespace med 39 | -------------------------------------------------------------------------------- /proto/main_proto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | 4 | #include 5 | 6 | #include "med.pb.h" 7 | 8 | std::string as_string(void const* p, std::size_t size) 9 | { 10 | std::string res; 11 | 12 | for (auto it = static_cast(p), ite = it + size; it != ite; ++it) 13 | { 14 | char bsz[4]; 15 | std::snprintf(bsz, sizeof(bsz), "%02X ", *it); 16 | res.append(bsz); 17 | } 18 | 19 | return res; 20 | } 21 | 22 | 23 | TEST(encode, first) 24 | { 25 | plain msg; 26 | 27 | msg.set_int_32(1); //1: 0108; -32:08 e0 ff ff ff ff ff ff ff ff 01 00 28 | msg.set_int_64(127); 29 | msg.set_uint_32(128); 30 | msg.set_uint_64(256); 31 | 32 | //std::stringstream out(std::ios_base::out | std::ios_base::binary); 33 | uint8_t out[1024]; 34 | ASSERT_TRUE(msg.SerializePartialToArray(out, sizeof(out))); 35 | std::printf("FIRST:\n%s\n", as_string(out, msg.ByteSize()).c_str()); 36 | } 37 | 38 | int main(int argc, char **argv) 39 | { 40 | GOOGLE_PROTOBUF_VERIFY_VERSION; 41 | 42 | ::testing::InitGoogleTest(&argc, argv); 43 | return RUN_ALL_TESTS(); 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Denis Priyomov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /med/debug.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | helper macros for debugging via printfs/logs 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | #include 12 | #include 13 | 14 | //#define CODEC_TRACE_ENABLE 15 | 16 | #define CODEC_TRACE(FMT, ...) CODEC_TRACE_FL(__FILE__, __LINE__, FMT, __VA_ARGS__) 17 | 18 | #ifdef CODEC_TRACE_ENABLE 19 | 20 | namespace med{ 21 | 22 | namespace debug { 23 | 24 | // NOTE! fname is expected to be NULL-terminated thus allowing to return C-string 25 | // this trick is only needed for constexpr since strrchr is not such. 26 | constexpr char const* filename(std::string_view fname) 27 | { 28 | if (auto pos = fname.rfind('/'); pos != std::string_view::npos) 29 | { 30 | return fname.substr(pos + 1).data(); 31 | } 32 | else 33 | { 34 | return fname.data(); 35 | } 36 | } 37 | 38 | } //end: namespace 39 | } //end: namespace med 40 | 41 | #define CODEC_TRACE_FL(F, L, FMT, ...) std::printf("%s:%u\t" FMT "\n", med::debug::filename(F), L, __VA_ARGS__) 42 | 43 | #else 44 | #define CODEC_TRACE_FL(...) 45 | #endif 46 | -------------------------------------------------------------------------------- /doc/Physical-Layer.md: -------------------------------------------------------------------------------- 1 | # 3. Physical layer 2 | This layer deals with the physical representation of the protocol data. 3 | Due to layered structure of the library the physical layer has no dependency on the layers above thus allowing to minimize the number of elements it needs to handle. The latter drastically simplifies definition of different encoders and decoders. 4 | As of now there is only octet encoder and decoder defined which covers most existing protocols used. 5 | There is also one special type of encoder supported by the library - printer. Its purpose is to dump decoded message contents into a human readable form. 6 | Yet again the regular user of the library doesn't need to know the details of this layer except the fact that there are only octet encoder and decoder currently defined and all elements of messages should have size multiple of 8 bits. Note that even though many protocols have bit fields they are not really bit-oriented as long as they are byte aligned (for example, RADIUS, DIAMETER, GTPC, NAS protocols are best processed with octet encoder/decoder). 7 | 8 | ## 3.1. Octet encoder/decoder 9 | Also note that only sizes multiple of 8 bits (an octet) can be used with octet encoder and decoder. 10 | 11 | ## 3.2. Printer 12 | -------------------------------------------------------------------------------- /med/protobuf/protobuf.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | Google Protobuf definitions 4 | 5 | @copyright Denis Priyomov 2018 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "../value.hpp" 15 | 16 | 17 | namespace med::protobuf { 18 | 19 | enum class wire_type : uint8_t 20 | { 21 | VARINT = 0, //int32, int64, uint32, uint64, sint32, sint64, bool, enum 22 | BITS_64 = 1, //fixed64, sfixed64, double 23 | LEN_DELIM = 2, //string, bytes, embedded messages, packed repeated fields 24 | GRP_START = 3, //groups (deprecated) 25 | GRP_END = 4, //groups (deprecated) 26 | BITS_32 = 5, //fixed32, sfixed32, float 27 | }; 28 | 29 | constexpr std::size_t MAX_VARINT_BYTES = 10; 30 | constexpr std::size_t MAX_VARINT32_BYTES = 5; 31 | 32 | using field_type = uint32_t; 33 | 34 | constexpr auto field_tag(std::size_t field_number, wire_type type) 35 | { 36 | return static_cast((field_number << 3) | static_cast(type)); 37 | } 38 | 39 | using int32 = value; 40 | using int64 = value; 41 | using uint32 = value; 42 | using uint64 = value; 43 | 44 | 45 | } //end: namespace med::protobuf 46 | -------------------------------------------------------------------------------- /med/hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | @file 4 | string hash computation utils 5 | 6 | @copyright Denis Priyomov 2018 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/ctstring) 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | namespace med { 15 | 16 | template 17 | class hash 18 | { 19 | public: 20 | using value_type = VALUE; 21 | static constexpr value_type init = 5381; 22 | 23 | static constexpr value_type compute(std::string_view const& sv) 24 | { 25 | return const_hash_impl(sv.data() + sv.size() - 1, sv.size()); 26 | } 27 | 28 | static constexpr value_type update(char const c, value_type hval) 29 | { 30 | return ((hval << 5) + hval) + std::size_t(c); //33*hash + c 31 | } 32 | 33 | //Fowler–Noll–Vo : http://isthe.com/chongo/tech/comp/fnv/ 34 | // constexpr unsigned hash(int n=0, unsigned h=2166136261) 35 | // { 36 | // return n == size ? h : hash(n+1,(h * 16777619) ^ (sv[n])); 37 | // } 38 | 39 | private: 40 | static constexpr value_type const_hash_impl(char const* end, std::size_t count) 41 | { 42 | return count > 0 43 | ? value_type(end[0]) + 33 * const_hash_impl(end - (count > 1 ? 1 : 0), count - 1) 44 | : init; 45 | } 46 | 47 | 48 | }; 49 | 50 | } //end: namespace med 51 | 52 | -------------------------------------------------------------------------------- /med/accessor.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | accessors for fields in container 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "field.hpp" 15 | 16 | namespace med { 17 | 18 | //read-only access optional field returning a pointer or null if not set 19 | template requires (!AMultiField && AOptional) 20 | FIELD const* get_field(IE const& ie) 21 | { 22 | return ie.is_set() ? &ie : nullptr; 23 | } 24 | 25 | //read-only access mandatory field returning a reference 26 | template requires (!AMultiField && !AOptional) 27 | FIELD const& get_field(IE const& ie) 28 | { 29 | return ie; 30 | } 31 | 32 | //read-only access of any multi-field 33 | template 34 | constexpr IE const& get_field(IE const& ie) 35 | { 36 | return ie; 37 | } 38 | 39 | namespace sl { 40 | 41 | template 42 | struct field_at 43 | { 44 | template 45 | using type = std::is_same>; 46 | template 47 | static constexpr bool value = std::is_same_v>; 48 | }; 49 | 50 | } //end: namespace sl 51 | 52 | } //end: namespace med 53 | -------------------------------------------------------------------------------- /med/name.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | helper traits to extract IE name 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "concepts.hpp" 17 | 18 | namespace med { 19 | 20 | //TODO: replace with string_view 21 | template 22 | const char* class_name() 23 | { 24 | static char const* psz = nullptr; 25 | static char sz[128]; 26 | if (!psz) 27 | { 28 | psz = typeid(T).name(); 29 | if (char* sane = abi::__cxa_demangle(psz, nullptr, nullptr, nullptr)) 30 | { 31 | std::strncpy(sz, sane, sizeof(sz)-1); 32 | sz[sizeof(sz) - 1] = '\0'; 33 | std::free(sane); 34 | psz = sz; 35 | } 36 | } 37 | 38 | return psz; 39 | } 40 | 41 | template 42 | concept AHasName = requires(T v) 43 | { 44 | { T::name() } -> std::same_as; 45 | }; 46 | 47 | template 48 | constexpr char const* name() 49 | { 50 | if constexpr (AHasName) 51 | { 52 | return IE::name(); 53 | } 54 | else if constexpr (AHasFieldType) 55 | { 56 | //gradually peel-off indirections looking for ::name() in each step 57 | return name(); 58 | } 59 | else 60 | { 61 | return class_name(); 62 | } 63 | } 64 | 65 | } //end: namespace med 66 | -------------------------------------------------------------------------------- /med/traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "concepts.hpp" 4 | 5 | namespace med { 6 | 7 | enum class mik //kind of meta-information 8 | { 9 | TAG, 10 | LEN, 11 | }; 12 | 13 | //meta-information holder 14 | template 15 | struct mi 16 | { 17 | using info_type = T; 18 | static constexpr mik kind = KIND; 19 | }; 20 | 21 | template 22 | using get_info_t = typename T::info_type; 23 | 24 | template 25 | using add_tag = mi; 26 | 27 | template 28 | using add_len = mi; 29 | 30 | template 31 | struct add_meta_info 32 | { 33 | using meta_info = meta::typelist; 34 | }; 35 | 36 | template 37 | struct get_meta_info 38 | { 39 | using type = meta::typelist<>; 40 | }; 41 | 42 | template requires requires(T) { typename T::meta_info; } 43 | struct get_meta_info 44 | { 45 | using type = typename T::meta_info; 46 | }; 47 | 48 | template 49 | concept AKind = std::same_as; 50 | 51 | template 52 | struct get_meta_info 53 | { 54 | using type = meta::typelist; 55 | }; 56 | template 57 | using get_meta_info_t = typename get_meta_info::type; 58 | 59 | template 60 | struct get_meta_tag 61 | { 62 | using type = void; 63 | }; 64 | 65 | template requires (!AEmptyTypeList) 66 | struct get_meta_tag 67 | { 68 | using type = conditional_t::kind == mik::TAG, get_info_t>, void>; 69 | }; 70 | template using get_meta_tag_t = typename get_meta_tag::type; 71 | 72 | } //end: namespace med 73 | -------------------------------------------------------------------------------- /med/decoder_context.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | context for decoding 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "allocator.hpp" 13 | #include "buffer.hpp" 14 | #include "snapshot.hpp" 15 | 16 | namespace med { 17 | 18 | template < 19 | class ALLOCATOR = const null_allocator, 20 | class BUFFER = buffer 21 | > 22 | class decoder_context : public detail::allocator_holder 23 | { 24 | public: 25 | using allocator_type = ALLOCATOR; 26 | using buffer_type = BUFFER; 27 | using state_t = typename buffer_type::state_type; 28 | 29 | decoder_context() noexcept : decoder_context(nullptr, 0){} 30 | decoder_context(void const* p, size_t s, allocator_type* a = nullptr) noexcept 31 | : detail::allocator_holder{a} { reset(p, s); } 32 | template 33 | decoder_context(T const (&p)[SIZE], allocator_type* a = nullptr) noexcept 34 | : decoder_context(p, sizeof(p), a) {} 35 | template 36 | decoder_context(std::span p, allocator_type* a = nullptr) noexcept 37 | : decoder_context(p.data(), p.size_bytes(), a) {} 38 | 39 | buffer_type& buffer() noexcept { return m_buffer; } 40 | 41 | template 42 | constexpr void reset(Ts&&... args)noexcept{ buffer().reset(std::forward(args)...); } 43 | 44 | private: 45 | decoder_context(decoder_context const&) = delete; 46 | decoder_context& operator=(decoder_context const&) = delete; 47 | 48 | buffer_type m_buffer; 49 | }; 50 | 51 | } //namespace med 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [ master ] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [ master ] 21 | schedule: 22 | - cron: '31 6 * * 2' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'cpp' ] 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v2 37 | 38 | # Initializes the CodeQL tools for scanning. 39 | - name: Initialize CodeQL 40 | uses: github/codeql-action/init@v1 41 | with: 42 | languages: ${{ matrix.language }} 43 | 44 | # build UTs for MED library 45 | - run: | 46 | sudo apt-get update 47 | sudo apt-get install -y libgtest-dev 48 | 49 | - run: cmake -E make_directory build 50 | - working-directory: build/ 51 | run: | 52 | cmake $GITHUB_WORKSPACE 53 | cmake --build . 54 | ctest --output-on-failure 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v1 58 | -------------------------------------------------------------------------------- /med/state.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | pseudo-states for encoding/decoding buffer 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | namespace med { 16 | 17 | //Memorize the current state of buffer if not at the end (one entry in buffer itself). 18 | struct PUSH_STATE {}; 19 | //Restore the last saved state of buffer if any. 20 | struct POP_STATE {}; 21 | //Check if the buffer state is valid (not EoF) 22 | struct CHECK_STATE {}; 23 | //Return current state of buffer. 24 | struct GET_STATE {}; 25 | //Reset state of buffer to the given one passed as argument. 26 | struct SET_STATE {}; 27 | //Advance the buffer state by relative number of codec units 28 | struct ADVANCE_STATE 29 | { 30 | int delta; 31 | }; 32 | 33 | //Get length of IE in codec units 34 | struct GET_LENGTH {}; 35 | //Set end of buffer (its size). 36 | struct PUSH_SIZE 37 | { 38 | std::size_t size; //in codec units 39 | bool commit{true}; //commit the size or delay 40 | }; 41 | 42 | //Pad buffer with specfied number of bits using filler value. 43 | struct ADD_PADDING 44 | { 45 | uint8_t pad_size; //in codec units 46 | uint8_t filler; 47 | }; 48 | 49 | 50 | //Save current buffer state referred by id. 51 | //It's similar to PUSH_STATE but saved in optional storage provided by user 52 | //and associated with identifier for later retrieval. 53 | struct SNAPSHOT 54 | { 55 | using id_type = char const*; 56 | 57 | id_type id; 58 | std::size_t size; 59 | }; 60 | 61 | template 62 | constexpr SNAPSHOT::id_type snapshot_id = IE::name(); 63 | 64 | } //end: namespace med 65 | -------------------------------------------------------------------------------- /med/count.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | counter related primitives 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include "config.hpp" 16 | #include "concepts.hpp" 17 | #include "name.hpp" 18 | #include "exception.hpp" 19 | 20 | namespace med { 21 | 22 | 23 | template 24 | struct counter_t 25 | { 26 | using counter_type = COUNTER; 27 | }; 28 | 29 | namespace detail { 30 | 31 | template 32 | constexpr void check_n_arity(FUNC&, IE const&, std::size_t count) 33 | { 34 | if (count >= IE::min) 35 | { 36 | if (count > IE::max) 37 | { 38 | MED_THROW_EXCEPTION(extra_ie, name(), IE::max, count) 39 | } 40 | } 41 | else 42 | { 43 | MED_THROW_EXCEPTION(missing_ie, name(), IE::min, count) 44 | } 45 | } 46 | 47 | } //end: namespace detail 48 | 49 | //multi-field 50 | template 51 | constexpr void check_arity(FUNC& func, IE const& ie, std::size_t count) 52 | { 53 | if constexpr (AOptional) 54 | { 55 | if (count) { detail::check_n_arity(func, ie, count); } 56 | } 57 | else 58 | { 59 | detail::check_n_arity(func, ie, count); 60 | } 61 | } 62 | 63 | template 64 | constexpr void check_arity(FUNC& func, IE const& ie) 65 | { 66 | check_arity(func, ie, ie.count()); 67 | } 68 | 69 | 70 | template 71 | constexpr std::size_t field_count(FIELD const& field) 72 | { 73 | if constexpr (AHasCount) 74 | { 75 | return field.count(); 76 | } 77 | else 78 | { 79 | return field.is_set() ? 1 : 0; 80 | } 81 | } 82 | 83 | } //namespace med 84 | -------------------------------------------------------------------------------- /doc/Overview.md: -------------------------------------------------------------------------------- 1 | # MED 2 | > Baby, did you forget to take your meds? 3 | > *(c) Placebo* 4 | 5 | The Meta-Encoder/Decoder (`med`) is a header only library to define non-ASN.1 messages with independent invocation of required encoder, decoder or printer that are automatically generated via meta-programming. 6 | 7 | The primary goal of the library design is to provide ultimate run-time performance on the par with hand-written code yet allowing to easily define complex messages taking care off the user for protocol validation. 8 | 9 | The next goal is to mimic ASN.1 design where message definition is abstracted from its physical representation. 10 | 11 | Because of performance requirements the library uses no dynamic memory allocation and exceptions for error reporting. 12 | Due to the fact that the library is based on templates any real protocol defined with it will put a noticeable load on the C++ compiler increasing the compilation times. To mitigate this it's advised to keep user code instantiating encoder/decoder in separate files. The latter also helps to mitigate possible code-bloat. 13 | 14 | The user of the library needs only to define the structure of the messages consisting particular protocol. All the checks for presence of mandatory and conditional fields and their validation will be performed automatically during decoding and even during encoding by the generated code. When message fails to decode it's still possible to print a part of it which is useful for diagnostic purposes. 15 | 16 | The library is split in 3 layers: 17 | 18 | 1. The [representation layer](Representation-Layer.md) to describe a message from the collection of building blocks, their presence and inter-dependencies if any. 19 | 20 | 2. The [structural layer](Structural-Layer.md) to provide internal means to traverse constructed protocol, messages and fields. 21 | 22 | 3. The [physical layer](Physical-Layer.md) to encode or decode the underlying primitives. 23 | -------------------------------------------------------------------------------- /ut/protobuf.cpp: -------------------------------------------------------------------------------- 1 | #include "ut.hpp" 2 | 3 | #include "protobuf/protobuf.hpp" 4 | #include "protobuf/encoder.hpp" 5 | #include "protobuf/decoder.hpp" 6 | 7 | using namespace med::protobuf; 8 | 9 | namespace pb { 10 | 11 | /* 12 | message plain { 13 | int32 int_32 = 1; 14 | int64 int_64 = 2; 15 | uint32 uint_32 = 3; 16 | uint64 uint_64 = 4; 17 | } 18 | */ 19 | 20 | template 21 | using T = med::value>; 22 | 23 | struct plain : med::sequence< 24 | O< T<1, wire_type::VARINT>, int32 >, 25 | O< T<2, wire_type::VARINT>, int64 >, 26 | O< T<3, wire_type::VARINT>, uint32 >, 27 | O< T<4, wire_type::VARINT>, uint64 > 28 | >{}; 29 | 30 | uint8_t const plain_encoded[] = { 31 | 0x08, 0x01, //(T{1}<<3)|Varint{0}, value{1} 32 | 0x10, 0x7f, //(T{2}<<3)|Varint{0}, value{127} 33 | 0x18, 0x80, 0x01, //(T{3}<<3)|Varint{0}, value{128} 34 | 0x20, 0x80, 0x02, //(T{4}<<3)|Varint{0}, value{256} 35 | }; 36 | 37 | } //end: namespace pb 38 | 39 | #define OPT_CHECK(MSG, FIELD, VALUE) \ 40 | { auto* p = MSG.get(); \ 41 | ASSERT_NE(nullptr, p); \ 42 | EXPECT_EQ(VALUE, p->get());} \ 43 | /* OPT_CHECK */ 44 | 45 | TEST(protobuf, encode_plain) 46 | { 47 | pb::plain msg; 48 | msg.ref().set(1); 49 | msg.ref().set(127); 50 | msg.ref().set(128); 51 | msg.ref().set(256); 52 | 53 | uint8_t buffer[128] = {}; 54 | med::encoder_context<> ctx{ buffer }; 55 | 56 | encode(med::protobuf::encoder{ctx}, msg); 57 | EXPECT_EQ(sizeof(pb::plain_encoded), ctx.buffer().get_offset()); 58 | EXPECT_TRUE(Matches(pb::plain_encoded, buffer)); 59 | } 60 | 61 | TEST(protobuf, decode_plain) 62 | { 63 | med::decoder_context<> ctx{ pb::plain_encoded }; 64 | 65 | pb::plain msg; 66 | 67 | decode(med::protobuf::decoder{ctx}, msg); 68 | 69 | pb::plain const& cmsg = msg; 70 | 71 | OPT_CHECK(cmsg, int32, 1); 72 | OPT_CHECK(cmsg, int64, 127); 73 | OPT_CHECK(cmsg, uint32, 128); 74 | OPT_CHECK(cmsg, uint64, 256); 75 | } 76 | -------------------------------------------------------------------------------- /med/bytes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | @file 4 | helpers to read and write bytes(octets) 5 | 6 | @copyright Denis Priyomov 2022 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 9 | */ 10 | 11 | #include 12 | 13 | #include "value_traits.hpp" 14 | 15 | namespace med { 16 | 17 | template 18 | concept AIeValue = requires(T t) 19 | { 20 | typename T::value_type; 21 | typename T::traits; 22 | { T::traits::bits + 0 } -> std::unsigned_integral; 23 | }; 24 | 25 | template 26 | constexpr void get_byte(uint8_t const*, uint8_t*) { } 27 | 28 | template 29 | constexpr void get_byte(uint8_t const* input, uint8_t* out) 30 | { 31 | constexpr uint8_t SHIFT = NUM_BYTES - OFS - 1; 32 | out[SHIFT] = input[OFS]; 33 | get_byte(input, out); 34 | } 35 | 36 | template 37 | constexpr VALUE get_bytes(uint8_t const* input) 38 | { 39 | return [input](std::index_sequence) 40 | { 41 | union { 42 | VALUE value{}; 43 | uint8_t bytes[sizeof(value)]; 44 | } out; 45 | get_byte(input, out.bytes); 46 | return out.value; 47 | }(std::make_index_sequence{}); 48 | } 49 | 50 | template 51 | constexpr void put_byte(uint8_t*, uint8_t const*) { } 52 | 53 | template 54 | constexpr void put_byte(uint8_t* output, uint8_t const* inp) 55 | { 56 | output[OFS] = inp[NUM_BYTES - OFS - 1]; 57 | put_byte(output, inp); 58 | } 59 | 60 | template 61 | constexpr void put_bytes(std::size_t value, uint8_t* output) 62 | { 63 | [](std::size_t val, uint8_t* out, std::index_sequence) 64 | { 65 | union { 66 | std::size_t value; 67 | uint8_t bytes[sizeof(value)]; 68 | } inp; 69 | inp.value = val; 70 | 71 | put_byte(out, inp.bytes); 72 | }(value, output, std::make_index_sequence{}); 73 | } 74 | 75 | } //end: namespace med 76 | -------------------------------------------------------------------------------- /ut/unique.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ut.hpp" 6 | #include "ut_proto.hpp" 7 | 8 | #include "meta/typelist.hpp" 9 | #include "meta/unique.hpp" 10 | #include "sl/octet_info.hpp" 11 | 12 | 13 | TEST(unique, typed) 14 | { 15 | //checking tag uniqueness 16 | using tags = med::meta::typelist< 17 | med::add_meta_info< med::add_tag> >, 18 | med::add_meta_info< med::add_tag> >, 19 | med::add_meta_info< med::add_tag> > 20 | >; 21 | static_assert(std::is_void_v, tags>>); 22 | 23 | //fails to compile with duplicate tag in error message 24 | // using dup_tags = med::meta::typelist< 25 | // med::add_meta_info< med::add_tag> >, 26 | // med::add_meta_info< med::add_tag> >, 27 | // med::add_meta_info< med::add_tag> >, 28 | // med::add_meta_info< med::add_tag> >, 29 | // med::add_meta_info< med::add_tag> > 30 | // >; 31 | // static_assert(not std::is_void_v, dup_tags>>); 32 | } 33 | 34 | TEST(unique, static_odd) 35 | { 36 | using namespace med::meta; 37 | static_assert(are_unique(nullptr, 1u, 2, 3, 4, 5)); 38 | static_assert(are_unique(1u, 2, 3, nullptr, 4, 5)); 39 | static_assert(are_unique(1u, 2, 3, 4, 5, nullptr)); 40 | static_assert(not are_unique(5, 2, 3, 4, 5)); 41 | static_assert(not are_unique(1, 5, 3, 4, 5)); 42 | static_assert(not are_unique(1, 2, 5, 4, 5)); 43 | static_assert(not are_unique(1, 2, 3, 5, 5)); 44 | } 45 | 46 | TEST(unique, static_even) 47 | { 48 | using namespace med::meta; 49 | static_assert(are_unique(1, 2, 3, 4)); 50 | static_assert(not are_unique(4, 2, 3, 4)); 51 | static_assert(not are_unique(1, 4, 3, 4)); 52 | static_assert(not are_unique(1, 2, 4, 4)); 53 | } 54 | 55 | TEST(unique, dynamics) 56 | { 57 | std::random_device rdev; 58 | std::mt19937 rgen{rdev()}; 59 | 60 | std::vector v{1, 2, 3, 4, 5}; 61 | for (auto i = 0; i < 5; ++i) 62 | { 63 | std::shuffle(v.begin(), v.end(), rgen); 64 | EXPECT_TRUE(med::meta::are_unique(v[0], v[1], v[2], v[3], v[4])); 65 | } 66 | 67 | v = {1, 2, 3, 4, 1}; 68 | for (auto i = 0; i < 5; ++i) 69 | { 70 | std::shuffle(v.begin(), v.end(), rgen); 71 | EXPECT_FALSE(med::meta::are_unique(v[0], v[1], v[2], v[3], v[4])); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ut/meta.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "meta/typelist.hpp" 5 | 6 | 7 | TEST(meta, index) 8 | { 9 | using list_t = med::meta::typelist; 10 | static_assert(med::meta::list_size_v == 3); 11 | static_assert(med::meta::list_index_of_v == 0); 12 | static_assert(med::meta::list_index_of_v == 1); 13 | static_assert(med::meta::list_index_of_v == 2); 14 | static_assert(med::meta::list_index_of_v == med::meta::list_size_v); 15 | } 16 | 17 | template struct err; 18 | 19 | 20 | TEST(meta, append) 21 | { 22 | using list_t = med::meta::typelist; 23 | static_assert(std::is_same_v< 24 | med::meta::typelist, 25 | med::meta::append_t, med::meta::typelist> 26 | >); 27 | } 28 | 29 | TEST(meta, transform) 30 | { 31 | using list_t = med::meta::typelist; 32 | static_assert( 33 | std::is_same_v< 34 | med::meta::typelist, 35 | med::meta::transform_t 36 | >); 37 | } 38 | 39 | 40 | template 41 | struct tag_of 42 | { 43 | static constexpr auto c = constructed; 44 | V value; 45 | }; 46 | 47 | template 48 | struct make_t 49 | { 50 | static constexpr auto constructed = I::value; 51 | using type = tag_of; 52 | }; 53 | 54 | 55 | template 56 | struct not_last 57 | { 58 | using type = std::bool_constant; 59 | }; 60 | 61 | 62 | TEST(meta, interleave) 63 | { 64 | static_assert( 65 | std::is_same_v< 66 | med::meta::typelist, tag_of, tag_of>, 67 | med::meta::transform_indexed_t, make_t, not_last> 68 | >); 69 | 70 | static_assert( 71 | std::is_same_v< 72 | med::meta::typelist>, 73 | med::meta::transform_indexed_t, make_t, not_last> 74 | >); 75 | 76 | using list1 = med::meta::typelist; 77 | using list2 = med::meta::typelist; 78 | static_assert( 79 | std::is_same_v< 80 | med::meta::typelist, 81 | med::meta::interleave_t 82 | >); 83 | static_assert( 84 | std::is_same_v< 85 | med::meta::typelist, 86 | med::meta::interleave_t 87 | >); 88 | } 89 | -------------------------------------------------------------------------------- /med/macros_on.hpp: -------------------------------------------------------------------------------- 1 | //#pragma once 2 | #include 3 | 4 | #ifndef __MED_MACROS_ON_HPP_INCLUDED__ 5 | #define __MED_MACROS_ON_HPP_INCLUDED__ 6 | #undef __MED_MACROS_OFF_HPP_INCLUDED__ 7 | 8 | 9 | #define MED_BIT_MASK(bits) ((1u << (bits)) - 1) 10 | 11 | /** 12 | * Define setter and getter for a bit 13 | */ 14 | #define MED_BIT_ACCESS_DEF(NAME, BIT, SETTER, GETTER) \ 15 | bool NAME() const { return this->GETTER() & (1 << BIT); } \ 16 | void NAME(bool v) { this->SETTER( v ? (this->GETTER() | (1 << BIT)) : (this->GETTER() & ~(1 << BIT)) ); } \ 17 | /* MED_BIT_ACCESS_DEF */ 18 | #define MED_BIT_ACCESS_DEF_1(NAME) MED_BIT_ACCESS_DEF(NAME, 0, set, get) 19 | #define MED_BIT_ACCESS_DEF_2(NAME, BIT) MED_BIT_ACCESS_DEF(NAME, BIT, set, get) 20 | #define MED_BIT_ACCESS_DEF_3(NAME, BIT, SETTER) MED_BIT_ACCESS_DEF(NAME, BIT, SETTER, get) 21 | #define MED_BIT_ACCESS_DEF_4(NAME, BIT, SETTER, GETTER) MED_BIT_ACCESS_DEF(NAME, BIT, SETTER, GETTER) 22 | 23 | #define MED_BIT_ACCESSOR(...) BOOST_PP_OVERLOAD(MED_BIT_ACCESS_DEF_, __VA_ARGS__)(__VA_ARGS__) 24 | 25 | /** 26 | * Define setter and getter for number of BITS starting from leaDEF_st significant at SHIFT 27 | */ 28 | #define MED_BITS_ACCESS_DEF(NAME, BITS, SHIFT, SETTER, GETTER) \ 29 | value_type NAME() const { return (this->GETTER() >> SHIFT) & MED_BIT_MASK(BITS); } \ 30 | void NAME(value_type v) { this->SETTER( (this->GETTER() & ~(MED_BIT_MASK(BITS) << SHIFT)) | ((v & MED_BIT_MASK(BITS)) << SHIFT) ); } \ 31 | /* MED_BITS_ACCESS_DEF */ 32 | #define MED_BITS_ACCESS_DEF_2(NAME, BITS) MED_BITS_ACCESS_DEF(NAME, BITS, 0, set, get) 33 | #define MED_BITS_ACCESS_DEF_3(NAME, BITS, SHIFT) MED_BITS_ACCESS_DEF(NAME, BITS, SHIFT, set, get) 34 | #define MED_BITS_ACCESS_DEF_4(NAME, BITS, SHIFT, SETTER) MED_BITS_ACCESS_DEF(NAME, BITS, SHIFT, SETTER, get) 35 | #define MED_BITS_ACCESS_DEF_5(NAME, BITS, SHIFT, SETTER, GETTER) MED_BITS_ACCESS_DEF(NAME, BITS, SHIFT, SETTER, GETTER) 36 | #define MED_BITS_ACCESSOR(...) BOOST_PP_OVERLOAD(MED_BITS_ACCESS_DEF_, __VA_ARGS__)(__VA_ARGS__) 37 | 38 | 39 | /** 40 | * Define setter and getter for least significant BITS ORing with MASK in setter 41 | */ 42 | #define MED_BITS_ACCESSOR_MASK(NAME, BITS, MASK) \ 43 | value_type NAME() const { return this->get() & MED_BIT_MASK(BITS); } \ 44 | void NAME(value_type v) { this->set( (this->get() & ~MED_BIT_MASK(BITS)) | MASK | (v & MED_BIT_MASK(BITS)) ); } \ 45 | /* MED_BITS_ACCESSOR_MASK */ 46 | 47 | 48 | #endif //__MED_MACROS_ON_HPP_INCLUDED__ 49 | 50 | -------------------------------------------------------------------------------- /med/asn/ber/ber_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "value.hpp" 5 | #include "field.hpp" 6 | #include "length.hpp" 7 | #include "ber_tag.hpp" 8 | 9 | namespace med::asn::ber { 10 | 11 | struct info 12 | { 13 | private: 14 | template 15 | struct make_tag 16 | { 17 | template 18 | using tag_of = value< fixed< V::value, bytes > >; 19 | using type = add_tag< tag_of, CONSTRUCTED::value>> >; 20 | }; 21 | 22 | template 23 | struct not_last 24 | { 25 | using type = std::bool_constant; 26 | }; 27 | 28 | 29 | public: 30 | template 31 | static constexpr auto produce_meta_info() 32 | { 33 | using asn_traits = get_meta_info_t; 34 | /* 35 | 8.14 Encoding of a value of a prefixed type 36 | 8.14.2 Encoding of tagged value is derived from complete encoding of corresponding value of the type 37 | appearing in "TaggedType" notation (called the base encoding) as specified in 8.14.3 and 8.14.4. 38 | 8.14.3 If implicit tagging (see X.680, 31.2.7) was not used, then: 39 | the encoding is constructed and contents octets is the complete base encoding. 40 | 8.14.4 If implicit tagging was used, then: 41 | a) the encoding is constructed if the base encoding is constructed, and primitive otherwise; 42 | b) the contents octets shall be the same as the contents octets of the base encoding. 43 | */ 44 | if constexpr (meta::list_is_empty_v) 45 | { 46 | return meta::wrap{}; 47 | } 48 | else 49 | { 50 | constexpr auto get_tags = [] 51 | { 52 | /* Rec. ITU-T X.690 (08/2015) 53 | 8.9.1 The encoding of a sequence value shall be constructed. 54 | 8.10.1 The encoding of a sequence-of value shall be constructed. 55 | 8.11.1 The encoding of a set value shall be constructed. 56 | 8.12.1 The encoding of a set-of value shall be constructed. 57 | */ 58 | constexpr bool is_constructed = AContainer || is_seqof_v; 59 | if constexpr (is_constructed) 60 | { 61 | return meta::wrap>{}; 62 | } 63 | else 64 | { 65 | return meta::wrap>{}; 66 | } 67 | }; 68 | 69 | //!TODO: LENSIZE need to calc len to known its size (only 1 byte for now) 70 | using len_t = add_len>; 71 | using meta_info = meta::interleave_t< meta::unwrap_t, len_t>; 72 | return meta::wrap{}; 73 | } 74 | } 75 | }; 76 | 77 | } //end: namespace med::asn::ber 78 | -------------------------------------------------------------------------------- /med/ie_type.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | elementary IE types 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "value_traits.hpp" 13 | #include "concepts.hpp" 14 | #include "exception.hpp" 15 | 16 | namespace med { 17 | 18 | 19 | //meta-selectors for IEs 20 | struct PRIMITIVE {}; //represents value/data which is handled by physical layer of codec 21 | 22 | //selectors for IEs 23 | struct IE_NULL : PRIMITIVE {}; 24 | struct IE_VALUE : PRIMITIVE {}; 25 | struct IE_OCTET_STRING : PRIMITIVE {}; 26 | struct IE_BIT_STRING : PRIMITIVE {}; 27 | 28 | struct CONTAINER {}; //represents a container of IEs (holds internally) 29 | //selectors for IEs 30 | struct IE_CHOICE : CONTAINER {}; 31 | struct IE_SEQUENCE : CONTAINER {}; 32 | struct IE_SET : CONTAINER {}; 33 | 34 | template 35 | concept AContainer = std::is_base_of_v; 36 | 37 | //structure layer selectors 38 | struct IE_TAG {}; //tag 39 | struct IE_LEN {}; //length 40 | 41 | template 42 | struct IE 43 | { 44 | using ie_type = IE_TYPE; 45 | }; 46 | 47 | //always-set empty IE as a message w/o body 48 | template 49 | struct empty : IE 50 | { 51 | using traits = value_traits, EXT_TRAITS...>; 52 | using value_type = void; 53 | 54 | static constexpr void clear() { } 55 | static constexpr void set() { } 56 | static constexpr bool get() { return true; } 57 | static constexpr bool is_set() { return true; } 58 | template 59 | static constexpr void copy(empty const&, ARGS&&...) { } 60 | }; 61 | 62 | template 63 | constexpr bool is_empty_v = std::is_same_v; 64 | 65 | //check if type used in meta-information is also used inside container 66 | //this means we shouldn't encode this meta-data implicitly 67 | template 68 | constexpr bool explicit_meta_in() 69 | { 70 | if constexpr (!meta::list_is_empty_v && AContainer) 71 | { 72 | using ft = get_info_t>; 73 | return CONT::template has(); 74 | } 75 | return false; 76 | } 77 | 78 | template < 79 | class IE_TYPE, 80 | class META_INFO = meta::typelist<>, 81 | class EXP_TAG = void, 82 | class EXP_LEN = void, 83 | class DEPENDENT = void, 84 | class DEPENDENCY = void 85 | > 86 | struct type_context 87 | { 88 | using ie_type = IE_TYPE; 89 | using meta_info_type = META_INFO; 90 | using explicit_tag_type = EXP_TAG; 91 | using explicit_length_type = EXP_LEN; 92 | using dependent_type = DEPENDENT; 93 | using dependency_type = DEPENDENCY; 94 | }; 95 | 96 | 97 | } //end: namespace med 98 | -------------------------------------------------------------------------------- /med/asn/ber/ber_length.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | @file 4 | ASN.1 BER tag definition 5 | 6 | @copyright Denis Priyomov 2018 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | 15 | namespace med::asn::ber { 16 | 17 | //no support for indefinite length as seems useless so far 18 | constexpr std::size_t bits_in_byte = 8; 19 | 20 | namespace detail { 21 | 22 | template 23 | constexpr uint8_t calc_least_bits(T x) 24 | { 25 | if constexpr (std::is_signed()) 26 | { 27 | if constexpr (sizeof(T) <= sizeof(int)) 28 | { 29 | #ifdef __clang__ 30 | return bits_in_byte * sizeof(int) - ((x != 0 && x != -1) 31 | ? (__builtin_clz(x > 0 ? x : ~x) - 1) 32 | : bits_in_byte * sizeof(int) - 1); 33 | #else 34 | return bits_in_byte * sizeof(int) - (__builtin_clz(x >= 0 ? x : ~x) - 1); 35 | #endif 36 | } 37 | else if constexpr (sizeof(T) <= sizeof(long)) 38 | { 39 | #ifdef __clang__ 40 | return bits_in_byte * sizeof(long) - ((x != 0 && x != -1) 41 | ? (__builtin_clzl(x > 0 ? x : ~x) - 1) 42 | : bits_in_byte * sizeof(long) - 1); 43 | #else 44 | return bits_in_byte * sizeof(long) - (__builtin_clzl(x >= 0 ? x : ~x) - 1); 45 | #endif 46 | } 47 | else 48 | { 49 | #ifdef __clang__ 50 | return bits_in_byte * sizeof(long long) - ((x != 0 && x != -1) 51 | ? (__builtin_clzll(x > 0 ? x : ~x) - 1) 52 | : bits_in_byte * sizeof(long long) - 1); 53 | #else 54 | return bits_in_byte * sizeof(long long) - (__builtin_clzll(x >= 0 ? x : ~x) - 1); 55 | #endif 56 | } 57 | } 58 | else 59 | { 60 | if constexpr (sizeof(T) <= sizeof(int)) 61 | { 62 | return x ? (bits_in_byte * sizeof(int) - __builtin_clz(x)) : 1; 63 | } 64 | else if constexpr (sizeof(T) <= sizeof(long)) 65 | { 66 | return x ? (bits_in_byte * sizeof(long) - __builtin_clzl(x)) : 1; 67 | } 68 | else 69 | { 70 | return x ? (bits_in_byte * sizeof(long long) - __builtin_clzll(x)) : 1; 71 | } 72 | } 73 | } 74 | 75 | template 76 | constexpr uint8_t least_bytes_encoded(T value) 77 | { 78 | return (calc_least_bits(value) + 6) / 7; 79 | } 80 | 81 | } //end: namespace detail 82 | 83 | namespace length { 84 | 85 | template 86 | constexpr uint8_t bits(INT x) { return detail::calc_least_bits(x); } 87 | 88 | template 89 | constexpr uint8_t bytes(INT x) 90 | { 91 | #if defined(__GNUC__) //FIXME: gcc optimizer bug for x=0 :( 92 | return x ? (bits(x) + (bits_in_byte - 1)) / bits_in_byte : 1; 93 | #else 94 | return (bits(x) + (bits_in_byte - 1)) / bits_in_byte; 95 | #endif 96 | } 97 | 98 | } //end: namespace length 99 | 100 | } //end: namespace med::asn::ber 101 | -------------------------------------------------------------------------------- /ut/bits.cpp: -------------------------------------------------------------------------------- 1 | #include "ut.hpp" 2 | #include "bit_string.hpp" 3 | 4 | 5 | constexpr std::size_t MIXED = 0b1110'1110'0111'0111'0011'0011'0101'0101; 6 | 7 | TEST(bits, variable) 8 | { 9 | med::bits_variable bits; 10 | EXPECT_FALSE(bits.is_set()); 11 | EXPECT_EQ(nullptr, bits.data()); 12 | EXPECT_EQ(med::nbits{0}, bits.least_bits()); 13 | 14 | { 15 | bits.uint(med::nbits{1}, MIXED); 16 | EXPECT_TRUE(bits.is_set()); 17 | EXPECT_EQ(0b1, bits.uint()); 18 | EXPECT_EQ(med::nbits{1}, bits.least_bits()); 19 | EXPECT_EQ(1, bits.size()); 20 | uint8_t const exp[] = {0b1000'0000}; 21 | ASSERT_TRUE(Matches(exp, bits.data())); 22 | } 23 | { 24 | bits.uint(med::nbits{7}, MIXED); 25 | EXPECT_EQ(0b1010101, bits.uint()); 26 | EXPECT_EQ(med::nbits{7}, bits.least_bits()); 27 | EXPECT_EQ(1, bits.size()); 28 | uint8_t const exp[] = {0b1010'1010}; 29 | ASSERT_TRUE(Matches(exp, bits.data())); 30 | } 31 | { 32 | bits.uint(med::nbits{8}, MIXED); 33 | EXPECT_EQ(0b1010101, bits.uint()); 34 | EXPECT_EQ(med::nbits{8}, bits.least_bits()); 35 | EXPECT_EQ(1, bits.size()); 36 | uint8_t const exp[] = {0b0101'0101}; 37 | ASSERT_TRUE(Matches(exp, bits.data())); 38 | } 39 | { 40 | bits.uint(med::nbits{19}, MIXED); 41 | EXPECT_EQ(0b111'0011'0011'0101'0101, bits.uint()); 42 | EXPECT_EQ(med::nbits{3}, bits.least_bits()); 43 | EXPECT_EQ(3, bits.size()); 44 | uint8_t const exp[] = {0b1110'0110, 0b0110'1010, 0b1010'0000}; 45 | ASSERT_TRUE(Matches(exp, bits.data())); 46 | } 47 | 48 | bits.clear(); 49 | EXPECT_FALSE(bits.is_set()); 50 | EXPECT_EQ(nullptr, bits.data()); 51 | EXPECT_EQ(med::nbits{0}, bits.least_bits()); 52 | } 53 | 54 | TEST(bits, fixed_small) 55 | { 56 | { 57 | med::bits_fixed<7> bits; 58 | EXPECT_FALSE(bits.is_set()); 59 | EXPECT_EQ(nullptr, bits.data()); 60 | EXPECT_EQ(med::nbits{7}, bits.least_bits()); 61 | 62 | bits.uint(0b01010101); 63 | EXPECT_TRUE(bits.is_set()); 64 | EXPECT_EQ(0b01010101, bits.uint()); 65 | uint8_t const exp[] = {0b1010'1010}; 66 | static_assert (std::size(exp) == bits.size()); 67 | ASSERT_TRUE(Matches(exp, bits.data())); 68 | } 69 | { 70 | med::bits_fixed<17> bits; 71 | EXPECT_EQ(med::nbits{1}, bits.least_bits()); 72 | 73 | bits.uint(0b1'0100'1100'0111); 74 | EXPECT_EQ(0b1'0100'1100'0111, bits.uint()); 75 | //0'0001'0100'1100'0111 76 | uint8_t const exp[] = {0b0'0001'010, 0b0110'0011, 0b1000'0000}; 77 | static_assert (std::size(exp) == bits.size()); 78 | ASSERT_TRUE(Matches(exp, bits.data())); 79 | } 80 | } 81 | 82 | TEST(bits, bit_string) 83 | { 84 | { 85 | using bs_t = med::bit_string<>; 86 | bs_t bs; 87 | static_assert(std::is_same()); 88 | EXPECT_FALSE(bs.is_set()); 89 | } 90 | { 91 | using bs_t = med::bit_string, med::max<64>>; 92 | bs_t bs; 93 | static_assert(std::is_same>()); 94 | EXPECT_FALSE(bs.is_set()); 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /med/value_traits.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | traits for IE of integral values 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or copy at https://opensource.org/licenses/MIT) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include "units.hpp" 16 | #include "traits.hpp" 17 | 18 | namespace med { 19 | 20 | using num_octs_t = uint32_t; 21 | 22 | constexpr std::size_t bits_to_bytes(std::size_t num_bits) 23 | { 24 | return (num_bits + 7) / 8; 25 | } 26 | 27 | //number of least significant bits = number of MSBits in the last octet 28 | constexpr uint8_t calc_least_bits(std::size_t num_bits) 29 | { 30 | uint8_t const last_octet_bits = num_bits % 8; 31 | return last_octet_bits ? last_octet_bits : 8; 32 | } 33 | 34 | namespace detail { 35 | 36 | template 37 | struct bit_traits : EXT_TRAITS... 38 | { 39 | static constexpr std::size_t bits = BITS; 40 | static constexpr std::size_t offset = OFS; 41 | using value_type = T; 42 | }; 43 | 44 | template < 45 | typename VT, 46 | std::size_t MIN_BYTES = sizeof(VT), 47 | std::size_t MAX_BYTES = MIN_BYTES, 48 | class... EXT_TRAITS 49 | > 50 | struct octet_traits : EXT_TRAITS... 51 | { 52 | static constexpr std::size_t min_octets = MIN_BYTES; 53 | static constexpr std::size_t max_octets = MAX_BYTES; 54 | using value_type = VT; 55 | }; 56 | 57 | //infer value_type to hold given bits 58 | template struct bits_infer; 59 | 60 | template requires (BITS == 0) 61 | struct bits_infer 62 | { 63 | using type = void; 64 | }; 65 | 66 | template requires (BITS > 0 && BITS <= 8) 67 | struct bits_infer 68 | { 69 | using type = uint8_t; 70 | }; 71 | 72 | template requires (BITS > 8 && BITS <= 16) 73 | struct bits_infer 74 | { 75 | using type = uint16_t; 76 | }; 77 | 78 | template requires (BITS > 16 && BITS <= 32) 79 | struct bits_infer 80 | { 81 | using type = uint32_t; 82 | }; 83 | 84 | template requires (BITS > 32 && BITS <= 64) 85 | struct bits_infer 86 | { 87 | using type = uint64_t; 88 | }; 89 | 90 | template requires (BITS > 64) 91 | struct bits_infer 92 | { 93 | using type = std::array; 94 | }; 95 | 96 | } //end: namespace detail 97 | 98 | /****************************************************************************** 99 | * Class: value_traits 100 | * Description: select min integer type to fit BITS bits 101 | * Notes: BITS is number of bits 102 | ******************************************************************************/ 103 | template 104 | struct value_traits : detail::bit_traits {}; 105 | 106 | template 107 | struct value_traits, EXT_TRAITS...> 108 | : detail::bit_traits::type, BITS, OFS, EXT_TRAITS...> {}; 109 | 110 | template 111 | struct value_traits, EXT_TRAITS...> 112 | : detail::bit_traits::type, BYTES*8, 0, EXT_TRAITS...> {}; 113 | 114 | } //end: namespace med 115 | -------------------------------------------------------------------------------- /med/encoder_context.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | context for encoding 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | 13 | #include "allocator.hpp" 14 | #include "snapshot.hpp" 15 | #include "buffer.hpp" 16 | #include "debug.hpp" 17 | 18 | namespace med { 19 | 20 | template < 21 | class ALLOCATOR = const null_allocator, 22 | class BUFFER = buffer 23 | > 24 | class encoder_context : public detail::allocator_holder 25 | { 26 | public: 27 | using allocator_type = ALLOCATOR; 28 | using buffer_type = BUFFER; 29 | using state_t = typename buffer_type::state_type; 30 | 31 | private: 32 | struct snapshot_s 33 | { 34 | SNAPSHOT snapshot; 35 | state_t state; 36 | snapshot_s* next; 37 | }; 38 | 39 | public: 40 | encoder_context(encoder_context const&) = delete; 41 | encoder_context& operator=(encoder_context const&) = delete; 42 | 43 | constexpr encoder_context(void* p, size_t s, allocator_type* a = nullptr) noexcept 44 | : detail::allocator_holder{a} { reset(p, s); } 45 | 46 | template 47 | explicit constexpr encoder_context(T (&p)[SIZE], allocator_type* a = nullptr) noexcept 48 | : encoder_context(p, sizeof(p), a) {} 49 | 50 | constexpr buffer_type& buffer() noexcept { return m_buffer; } 51 | constexpr buffer_type const& buffer()const noexcept { return m_buffer; } 52 | 53 | template 54 | constexpr void reset(Ts... args) noexcept 55 | { 56 | buffer().reset(args...); 57 | m_snapshot = nullptr; 58 | } 59 | 60 | /** 61 | * Stores the buffer snapshot 62 | * @param snap 63 | */ 64 | constexpr void put_snapshot(SNAPSHOT snap) 65 | { 66 | CODEC_TRACE("snapshot %p{%zu}", static_cast(snap.id), snap.size); 67 | snapshot_s* p = create(this->get_allocator()); 68 | p->snapshot = snap; 69 | p->state = m_buffer.get_state(); 70 | 71 | p->next = m_snapshot ? m_snapshot->next : nullptr; 72 | m_snapshot = p; 73 | } 74 | 75 | class snap_s : public state_t 76 | { 77 | public: 78 | constexpr bool validate_length(size_t s) const 79 | { 80 | CODEC_TRACE("%s(%zu ? %zu)=%d", __FUNCTION__, m_length, s, m_length == s); 81 | return m_length == s; 82 | } 83 | 84 | private: 85 | friend class encoder_context; 86 | constexpr snap_s(state_t const& st, size_t len) : state_t{st}, m_length{len} {} 87 | constexpr snap_s() = default; 88 | 89 | size_t m_length{}; 90 | }; 91 | 92 | /** 93 | * Finds snapshot of the buffer state for the IE 94 | * @details Used in encoder to set the buffer state before updating 95 | * @param IE to retrieve its snapshot 96 | * @return snapshot or empty snapshot if not found 97 | */ 98 | template 99 | constexpr snap_s get_snapshot(IE const&) const 100 | { 101 | static_assert(std::is_base_of(), "IE WITH with_snapshot EXPECTED"); 102 | 103 | for (snapshot_s const* p = m_snapshot; p != nullptr; p = p->next) 104 | { 105 | if (p->snapshot.id == snapshot_id) { return snap_s{p->state, p->snapshot.size}; } 106 | } 107 | return snap_s{}; 108 | } 109 | 110 | private: 111 | using const_iterator = snapshot_s const*; 112 | 113 | buffer_type m_buffer; 114 | snapshot_s* m_snapshot{ nullptr }; 115 | }; 116 | 117 | } //namespace med 118 | -------------------------------------------------------------------------------- /med/protobuf/encoder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | Google Protobuf encoder definition 4 | 5 | @copyright Denis Priyomov 2018 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "debug.hpp" 13 | #include "name.hpp" 14 | #include "state.hpp" 15 | #include "octet_string.hpp" 16 | #include "sl/octet_info.hpp" 17 | 18 | namespace med::protobuf { 19 | 20 | template 21 | struct encoder : sl::octet_info 22 | { 23 | //required for length_encoder 24 | using state_type = typename ENC_CTX::buffer_type::state_type; 25 | using allocator_type = typename ENC_CTX::allocator_type; 26 | 27 | explicit encoder(ENC_CTX& ctx_) : m_ctx{ ctx_ } { } 28 | ENC_CTX& get_context() noexcept { return m_ctx; } 29 | allocator_type& get_allocator() { return get_context().get_allocator(); } 30 | 31 | //state 32 | auto operator() (GET_STATE) { return get_context().buffer().get_state(); } 33 | void operator() (SET_STATE, state_type const& st) { get_context().buffer().set_state(st); } 34 | template 35 | void operator() (SET_STATE, IE const& ie) 36 | { 37 | if (auto const ss = get_context().get_snapshot(ie)) 38 | { 39 | auto const len = field_length(ie, *this); 40 | if (ss.validate_length(len)) 41 | { 42 | get_context().buffer().set_state(ss); 43 | } 44 | else 45 | { 46 | MED_THROW_EXCEPTION(invalid_value, name(), len, get_context().buffer()) 47 | } 48 | } 49 | else 50 | { 51 | MED_THROW_EXCEPTION(missing_ie, name(), 1, 0, get_context().buffer()) 52 | } 53 | } 54 | 55 | template 56 | bool operator() (PUSH_STATE, IE const&) { return get_context().buffer().push_state(); } 57 | void operator() (POP_STATE) { get_context().buffer().pop_state(); } 58 | void operator() (ADVANCE_STATE ss) { get_context().buffer().template advance(ss.delta); } 59 | void operator() (SNAPSHOT ss) { get_context().put_snapshot(ss); } 60 | 61 | //IE_TAG/IE_LEN 62 | template void operator() (IE const& ie, IE_TAG) 63 | { (*this)(ie, typename IE::ie_type{}); } 64 | 65 | //IE_VALUE 66 | //Little Endian Base 128: https://en.wikipedia.org/wiki/LEB128 67 | template 68 | void operator() (IE const& ie, IE_VALUE) 69 | { 70 | static_assert(0 == (IE::traits::bits % 8), "OCTET VALUE EXPECTED"); 71 | auto value = ie.get_encoded(); 72 | CODEC_TRACE("VAL[%s]=%#zX(%zu) %zu bits: %s", name(), std::size_t(value), std::size_t(value), IE::traits::bits, get_context().buffer().toString()); 73 | //TODO: estimate exact size needed? will it be faster? 74 | while (value >= 0x80) 75 | { 76 | get_context().buffer().template push(value | 0x80); 77 | CODEC_TRACE("\twrote %#02X, value=%#zX", uint8_t(value|0x80), std::size_t(value >> 7)); 78 | value >>= 7; 79 | } 80 | get_context().buffer().template push(value); 81 | CODEC_TRACE("\twrote value %02X", uint8_t(value)); 82 | } 83 | 84 | //IE_OCTET_STRING 85 | template 86 | void operator() (IE const& ie, IE_OCTET_STRING) 87 | { 88 | uint8_t* out = get_context().buffer().template advance(ie.size()); 89 | octets::copy(out, ie.data(), ie.size()); 90 | CODEC_TRACE("STR[%s] %zu octets: %s", name(), ie.size(), get_context().buffer().toString()); 91 | } 92 | 93 | private: 94 | ENC_CTX& m_ctx; 95 | }; 96 | 97 | template explicit encoder(C&) -> encoder; 98 | 99 | } //end: namespace med::protobuf 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/cppden/med/workflows/Linux/badge.svg)](https://github.com/cppden/med/actions?query=workflow%3ALinux) 2 | [![Coverage](https://codecov.io/gh/cppden/med/branch/master/graph/badge.svg?token=FP0KOE0YAW)](https://codecov.io/gh/cppden/med) 3 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](../master/LICENSE) 4 | 5 | # Meta-Encoder/Decoder 6 | 7 | ## Description 8 | Zero-dependency (STL) header-only C++ library for definition of messages with compile-time generation of corresponding encoder/decoder/printer. 9 | MED is extensible library which can be adopted to support many type of encoding rules. Currently it includes: 10 | * extensible implementation of non-ASN.1 octet encoding rules; 11 | * incomplete implementation of ASN.1 BER; 12 | * initial implementation of Google ProtoBuf encoding rules; 13 | 14 | See [overview](doc/Overview.md) for details and samples. 15 | 16 | See [repos](https://github.com/cppden/gtpu) for examples of med usage. 17 | 18 | # Usage 19 | ## Define your protocol with MED 20 | ```cpp 21 | #include "med/med.hpp" 22 | 23 | template 24 | using T = med::value>; 25 | template 26 | using M = med::mandatory; 27 | template 28 | using O = med::optional; 29 | using L = med::length_t>; 30 | 31 | struct BYTE : med::value {}; 32 | struct WORD : med::value {}; 33 | struct TRIBYTE : med::value> {}; 34 | struct IP4 : med::value 35 | { 36 | static constexpr char const* name() { return "ip-addr"; } 37 | }; 38 | 39 | struct DWORD : med::value {}; 40 | struct URL : med::octet_string, med::max<10>>, med::with_snapshot {}; 41 | 42 | struct MSG1 : med::sequence< 43 | M< BYTE >, 44 | M< T<0x21>, WORD >, 45 | M< L, URL >, 46 | O< T<0x49>, TRIBYTE >, 47 | O< T<0x89>, IP4 >, 48 | O< T<0x03>, DWORD > 49 | >{}; 50 | 51 | struct MSG2 : med::set< 52 | M< tag<0x0b>, BYTE >, 53 | M< tag<0x21>, L, WORD >, 54 | O< tag<0x49>, L, TRIBYTE >, 55 | O< tag<0x89>, IP4 >, 56 | O< tag<0x22>, L, URL > 57 | >{}; 58 | 59 | struct PROTO : med::choice< 60 | M, MSG1>, 61 | M, MSG2> 62 | >{}; 63 | ``` 64 | 65 | ## Encode 66 | ```cpp 67 | //a buffer to be written with binary data of encoded message 68 | uint8_t buffer[100]; 69 | //create encoding context to define input/output/auxiliar 70 | med::encoder_context<> ctx{ buffer }; 71 | //the protocol to encode 72 | PROTO proto; 73 | //set particular message in the protocol 74 | auto& msg = proto.ref(); 75 | //set particular fields in the message 76 | msg.ref().set(0x12); 77 | msg.ref().set(0x3456); 78 | //encode the protocol with octet-encoder into given context 79 | encode(med::octet_encoder{ctx}, proto); 80 | //now the buffer holds encoded message of size ctx.buffer().get_offset() 81 | ``` 82 | 83 | ## Decode 84 | ```cpp 85 | //a binary message received in a buffer of size num_bytes 86 | PROTO proto; 87 | med::decoder_context<> ctx; 88 | ctx.reset(buffer, num_bytes); 89 | decode(med::octet_decoder{ctx}, proto); 90 | 91 | if (auto const* msg = proto.get()) 92 | { 93 | //read any message field needed 94 | } 95 | else if (auto const* msg = proto.get()) 96 | { 97 | //read any message field needed 98 | } 99 | ``` 100 | 101 | ## Print 102 | ```cpp 103 | //decode first (see above) 104 | decode(med::octet_decoder{ctx}, proto); 105 | //use your sink to consume traces, e.g. to print them to console 106 | your_sink sink{}; 107 | //trace protocol via your sink 108 | med::print(sink, proto); 109 | ``` 110 | 111 | ## Dependencies 112 | Any modern C++ compiler with C++20 support (see CI for the selected ones). 113 | -------------------------------------------------------------------------------- /med/meta/unique.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | @file 5 | helper classes to detect and prevent duplicate tags in choice and set. 6 | 7 | @copyright Denis Priyomov 2016-2019 8 | Distributed under the MIT License 9 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | namespace med::meta { 16 | 17 | namespace detail { 18 | 19 | template 20 | struct all_ne 21 | { 22 | template 23 | static constexpr bool apply(T0) 24 | { 25 | return true; 26 | } 27 | }; 28 | 29 | 30 | template requires (!std::same_as) 31 | struct all_ne 32 | { 33 | template 34 | static constexpr bool apply(T0 base, T1 val1, T... vals) 35 | { 36 | return base != static_cast(val1) && all_ne::apply(base, vals...); 37 | } 38 | }; 39 | 40 | template 41 | struct all_ne 42 | { 43 | template 44 | static constexpr bool apply(T0 base, std::nullptr_t, T... vals) 45 | { 46 | return all_ne::apply(base, vals...); 47 | } 48 | }; 49 | 50 | template 51 | struct unique_values 52 | { 53 | static constexpr bool apply(T...) 54 | { 55 | return true; 56 | } 57 | }; 58 | 59 | template requires (!std::same_as) 60 | struct unique_values 61 | { 62 | static constexpr bool apply(T1 val, T... vals) 63 | { 64 | return all_ne::apply(val, vals...) && unique_values::apply(vals...); 65 | } 66 | }; 67 | 68 | template requires (std::same_as) 69 | struct unique_values 70 | { 71 | static constexpr bool apply(std::nullptr_t, T... vals) 72 | { 73 | return unique_values::apply(vals...); 74 | } 75 | }; 76 | 77 | } //end: namespace detail 78 | 79 | template 80 | constexpr bool are_unique(T... vals) 81 | { 82 | return detail::unique_values::apply(vals...); 83 | } 84 | 85 | namespace detail { 86 | 87 | template 88 | struct is_duplicate 89 | { 90 | static constexpr auto value = false; 91 | using type = void; 92 | }; 93 | 94 | template 95 | struct is_duplicate::get()), decltype(getter::template apply::get())>> 97 | { 98 | static constexpr auto value = getter::template apply::get() == getter::template apply::get(); 99 | using type = std::pair; 100 | }; 101 | 102 | struct fallback 103 | { 104 | static constexpr bool value = true; 105 | using type = void; 106 | }; 107 | 108 | template 109 | struct same; 110 | 111 | template class L, class... IEs> 112 | struct same> : std::disjunction..., fallback> 113 | {}; 114 | 115 | template struct clash; 116 | template <> struct clash 117 | { 118 | static constexpr void error() {} 119 | }; 120 | 121 | } //end: namespace detail 122 | 123 | template 124 | struct unique; 125 | template 126 | using unique_t = typename unique::type; 127 | 128 | template class L> 129 | struct unique> 130 | { 131 | using type = void; 132 | }; 133 | 134 | 135 | template class L, class T1, class... T> 136 | struct unique> 137 | { 138 | using clashed_tags = decltype(detail::clash>::type>::error()); 139 | using type = std::enable_if_t, unique_t>>; 140 | }; 141 | 142 | } //namespace med::meta 143 | -------------------------------------------------------------------------------- /med/exception.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | med exceptions 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "config.hpp" 19 | #include "debug.hpp" 20 | 21 | namespace med { 22 | 23 | class exception : public std::exception 24 | { 25 | public: 26 | exception& operator=(exception const&) = delete; 27 | ~exception() noexcept override = default; 28 | const char* what() const noexcept override { return m_what; } 29 | 30 | protected: 31 | //only derived can be created 32 | exception() = default; 33 | 34 | template 35 | void format(char const* bufpos, char const* fmt, ARGS&&... args) noexcept 36 | { 37 | int res = std::snprintf(m_what, sizeof(m_what), fmt, std::forward(args)...); 38 | if (bufpos && res > 0 && res < int(sizeof(m_what))) 39 | { 40 | std::strncpy(m_what + res, bufpos, sizeof(m_what) - res - 1); 41 | } 42 | m_what[sizeof(m_what) - 1] = 0; 43 | } 44 | 45 | char m_what[128]; 46 | }; 47 | 48 | //OVERFLOW 49 | struct overflow : exception 50 | { 51 | overflow(char const* name, std::size_t bytes, char const* bufpos = nullptr) noexcept 52 | { format(bufpos, "'%.64s' needs %zu octets.", name, bytes); } 53 | 54 | template 55 | overflow(char const* name, std::size_t bytes, CTX const& ctx) noexcept 56 | : overflow{name, bytes, ctx.toString()} {} 57 | }; 58 | 59 | struct value_exception : public exception {}; 60 | 61 | struct invalid_value : public value_exception 62 | { 63 | invalid_value(char const* name, std::size_t val, char const* bufpos = nullptr) noexcept 64 | { format(bufpos, "Invalid value of '%.64s' = 0x%zX.", name, val); } 65 | 66 | template 67 | invalid_value(char const* name, std::size_t val, CTX const& ctx) noexcept 68 | : invalid_value{name, val, ctx.toString()} {} 69 | }; 70 | struct unknown_tag : public value_exception 71 | { 72 | unknown_tag(char const* name, std::size_t val, char const* bufpos = nullptr) noexcept 73 | { format(bufpos, "Unknown tag of '%.64s' = 0x%zX.", name, val); } 74 | 75 | template 76 | unknown_tag(char const* name, std::size_t val, CTX const& ctx) noexcept 77 | : unknown_tag{name, val, ctx.toString()} {} 78 | }; 79 | 80 | struct ie_exception : public exception {}; 81 | 82 | struct missing_ie : public ie_exception 83 | { 84 | missing_ie(char const* name, std::size_t exp, std::size_t got, char const* bufpos = nullptr) noexcept 85 | { format(bufpos, "Missing IE '%.64s': at least %zu expected, got %zu.", name, exp, got); } 86 | 87 | template 88 | missing_ie(char const* name, std::size_t exp, std::size_t got, CTX const& ctx) noexcept 89 | : missing_ie{name, exp, got, ctx.toString()} {} 90 | }; 91 | struct extra_ie : public ie_exception 92 | { 93 | extra_ie(char const* name, std::size_t exp, std::size_t got, char const* bufpos = nullptr) noexcept 94 | { format(bufpos, "Excessive IE '%.64s': no more than %zu expected, got %zu.", name, exp, got); } 95 | 96 | template 97 | extra_ie(char const* name, std::size_t exp, std::size_t got, CTX const& ctx) noexcept 98 | : extra_ie{name, exp, got, ctx.toString()} {} 99 | }; 100 | 101 | //OUT_OF_MEMORY 102 | struct out_of_memory : public exception 103 | { 104 | out_of_memory(char const* name, std::size_t bytes, char const* bufpos = nullptr) noexcept 105 | { format(bufpos, "No space to allocate '%.64s': %zu octets.", name, bytes); } 106 | 107 | template 108 | out_of_memory(char const* name, std::size_t bytes, CTX const& ctx) noexcept 109 | : out_of_memory{name, bytes, ctx.toString()} {} 110 | }; 111 | 112 | #define MED_THROW_EXCEPTION(ex, ...) { CODEC_TRACE("THROW: %s", #ex); throw ex(__VA_ARGS__); } 113 | 114 | } //end: namespace med 115 | -------------------------------------------------------------------------------- /ut/gtpc.cpp: -------------------------------------------------------------------------------- 1 | #include "ut.hpp" 2 | 3 | 4 | //GTPC-like header for testing 5 | namespace gtpc { 6 | 7 | //Bits: 7 6 5 4 3 2 1 0 8 | // [ Msg Prio ] [ Spare ] 9 | // The relative priority of the GTP-C message may take any value between 0 and 15, 10 | // where 0 corresponds to the highest priority and 15 the lowest priority. 11 | struct message_priority : med::value> 12 | { 13 | static constexpr char const* name() { return "Message-Priority"; } 14 | 15 | enum : value_type 16 | { 17 | MASK = 0b11110000 18 | }; 19 | 20 | uint8_t get() const { return (get_encoded() & MASK) >> 4; } 21 | void set(uint8_t v) { set_encoded((v << 4) & MASK); } 22 | }; 23 | 24 | //Bits: 7 6 5 4 3 2 1 0 25 | // [ Version ] [P] [T] [MP][Spare] 26 | struct version_flags : med::value 27 | { 28 | enum : value_type 29 | { 30 | MP = 1u << 2, //Message Priority flag 31 | T = 1u << 3, //TEID flag 32 | P = 1u << 4, //Piggybacking flag 33 | MASK_VERSION = 0xE0u, //1110 0000 34 | }; 35 | 36 | uint8_t version() const { return (get() & MASK_VERSION) >> 5; } 37 | 38 | bool piggyback() const { return get() & P; } 39 | void piggyback(bool v) { set(v ? (get() | P) : (get() & ~P)); } 40 | 41 | static constexpr char const* name() { return "Version-Flags"; } 42 | 43 | struct setter 44 | { 45 | template 46 | void operator()(version_flags& flags, HDR const& hdr) const 47 | { 48 | auto bits = flags.get(); 49 | if (hdr.template as().is_default()) 50 | { 51 | bits &= ~MP; 52 | } 53 | else 54 | { 55 | bits |= MP; 56 | } 57 | flags.set(bits); 58 | } 59 | }; 60 | 61 | struct has_message_priority 62 | { 63 | template 64 | bool operator()(HDR const& hdr) const 65 | { 66 | return hdr.template as().get() & version_flags::MP; 67 | } 68 | }; 69 | }; 70 | 71 | struct sequence_number : med::value> 72 | { 73 | static constexpr char const* name() { return "Sequence-Number"; } 74 | }; 75 | 76 | /*Oct\Bit 8 7 6 5 4 3 2 1 77 | ----------------------------------------- 78 | 1 [ Version ] [P] [T] [MP][Spare] 79 | 2 [ Sequence Number (1st octet) ] 80 | 3 [ Sequence Number (2nd octet) ] 81 | 4 [ Sequence Number (3rd octet) ] 82 | 5 [ Msg Priority] [ Spare ] 83 | ----------------------------------------- */ 84 | struct header : med::sequence< 85 | M< version_flags, version_flags::setter >, 86 | M< sequence_number >, 87 | O< message_priority, version_flags::has_message_priority > 88 | > 89 | { 90 | version_flags const& flags() const { return get(); } 91 | version_flags& flags() { return ref(); } 92 | 93 | sequence_number::value_type sn() const { return get().get(); } 94 | void sn(sequence_number::value_type v) { ref().set(v); } 95 | 96 | static constexpr char const* name() { return "Header"; } 97 | 98 | explicit header(uint8_t ver = 2) 99 | { 100 | flags().set(ver << 5); 101 | } 102 | }; 103 | 104 | } //end: namespace gtpc 105 | 106 | TEST(opt_defs, unset) 107 | { 108 | uint8_t buffer[1024]; 109 | 110 | gtpc::header header; 111 | header.sn(0x010203); 112 | 113 | med::encoder_context<> ctx{ buffer }; 114 | encode(med::octet_encoder{ctx}, header); 115 | ASSERT_STREQ("40 01 02 03 00 ", as_string(ctx.buffer())); 116 | check_decode(header, ctx.buffer()); 117 | } 118 | 119 | TEST(opt_defs, set) 120 | { 121 | uint8_t buffer[1024] = {}; 122 | 123 | gtpc::header header; 124 | header.sn(0x010203); 125 | header.ref().set(7); 126 | 127 | med::encoder_context<> ctx{ buffer }; 128 | encode(med::octet_encoder{ctx}, header); 129 | ASSERT_STREQ("44 01 02 03 70 ", as_string(ctx.buffer())); 130 | check_decode(header, ctx.buffer()); 131 | } 132 | -------------------------------------------------------------------------------- /med/protobuf/decoder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | octet decoder definition 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "exception.hpp" 15 | #include "state.hpp" 16 | #include "name.hpp" 17 | #include "ie_type.hpp" 18 | #include "protobuf.hpp" 19 | #include "sl/octet_info.hpp" 20 | 21 | namespace med::protobuf { 22 | 23 | template 24 | struct decoder : sl::octet_info 25 | { 26 | using state_type = typename DEC_CTX::buffer_type::state_type; 27 | using size_state = typename DEC_CTX::buffer_type::size_state; 28 | using allocator_type = typename DEC_CTX::allocator_type; 29 | 30 | explicit decoder(DEC_CTX& ctx_) : m_ctx{ctx_} { } 31 | DEC_CTX& get_context() noexcept { return m_ctx; } 32 | allocator_type& get_allocator() { return get_context().get_allocator(); } 33 | 34 | //state 35 | auto operator() (PUSH_SIZE ps) { return get_context().buffer().push_size(ps.size, ps.commit); } 36 | template 37 | bool operator() (PUSH_STATE, IE const&) { return get_context().buffer().push_state(); } 38 | void operator() (POP_STATE) { get_context().buffer().pop_state(); } 39 | auto operator() (GET_STATE) { return get_context().buffer().get_state(); } 40 | template 41 | bool operator() (CHECK_STATE, IE const&) { return !get_context().buffer().empty(); } 42 | void operator() (ADVANCE_STATE ss) { get_context().buffer().template advance(ss.delta); } 43 | 44 | //IE_TAG 45 | template [[nodiscard]] auto operator() (IE&, IE_TAG) 46 | { 47 | CODEC_TRACE("TAG[%s]: %s", name(), get_context().buffer().toString()); 48 | as_writable_t ie; 49 | (*this)(ie, typename as_writable_t::ie_type{}); 50 | return ie.get_encoded(); 51 | } 52 | 53 | //IE_VALUE 54 | //Little Endian Base 128: https://en.wikipedia.org/wiki/LEB128 55 | template 56 | void operator() (IE& ie, IE_VALUE) 57 | { 58 | static_assert(0 == (IE::traits::bits % 8), "OCTET VALUE EXPECTED"); 59 | CODEC_TRACE("->VAL[%s] %zu bits: %s", name(), IE::traits::bits, get_context().buffer().toString()); 60 | typename IE::value_type val = get_context().buffer().template pop(); 61 | if (val & 0x80) 62 | { 63 | val &= 0x7F; 64 | std::size_t count = 0; 65 | for (;;) 66 | { 67 | if (++count < MAX_VARINT_BYTES) 68 | { 69 | auto const byte = get_context().buffer().template pop(); 70 | val |= static_cast(byte & 0x7F) << (7 * count); 71 | if (0 == (byte & 0x80)) { break; } 72 | } 73 | else 74 | { 75 | MED_THROW_EXCEPTION(invalid_value, name(), val, get_context().buffer()) 76 | } 77 | } 78 | } 79 | 80 | if constexpr (std::is_same_v) 81 | { 82 | if (not ie.set_encoded(val)) 83 | { 84 | MED_THROW_EXCEPTION(invalid_value, name(), val, get_context().buffer()) 85 | } 86 | } 87 | else 88 | { 89 | ie.set_encoded(val); 90 | } 91 | CODEC_TRACE("<-VAL[%s]=%zX: %s", name(), std::size_t(val), get_context().buffer().toString()); 92 | } 93 | 94 | //IE_OCTET_STRING 95 | template 96 | void operator() (IE& ie, IE_OCTET_STRING) 97 | { 98 | CODEC_TRACE("STR[%s] <-(%zu bytes): %s", name(), get_context().buffer().size(), get_context().buffer().toString()); 99 | if (ie.set_encoded(get_context().buffer().size(), get_context().buffer().begin())) 100 | { 101 | CODEC_TRACE("STR[%s] -> len = %zu bytes", name(), std::size_t(ie.size())); 102 | get_context().buffer().template advance(ie.size()); 103 | } 104 | else 105 | { 106 | MED_THROW_EXCEPTION(invalid_value, name(), ie.size(), get_context().buffer()) 107 | } 108 | } 109 | 110 | private: 111 | DEC_CTX& m_ctx; 112 | }; 113 | 114 | template explicit decoder(C&) -> decoder; 115 | 116 | } //end: namespace med::protobuf 117 | -------------------------------------------------------------------------------- /med/asn/ber/ber_tag.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | @file 4 | ASN.1 BER tag definition 5 | 6 | @copyright Denis Priyomov 2018 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 9 | */ 10 | 11 | #include "asn/ids.hpp" 12 | #include "ber_length.hpp" 13 | 14 | 15 | namespace med::asn::ber { 16 | 17 | namespace detail { 18 | 19 | /* X.690 20 | 8.1.2 Identifier octets 21 | 8.1.2.1 The identifier octets encode the ASN.1 tag (class and number) of the type of the data value. 22 | 8.1.2.2 For tags in [0..30], the identifier octets comprise a single octet encoded as follows: 23 | a) bits 8 and 7 represent the class of the tag as specified in Table 1; 24 | b) bit 6 represent the encoding: 0 - primitive, 1 - constructed; 25 | c) bits 5 to 1 encode the number of the tag as a binary integer with bit 5 as MSB. 26 | 8.1.2.4 For tags > 30, the identifier comprise a leading octet followed by 1+ subsequent octets. 27 | 8.1.2.4.1 The leading octet shall be encoded as follows: 28 | a) bits 8,7,6 encoded as in 8.1.2.2; 29 | c) bits 5 to 1 shall be encoded as 0b11111. 30 | 8.1.2.4.2 The subsequent octets encode the number of the tag as follows: 31 | a) bit 8 of each octet shall be set to 1 unless it is the last octet; 32 | b) bits 7 to 1 of the first subsequent octet, followed by bits 7 to 1 of the second subsequent 33 | octet, followed in turn by bits 7 to 1 of each further octet, up to and including the last 34 | subsequent octet in the identifier octets shall be the encoding of an unsigned binary integer 35 | equal to the tag number, with bit 7 of the 1st subsequent octet as the MSB; 36 | c) bits 7 to 1 of the first subsequent octet shall not all be zero. 37 | */ 38 | 39 | constexpr std::size_t TAG_MAX_IN_BYTE = 0b011111; //31 40 | 41 | 42 | constexpr uint8_t tag_bytes(std::size_t value) 43 | { 44 | if (value < TAG_MAX_IN_BYTE) 45 | { 46 | return 1; 47 | } 48 | else 49 | { 50 | return 1 + least_bytes_encoded(value); 51 | } 52 | } 53 | 54 | constexpr std::size_t encode_unsigned(std::size_t value, std::size_t init = 0) 55 | { 56 | std::size_t encoded = init; 57 | 58 | //TODO: rewrite? 59 | auto const num_bits = detail::calc_least_bits(value); 60 | for (std::size_t v = value << (sizeof(value) * 8 - num_bits), //bits value to write out 61 | bits = num_bits, //total number of bits in value to write 62 | step = (bits % 7) ? (bits % 7) : 7; //number of bits in single step 63 | bits; 64 | bits -= step, step = 7) 65 | { 66 | encoded <<= 8; 67 | encoded |= ((bits > 7 ? 0x80 : 0) | uint8_t(v >> (sizeof(v) * 8 - step))); 68 | v <<= step; 69 | } 70 | 71 | return encoded; 72 | } 73 | 74 | constexpr std::size_t tag_encoded(tg_class tagclass, bool constructed, std::size_t value) 75 | { 76 | constexpr std::size_t CLASS_SHIFT = 6; 77 | constexpr std::size_t PC_BIT = 1 << 5; //Primitive or Constructed encoding 78 | 79 | std::size_t tag = (uint8_t(tagclass) << CLASS_SHIFT) | (constructed ? PC_BIT : 0); 80 | if (value < TAG_MAX_IN_BYTE) 81 | { 82 | tag |= value; 83 | } 84 | else 85 | { 86 | tag |= TAG_MAX_IN_BYTE; 87 | tag = encode_unsigned(value, tag); 88 | 89 | // constexpr auto num_bits = detail::calc_least_bits(TRAITS::AsnTagValue); 90 | // for (std::size_t v = TRAITS::AsnTagValue << (sizeof(std::size_t) * 8 - num_bits), //bits value to write out 91 | // bits = num_bits, //total number of bits in value to write 92 | // step = (bits % 7) ? (bits % 7) : 7; //number of bits in single step 93 | // bits; 94 | // bits -= step, step = 7) 95 | // { 96 | // tag <<= 8; 97 | // tag |= ((bits > 7 ? 0x80 : 0) | uint8_t(v >> (sizeof(v) * 8 - step))); 98 | // v <<= step; 99 | // } 100 | } 101 | return tag; 102 | } 103 | 104 | } //end: namespace detail 105 | 106 | template 107 | struct tag_value 108 | { 109 | static constexpr uint8_t num_bytes = detail::tag_bytes(TRAITS::AsnTagValue); 110 | static constexpr std::size_t value = detail::tag_encoded(TRAITS::AsnTagClass, CONSTRUCTED, TRAITS::AsnTagValue); 111 | }; 112 | 113 | } //end: namespace med::asn::ber 114 | 115 | -------------------------------------------------------------------------------- /ut/multi.cpp: -------------------------------------------------------------------------------- 1 | #include "ut.hpp" 2 | 3 | namespace multi { 4 | 5 | struct L : med::length_t>{ 6 | static constexpr char const* name() { return "LEN"; } 7 | }; 8 | 9 | template 10 | using T = med::value>; 11 | 12 | struct U8 : med::value {}; 13 | struct U16 : med::value {}; 14 | struct U24 : med::value> {}; 15 | struct U32 : med::value {}; 16 | 17 | struct M1 : med::sequence< 18 | M< T<1>, U8, med::max<3>>, 19 | O< T<2>, U16, med::inf> 20 | >{}; 21 | 22 | } //end: namespace multi 23 | 24 | TEST(multi, pop_back) 25 | { 26 | uint8_t buffer[16]; 27 | med::encoder_context<> ctx{ buffer }; 28 | 29 | using namespace multi; 30 | M1 msg; 31 | auto const& mie = msg.get(); 32 | msg.ref().push_back()->set(1); 33 | msg.ref().pop_back(); 34 | EXPECT_TRUE(mie.empty()); 35 | EXPECT_EQ(0, mie.count()); 36 | 37 | msg.ref().push_back()->set(1); 38 | msg.ref().push_back()->set(2); 39 | msg.ref().push_back()->set(3); 40 | EXPECT_FALSE(mie.empty()); 41 | EXPECT_EQ(3, mie.count()); 42 | EXPECT_EQ(3, std::distance(mie.begin(), mie.end())); 43 | 44 | msg.ref().pop_back(); 45 | 46 | encode(med::octet_encoder{ctx}, msg); 47 | 48 | uint8_t const encoded[] = { 49 | 1, 1, 50 | 1, 2, 51 | }; 52 | EXPECT_EQ(sizeof(encoded), ctx.buffer().get_offset()); 53 | EXPECT_TRUE(Matches(encoded, buffer)); 54 | } 55 | 56 | TEST(multi, push_back) 57 | { 58 | uint8_t buffer[1]; 59 | med::encoder_context<> ctx{ buffer }; 60 | 61 | using namespace multi; 62 | M1 msg; 63 | 64 | //check the inplace storage for unlimited field has only one slot 65 | auto* p = msg.ref().push_back(); 66 | ASSERT_NE(nullptr, p); 67 | p->set(1); 68 | 69 | ASSERT_THROW(msg.ref().push_back(), med::out_of_memory); 70 | ASSERT_THROW(msg.ref().push_back(ctx), med::out_of_memory); 71 | } 72 | 73 | TEST(multi, clear) 74 | { 75 | using namespace multi; 76 | M1 msg; 77 | 78 | auto const& mie = msg.get(); 79 | EXPECT_TRUE(mie.empty()); 80 | msg.ref().push_back()->set(1); 81 | msg.ref().push_back()->set(2); 82 | msg.ref().push_back()->set(3); 83 | EXPECT_FALSE(mie.empty()); 84 | EXPECT_EQ(3, mie.count()); 85 | EXPECT_EQ(3, std::distance(mie.begin(), mie.end())); 86 | 87 | msg.clear(); 88 | msg.ref().push_back()->set(1); 89 | msg.ref().push_back()->set(2); 90 | msg.ref().push_back()->set(3); 91 | EXPECT_FALSE(mie.empty()); 92 | EXPECT_EQ(3, mie.count()); 93 | EXPECT_EQ(3, std::distance(mie.begin(), mie.end())); 94 | } 95 | 96 | TEST(multi, erase) 97 | { 98 | uint8_t buffer[16]; 99 | med::encoder_context<> ctx{ buffer }; 100 | 101 | using namespace multi; 102 | M1 msg; 103 | 104 | auto& mie = msg.ref(); 105 | auto it = mie.erase(mie.begin()); //erase end -> end 106 | EXPECT_EQ(mie.end(), it); 107 | EXPECT_EQ(0, mie.count()); 108 | 109 | mie.push_back()->set(1); 110 | mie.push_back()->set(2); 111 | mie.push_back()->set(3); 112 | 113 | EXPECT_EQ(3, mie.count()); //(1,2,3) 114 | EXPECT_EQ(3, std::distance(mie.begin(), mie.end())); 115 | 116 | mie.erase(std::next(std::next(mie.begin()))); //erase last (1,2) 117 | EXPECT_EQ(2, mie.count()); 118 | EXPECT_EQ(2, std::distance(mie.begin(), mie.end())); 119 | mie.push_back()->set(3); // get back (1,2,3) 120 | 121 | mie.erase(mie.begin()); //erase 1st (2,3) 122 | EXPECT_EQ(2, mie.count()); 123 | EXPECT_EQ(2, std::distance(mie.begin(), mie.end())); 124 | 125 | mie.push_back()->set(1); //(2,3,1) 126 | mie.erase(std::next(mie.begin())); //erase in middle (2,1) 127 | EXPECT_EQ(2, mie.count()); 128 | EXPECT_EQ(2, std::distance(mie.begin(), mie.end())); 129 | 130 | mie.push_back()->set(3); //(2,1,3) 131 | mie.erase(std::next(std::next(mie.begin()))); //erase last (2,1) 132 | EXPECT_EQ(2, mie.count()); 133 | EXPECT_EQ(2, std::distance(mie.begin(), mie.end())); 134 | 135 | encode(med::octet_encoder{ctx}, msg); 136 | 137 | uint8_t const encoded[] = { 138 | 1, 2, 139 | 1, 1, 140 | }; 141 | EXPECT_EQ(sizeof(encoded), ctx.buffer().get_offset()); 142 | EXPECT_TRUE(Matches(encoded, buffer)); 143 | } 144 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: 4 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") 5 | cmake_policy(SET CMP0135 NEW) 6 | endif() 7 | 8 | set(THIS_NAME med) 9 | 10 | project(${THIS_NAME}) 11 | enable_language(CXX) 12 | 13 | message(STATUS "-------------------------------------------------------") 14 | message(STATUS "MED library optional configuration:") 15 | message(STATUS "\t(use ccmake or cmake -DWITH_XX=true)") 16 | message(STATUS "-------------------------------------------------------") 17 | option (WITH_BM "With Google benchmark." OFF) 18 | 19 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") 20 | 21 | include(FetchContent) 22 | 23 | FetchContent_Declare( 24 | googletest 25 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 26 | ) 27 | 28 | FetchContent_MakeAvailable(googletest) 29 | 30 | find_package(Threads REQUIRED) 31 | 32 | include_directories( 33 | ${PROJECT_SOURCE_DIR}/med 34 | ) 35 | include(GoogleTest) 36 | # ----------------------------------------------------------------------------- 37 | # Benchmarks 38 | # ----------------------------------------------------------------------------- 39 | if (WITH_BM) 40 | message(STATUS "\tWITH_BM=true (google benchmark)") 41 | find_package(BENCHMARK REQUIRED) 42 | if (BENCHMARK_FOUND) 43 | include_directories( ${BENCHMARK_INCLUDE_DIRS}) 44 | message(STATUS "Google benchmark found. ${BENCHMARK_LIBS}") 45 | else (BENCHMARK_FOUND) 46 | message(FATAL_ERROR "Google benchmark not found. Disable WITH_BM or install it.") 47 | endif (BENCHMARK_FOUND) 48 | else (WITH_BM) 49 | message(STATUS "\tWITH_BM=false (no benchmark)") 50 | endif (WITH_BM) 51 | 52 | 53 | include(CheckCXXCompilerFlag) 54 | check_cxx_compiler_flag("-std=c++20" COMPILER_SUPPORTS_CXX20) 55 | if (COMPILER_SUPPORTS_CXX20) 56 | add_compile_options(-std=c++20) 57 | else () 58 | message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++20 support. Please use a different C++ compiler.") 59 | endif () 60 | 61 | 62 | # Library path 63 | set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L\".\" ") 64 | 65 | file(GLOB_RECURSE MED_SRCS med/*.hpp) 66 | # file(GLOB_RECURSE UT_SRCS ut/*.cpp) 67 | set(UT_SRCS 68 | ut/bits.cpp 69 | ut/choice.cpp 70 | ut/copy.cpp 71 | ut/diameter.cpp 72 | ut/gtpc.cpp 73 | ut/length.cpp 74 | ut/med.cpp 75 | ut/meta.cpp 76 | ut/multi.cpp 77 | ut/octets.cpp 78 | ut/padding.cpp 79 | ut/print.cpp 80 | ut/protobuf.cpp 81 | ut/sequence.cpp 82 | ut/set.cpp 83 | ut/unique.cpp 84 | ut/value.cpp 85 | ut/asn/ber.cpp 86 | ) 87 | file(GLOB_RECURSE BM_SRCS benchmark/*.cpp) 88 | 89 | #expose private for UTs 90 | #add_compile_definitions(UNIT_TEST) 91 | add_definitions(-DUNIT_TEST) 92 | 93 | add_compile_options( 94 | -Werror 95 | -Wall 96 | -Wextra 97 | -Waddress 98 | -Warray-bounds 99 | -Winit-self 100 | -Wunreachable-code 101 | -pedantic 102 | -pedantic-errors 103 | #-march=native 104 | -mtune=native 105 | ) 106 | 107 | #set(PROJECT_CXXFLAGS "${PROJECT_CXXFLAGS} -Wno-gnu-zero-variadic-macro-arguments") 108 | 109 | add_executable(gtest_med ${UT_SRCS} ${MED_SRCS}) 110 | 111 | if (WITH_BM) 112 | add_executable(bm_med ${BM_SRCS}) 113 | # set_target_properties(bm_med PROPERTIES 114 | # INTERPROCEDURAL_OPTIMIZATION ON) 115 | target_link_libraries(bm_med 116 | ${BENCHMARK_LIBS} 117 | ${CMAKE_THREAD_LIBS_INIT} 118 | ) 119 | add_custom_target(benchmark COMMAND echo "Benchmarking....." 120 | DEPENDS bm_med 121 | ) 122 | endif (WITH_BM) 123 | 124 | # ------------------------------------------------------------------------------ 125 | # Coverage 126 | # ------------------------------------------------------------------------------ 127 | if(COVERAGE) 128 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") 129 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 130 | else() 131 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") 132 | endif() 133 | 134 | #if(DEFINED ENV{BUILD_FLAGS}) 135 | # set(BUILD_FLAGS "$ENV{BUILD_FLAGS}") 136 | #endif () 137 | #set_target_properties(gtest_${THIS_NAME} PROPERTIES COMPILE_FLAGS 138 | # ${BUILD_FLAGS} 139 | #) 140 | 141 | target_link_libraries(gtest_med 142 | GTest::gtest 143 | ${CMAKE_THREAD_LIBS_INIT} 144 | ) 145 | 146 | enable_testing() 147 | add_test(UT gtest_${THIS_NAME}) 148 | add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} 149 | DEPENDS gtest_${THIS_NAME} 150 | ) 151 | -------------------------------------------------------------------------------- /med/octet_decoder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | octet decoder definition 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "bytes.hpp" 13 | #include "exception.hpp" 14 | #include "state.hpp" 15 | #include "name.hpp" 16 | #include "ie_type.hpp" 17 | #include "sl/octet_info.hpp" 18 | #include "padding.hpp" 19 | 20 | namespace med { 21 | 22 | template 23 | struct octet_decoder : sl::octet_info 24 | { 25 | using state_type = typename DEC_CTX::buffer_type::state_type; 26 | using size_state = typename DEC_CTX::buffer_type::size_state; 27 | using allocator_type = typename DEC_CTX::allocator_type; 28 | template 29 | using padder_type = octet_padder; 30 | 31 | explicit octet_decoder(DEC_CTX& c) : m_ctx{c} { } 32 | DEC_CTX& get_context() noexcept { return m_ctx; } 33 | allocator_type& get_allocator() { return get_context().get_allocator(); } 34 | 35 | //state 36 | auto operator() (PUSH_SIZE ps) { return get_context().buffer().push_size(ps.size, ps.commit); } 37 | template 38 | bool operator() (PUSH_STATE, IE const&) { return get_context().buffer().push_state(); } 39 | void operator() (POP_STATE) { get_context().buffer().pop_state(); } 40 | auto operator() (GET_STATE) { return get_context().buffer().get_state(); } 41 | template 42 | bool operator() (CHECK_STATE, IE const&) { return !get_context().buffer().empty(); } 43 | void operator() (ADVANCE_STATE ss) { get_context().buffer().template advance(ss.delta); } 44 | void operator() (ADD_PADDING pad) { get_context().buffer().template advance(pad.pad_size); } 45 | 46 | //IE_TAG 47 | template [[nodiscard]] auto operator() (IE&, IE_TAG) 48 | { 49 | as_writable_t ie; 50 | (*this)(ie, typename as_writable_t::ie_type{}); 51 | return ie.get_encoded(); 52 | } 53 | //IE_LEN 54 | template void operator() (IE& ie, IE_LEN) 55 | { 56 | (*this)(ie, typename IE::ie_type{}); 57 | } 58 | 59 | //IE_NULL 60 | template void operator() (IE&, IE_NULL) 61 | { 62 | CODEC_TRACE("NULL[%s]: %s", name(), get_context().buffer().toString()); 63 | } 64 | 65 | //IE_VALUE 66 | template void operator() (IE& ie, IE_VALUE) 67 | { 68 | using value_t = typename IE::value_type; 69 | constexpr auto NUM_BITS = IE::traits::bits + IE::traits::offset; 70 | constexpr auto NUM_BYTES = bits_to_bytes(NUM_BITS); 71 | uint8_t const* pval = get_context().buffer().template advance_bits(); 72 | 73 | auto const val = [](uint8_t const* in) 74 | { 75 | if constexpr (IE::traits::offset == 0 && (IE::traits::bits % 8) == 0) 76 | { 77 | return get_bytes(in); 78 | } 79 | else 80 | { 81 | size_t res = get_bytes(in); 82 | if constexpr (IE::traits::offset) 83 | { 84 | constexpr size_t MASK = (size_t(1) << (NUM_BYTES * 8 - IE::traits::offset)) - 1; 85 | CODEC_TRACE("V=%zXh(%zu) M=%zXh", res, NUM_BYTES, MASK); 86 | res &= MASK; 87 | } 88 | constexpr uint8_t RS = NUM_BYTES * 8 - NUM_BITS; 89 | if constexpr (RS) { res >>= RS; } 90 | return value_t(res); 91 | } 92 | }(pval); 93 | 94 | if constexpr (std::is_same_v) 95 | { 96 | if (not ie.set_encoded(val)) 97 | { 98 | MED_THROW_EXCEPTION(invalid_value, name(), val, get_context().buffer()) 99 | } 100 | } 101 | else 102 | { 103 | ie.set_encoded(val); 104 | } 105 | CODEC_TRACE("VAL=%zXh [%s]: %s", std::size_t(val), name(), get_context().buffer().toString()); 106 | } 107 | 108 | //IE_OCTET_STRING 109 | template void operator() (IE& ie, IE_OCTET_STRING) 110 | { 111 | CODEC_TRACE("STR[%s] <-(%zu bytes): %s", name(), get_context().buffer().size(), get_context().buffer().toString()); 112 | if (ie.set_encoded(get_context().buffer().size(), get_context().buffer().begin())) 113 | { 114 | CODEC_TRACE("STR[%s] -> len = %zu bytes", name(), std::size_t(ie.size())); 115 | get_context().buffer().template advance(ie.size()); 116 | } 117 | else 118 | { 119 | MED_THROW_EXCEPTION(invalid_value, name(), ie.size(), get_context().buffer()) 120 | } 121 | } 122 | 123 | private: 124 | DEC_CTX& m_ctx; 125 | }; 126 | 127 | } //end: namespace med 128 | -------------------------------------------------------------------------------- /med/padding.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | pseudo-IE for padding/alignments within containers 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "config.hpp" 15 | #include "units.hpp" 16 | #include "debug.hpp" 17 | #include "state.hpp" 18 | 19 | namespace med { 20 | 21 | enum class filler_value : uint8_t {}; 22 | template struct filler 23 | { 24 | using type = std::integral_constant; 25 | }; 26 | enum class offset_value : int8_t {}; 27 | template struct offset 28 | { 29 | using type = std::integral_constant; 30 | }; 31 | enum class delta_value : int8_t {}; 32 | template struct delta 33 | { 34 | using type = std::integral_constant; 35 | }; 36 | 37 | namespace detail { 38 | 39 | template 40 | using type_of = typename T::type; 41 | 42 | struct def_value 43 | { 44 | using type = void; 45 | }; 46 | 47 | template struct value_of; 48 | 49 | template 50 | struct value_of, Tail...> : std::integral_constant {}; 51 | 52 | template 53 | struct value_of : std::integral_constant {}; 54 | 55 | template 56 | struct value_of : value_of {}; 57 | 58 | template 59 | struct pad_traits 60 | { 61 | static constexpr std::size_t pad_bits = SIZE; 62 | static constexpr uint8_t filler = uint8_t(value_of...>::value); 63 | static constexpr int offset = int(value_of...>::value); 64 | static constexpr int delta = int(value_of...>::value); 65 | }; 66 | 67 | } //end: namespace detail 68 | 69 | 70 | template 71 | struct padding; 72 | 73 | template requires (BITS > 0) 74 | struct padding, D, O, F> 75 | { 76 | using padding_traits = detail::pad_traits; 77 | }; 78 | 79 | template requires (BYTES > 0) 80 | struct padding, D, O, F> 81 | { 82 | using padding_traits = detail::pad_traits; 83 | }; 84 | 85 | template 86 | struct padding 87 | { 88 | using padding_traits = detail::pad_traits; 89 | }; 90 | 91 | template 92 | struct get_padding 93 | { 94 | using type = void; 95 | }; 96 | 97 | template 98 | struct get_padding>> 99 | { 100 | using type = typename T::padding_traits; 101 | }; 102 | 103 | template 104 | struct get_padding>> : get_padding { }; 105 | 106 | template 107 | struct octet_padder 108 | { 109 | explicit octet_padder(FUNC& func) noexcept 110 | : m_func{ func } 111 | , m_start{ m_func(GET_STATE{}) } 112 | {} 113 | 114 | void enable_padding(bool v) const { m_enable = v; } 115 | 116 | static constexpr std::size_t calc_padding_size(std::size_t len) 117 | { 118 | constexpr auto pad_bytes = PAD_TRAITS::pad_bits/8; 119 | auto const padding_size = (pad_bytes - ((len + PAD_TRAITS::offset) % pad_bytes) + PAD_TRAITS::delta) % pad_bytes; 120 | CODEC_TRACE("%s: pad=%zu bytes for len=%zu+%d delta=%d => %zu", __FUNCTION__ 121 | , pad_bytes, len, PAD_TRAITS::offset, PAD_TRAITS::delta, padding_size); 122 | return padding_size; 123 | } 124 | 125 | void add_padding() const 126 | { 127 | if (auto const pad_bytes = padding_size()) 128 | { 129 | CODEC_TRACE("PADDING %u bytes", pad_bytes); 130 | m_func(ADD_PADDING{pad_bytes, PAD_TRAITS::filler}); 131 | } 132 | } 133 | 134 | //current padding size in units of codec 135 | uint8_t padding_size() const { return m_enable ? calc_padding_size(m_func(GET_STATE{}) - m_start) : 0; } 136 | 137 | private: 138 | octet_padder(octet_padder const&) = delete; 139 | octet_padder& operator= (octet_padder const&) = delete; 140 | 141 | FUNC& m_func; 142 | typename FUNC::state_type m_start; 143 | bool mutable m_enable{ true }; 144 | }; 145 | 146 | } //end: namespace med 147 | -------------------------------------------------------------------------------- /benchmark/bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //#pragma GCC diagnostic push 4 | //#pragma GCC diagnostic pop 5 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 6 | 7 | #include "med.hpp" 8 | #include "encode.hpp" 9 | #include "decode.hpp" 10 | #include "encoder_context.hpp" 11 | #include "decoder_context.hpp" 12 | #include "octet_encoder.hpp" 13 | #include "octet_decoder.hpp" 14 | 15 | namespace { 16 | 17 | template using M = med::mandatory; 18 | template using O = med::optional; 19 | using L = med::length_t>; 20 | template using T = med::value>; 21 | 22 | struct FLD_UC : med::value{}; 23 | struct FLD_U16 : med::value{}; 24 | struct FLD_U24 : med::value>{}; 25 | struct FLD_IP : med::value{}; 26 | struct FLD_DW : med::value{}; 27 | struct VFLD1 : med::ascii_string, med::max<10>>{}; 28 | 29 | struct custom_length : med::value 30 | { 31 | std::size_t get_length() const noexcept 32 | { 33 | return 2 * (get_encoded() + 1); 34 | } 35 | 36 | bool set_length(std::size_t v) 37 | { 38 | if (0 == (v & 1)) //should be even 39 | { 40 | set_encoded((v - 2) / 2); 41 | return true; 42 | } 43 | return false; 44 | } 45 | }; 46 | using CLEN = med::length_t; 47 | 48 | struct MSG_SEQ : med::sequence< 49 | M< FLD_UC >, // 50 | M< T<0x21>, FLD_U16 >, // 51 | M< L, FLD_U24 >, //(fixed) 52 | M< T<0x42>, L, FLD_IP >, //(fixed) 53 | O< T<0x51>, FLD_DW >, //[TV] 54 | O< T<0x12>, CLEN, VFLD1 > //TLV(var) 55 | > 56 | { 57 | }; 58 | 59 | struct PROTO : med::choice< 60 | M< T<0x01>, MSG_SEQ > 61 | > 62 | { 63 | }; 64 | 65 | 66 | void BM_encode_ok(benchmark::State& state) 67 | { 68 | PROTO proto; 69 | uint8_t buffer[1024]; 70 | med::encoder_context<> ctx{ buffer }; 71 | 72 | auto& msg = proto.ref(); 73 | msg.ref().set(37); 74 | msg.ref().set(0x35D9); 75 | msg.ref().set(0xDABEEF); 76 | msg.ref().set(0xFee1ABBA); 77 | 78 | 79 | msg.ref().set(0x01020304); 80 | msg.ref().set("test.this!"); 81 | 82 | std::uint8_t dummy = 0; 83 | while (state.KeepRunning()) 84 | { 85 | ctx.reset(); 86 | dummy += buffer[0]; 87 | msg.ref().set(dummy); 88 | encode(med::octet_encoder{ctx}, proto); 89 | benchmark::DoNotOptimize(dummy); 90 | } 91 | } 92 | BENCHMARK(BM_encode_ok); 93 | 94 | void BM_encode_fail(benchmark::State& state) 95 | { 96 | PROTO proto; 97 | uint8_t buffer[1024]; 98 | med::encoder_context<> ctx{ buffer }; 99 | 100 | auto& msg = proto.ref(); 101 | msg.ref().set(0); 102 | msg.ref().set(0); 103 | 104 | while (state.KeepRunning()) 105 | { 106 | ctx.reset(); 107 | try 108 | { 109 | encode(med::octet_encoder{ctx}, proto); 110 | std::abort(); 111 | } 112 | catch (med::missing_ie const& ex) 113 | { 114 | } 115 | } 116 | } 117 | BENCHMARK(BM_encode_fail); 118 | 119 | void BM_decode_ok(benchmark::State& state) 120 | { 121 | PROTO proto; 122 | med::decoder_context<> ctx; 123 | 124 | uint8_t const encoded[] = { 1 125 | , 37 126 | , 0x21, 0x35, 0xD9 127 | , 3, 0xDA, 0xBE, 0xEF 128 | , 0x42, 4, 0xFE, 0xE1, 0xAB, 0xBA 129 | , 0x51, 0x01, 0x02, 0x03, 0x04 130 | , 0x12, 4, 't', 'e', 's', 't', '.', 't', 'h', 'i', 's', '!' 131 | }; 132 | 133 | std::size_t dummy = 0; 134 | while (state.KeepRunning()) 135 | { 136 | ctx.reset(encoded, sizeof(encoded)); 137 | decode(med::octet_decoder{ctx}, proto); 138 | dummy += proto.get()->get().get(); 139 | benchmark::DoNotOptimize(dummy); 140 | } 141 | 142 | //std::printf("dummy=%zu\n", dummy); 143 | } 144 | BENCHMARK(BM_decode_ok); 145 | 146 | void BM_decode_fail(benchmark::State& state) 147 | { 148 | PROTO proto; 149 | med::decoder_context<> ctx; 150 | 151 | //invalid length of variable field (longer) 152 | uint8_t const bad_var_len_hi[] = { 1 153 | , 37 154 | , 0x21, 0x35, 0xD9 155 | , 3, 0xDA, 0xBE, 0xEF 156 | , 0x42, 4, 0xFE, 0xE1, 0xAB, 0xBA 157 | , 0x12, 5, 't', 'e', 's', 't', 'e', 's', 't', 'e', 's', 't', 'e' 158 | }; 159 | 160 | while (state.KeepRunning()) 161 | { 162 | ctx.reset(bad_var_len_hi, sizeof(bad_var_len_hi)); 163 | try 164 | { 165 | decode(med::octet_decoder{ctx}, proto); 166 | std::printf("SHOULD NOT REACH HERE!\n"); 167 | std::abort(); 168 | } 169 | catch (med::exception const& ex) 170 | { 171 | } 172 | } 173 | } 174 | BENCHMARK(BM_decode_fail); 175 | 176 | } //end: namespace 177 | 178 | BENCHMARK_MAIN(); 179 | -------------------------------------------------------------------------------- /med/allocator.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | simplest allocator in fixed buffer 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include "name.hpp" 16 | #include "exception.hpp" 17 | 18 | namespace med { 19 | 20 | struct null_allocator 21 | { 22 | [[nodiscard]] 23 | void* allocate(std::size_t bytes, std::size_t /*alignment*/) const 24 | { 25 | CODEC_TRACE("%s", __FUNCTION__); 26 | MED_THROW_EXCEPTION(out_of_memory, __FUNCTION__, bytes); 27 | } 28 | }; 29 | 30 | template 31 | T* create(ALLOCATOR& alloc, ARGs&&... args) 32 | { 33 | CODEC_TRACE("%s", __FUNCTION__); 34 | void* p = alloc.allocate(sizeof(T), alignof(T)); 35 | if (!p) { MED_THROW_EXCEPTION(out_of_memory, name(), sizeof(T)); } 36 | return new (p) T{std::forward(args)...}; 37 | } 38 | 39 | namespace detail { 40 | 41 | template 42 | struct allocator_holder 43 | { 44 | using allocator_type = T; 45 | explicit constexpr allocator_holder(allocator_type* p) : m_ref{*p} {} 46 | constexpr allocator_type& get_allocator() { return m_ref; } 47 | 48 | private: 49 | T& m_ref; 50 | }; 51 | 52 | template <> 53 | struct allocator_holder 54 | { 55 | static constexpr null_allocator instance{}; 56 | 57 | using allocator_type = const null_allocator; 58 | explicit constexpr allocator_holder(allocator_type*) {} 59 | constexpr allocator_type& get_allocator() { return instance; } 60 | }; 61 | 62 | } 63 | 64 | class allocator 65 | { 66 | public: 67 | allocator(allocator const&) = delete; 68 | allocator& operator=(allocator const&) = delete; 69 | constexpr allocator() noexcept = default; 70 | constexpr allocator(void* data, std::size_t size) { reset(data, size); } 71 | template 72 | explicit constexpr allocator(T (&data)[SIZE]) { reset(data); } 73 | 74 | /** 75 | * Resets to the current allocation buffer 76 | */ 77 | constexpr void release() noexcept { m_begin = m_start; m_size = m_total; } 78 | 79 | /** 80 | * Resets to new allocation buffer 81 | * @param data start of allocation buffer 82 | * @param size size of allocation buffer 83 | */ 84 | constexpr void reset(void* data, std::size_t size) noexcept 85 | { 86 | m_start = data; 87 | m_total = size; 88 | release(); 89 | } 90 | 91 | template 92 | constexpr void reset(T (&data)[SIZE]) noexcept { reset(data, SIZE * sizeof(T)); } 93 | 94 | /** 95 | * Allocates T from the beginning of current free buffer space 96 | * @return pointer to instance of T or nullptr/throw when out of space 97 | */ 98 | [[nodiscard]] 99 | constexpr void* allocate(std::size_t bytes, std::size_t alignment) noexcept 100 | { 101 | //void* p = std::align(alignment, bytes, m_begin, m_size); 102 | void* p = salign(alignment, bytes, m_begin, m_size); 103 | if (p) 104 | { 105 | m_begin = (uint8_t*)m_begin + bytes; 106 | m_size -= bytes; 107 | } 108 | return p; 109 | } 110 | 111 | private: 112 | static constexpr void* salign(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept 113 | { 114 | if (__space < __size) return nullptr; 115 | const auto __intptr = std::bit_cast(__ptr); 116 | const auto __aligned = (__intptr - 1u + __align) & -__align; 117 | const auto __diff = __aligned - __intptr; 118 | if (__diff > (__space - __size)) 119 | { 120 | return nullptr; 121 | } 122 | else 123 | { 124 | __space -= __diff; 125 | return __ptr = std::bit_cast(__aligned); 126 | } 127 | } 128 | 129 | void* m_begin {}; 130 | std::size_t m_size{}; 131 | void* m_start {}; 132 | std::size_t m_total {}; 133 | }; 134 | 135 | 136 | 137 | 138 | template 139 | constexpr T& get_allocator(T& t) { return t; } 140 | template 141 | constexpr T& get_allocator(T* pt) { return *pt; } 142 | template //requires requires(T v) { v.get_allocator() } 143 | constexpr auto get_allocator(T& t) -> std::add_lvalue_reference_t 144 | { 145 | auto& allocator = t.get_allocator(); 146 | static_assert(AAllocator, "IS NOT ALLOCATOR!"); 147 | return allocator; 148 | } 149 | template 150 | constexpr auto get_allocator(T* pt) -> std::add_lvalue_reference_tget_allocator())> 151 | { 152 | auto& allocator = pt->get_allocator(); 153 | static_assert(AAllocator, "IS NOT ALLOCATOR!"); 154 | return allocator; 155 | } 156 | 157 | } //end: namespace med 158 | -------------------------------------------------------------------------------- /doc/Representation-Layer.md: -------------------------------------------------------------------------------- 1 | # 1. Representation layer 2 | This layer is a front-end for library user. It consists of three types of building blocks - values, containers and presence definitions of values within a container. A container in its turn can be included as an aggregate value in another container thus constructing complex information elements. 3 | 4 | ## 1.1. Values 5 | ### 1.1.1. Generic integer 6 | Any message field that is of an integer type should be represented using med::value template class. The only template parameter it needs is the size of the integer field in bits. Only unsigned integers are supported with maximum size of 64 bits. 7 | 8 | A typical definition of a field looks like this: 9 | ```cpp 10 | struct afield : med::value<24>{ 11 | static constexpr char const* name() { return "Field A"; } 12 | }; 13 | ``` 14 | The definition of the field name is optional and only needed if you use med::printer and want this field to be printed. 15 | If the field has some restriction on its value you need to define the set member function returning bool as follows: 16 | ```cpp 17 | bool set(value_type v) { 18 | if (condition(v)){ 19 | base_t::set(v); 20 | return true; 21 | } 22 | return false; 23 | } 24 | ``` 25 | Note that you shouldn't use exceptions since the library is not designed this way because of performance reasons while error context is captured automatically and can be extracted later after encoding or decoding and used to throw an exception if needed. 26 | CAUTION: Do not override the get member functions to force any restrictions! 27 | 28 | ### 1.1.2. Octet string 29 | This value type represents a sequence of octets, i.e. an array or a string. There are 2 options for data storage in octet string - internal or external. The internal storage makes it possible to easily copy the field but impose a performance penalty both on CPU and memory usage. Thus it's only recommended for small string of complex structure. The external storage is the best option for lengthy strings as it only holds a pointer to the data and its size. Therefore it requires special care of the pointed data buffer and its lifetime relative to the lifetime of the message itself and is not suitable for copying of the field (see also the note on encoder/decoder features for a possible solution). 30 | The template of the octet string accepts up to 3 parameters to specify restrictions on minimum and/or maximal length, varying or fixed size and the storage type discussed. 31 | 32 | ## 1.2. Containers 33 | The containers are the means to build a message from information elements and a protocol from the messages as well as to define complex IEs. 34 | 35 | ### 1.2.1. Sequence 36 | This type of container represents a sequence of elements which follow in strict order one after another. It is the most common way to define a message and unlike the rest of containers it doesn't put any requirements on its elements. 37 | Example... 38 | 39 | ### 1.2.2. Set 40 | The set container is similar to the sequence container as it represents a number of elements. But elements of the set can appear in any order in encoded stream. To achieve this every element in the set must not only to have some identity but moreover the "structure" of their identity should be the same, that is every set element should have a common header. Such a header can be an integer tag or some sequence which knows how to form a tag equivalent (e.g. radius AVP header). 41 | 42 | ### 1.2.3. Choice 43 | Just like the set container the choice requires its elements to have a common header but only one element can appear in encoded stream. This type of container can always be found on the top level of any protocol. 44 | 45 | ### 1.2.4. Accessors 46 | The accessors of elements in containers are naturally divided in 2 categories - for encoding, read-write (ref); and for decoding, read-only (get): 47 | ```cpp 48 | auto const& esm = msg->get().get(); 49 | ``` 50 | 51 | The important thing to remember is that read-write accessors return reference of the field while read-only accessors return const reference for mandatory but const pointer for optional fields to reflect that optional field may not present in the decoded message. 52 | 53 | ## 1.3. Presence definitions 54 | This part of the library serves as a glue between elements and containers holding them. Besides specification of the presence itself it also allows to specify the arity of the element and its conditional dependencies if any. 55 | 56 | ### 1.3.1. Mandatory 57 | The `med::mandatory` accepts from 1 to 3 template arguments specifying the element's value itself, its tag and/or length, and optionally its arity (by default the arity of mandatory element is one). 58 | 59 | ### 1.3.2. Optional 60 | The `med::optional` is similar to the mandatory presence definition but requires a tag, a length, or a condition to define its presence. The default arity of the optional element is zero. 61 | -------------------------------------------------------------------------------- /med/octet_encoder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | octet encoder definition 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "bytes.hpp" 13 | #include "name.hpp" 14 | #include "state.hpp" 15 | #include "length.hpp" 16 | #include "octet_string.hpp" 17 | #include "sl/octet_info.hpp" 18 | #include "padding.hpp" 19 | 20 | namespace med { 21 | 22 | template 23 | struct octet_encoder : sl::octet_info 24 | { 25 | //required for length_encoder 26 | using state_type = typename ENC_CTX::buffer_type::state_type; 27 | template 28 | using padder_type = octet_padder; 29 | using allocator_type = typename ENC_CTX::allocator_type; 30 | 31 | explicit octet_encoder(ENC_CTX& ctx_) : m_ctx{ ctx_ } { } 32 | ENC_CTX& get_context() noexcept { return m_ctx; } 33 | allocator_type& get_allocator() { return get_context().get_allocator(); } 34 | 35 | //state 36 | auto operator() (GET_STATE) { return get_context().buffer().get_state(); } 37 | void operator() (SET_STATE, state_type const& st) { get_context().buffer().set_state(st); } 38 | template 39 | void operator() (SET_STATE, IE const& ie) 40 | { 41 | if (auto const ss = get_context().get_snapshot(ie)) 42 | { 43 | auto const len = field_length(ie, *this); 44 | if (ss.validate_length(len)) 45 | { 46 | get_context().buffer().set_state(ss); 47 | } 48 | else 49 | { 50 | MED_THROW_EXCEPTION(invalid_value, name(), len, get_context().buffer()) 51 | } 52 | } 53 | else 54 | { 55 | MED_THROW_EXCEPTION(missing_ie, name(), 1, 0, get_context().buffer()) 56 | } 57 | } 58 | 59 | template 60 | bool operator() (PUSH_STATE, IE const&) { return get_context().buffer().push_state(); } 61 | void operator() (POP_STATE) { get_context().buffer().pop_state(); } 62 | void operator() (ADVANCE_STATE ss) { get_context().buffer().template advance(ss.delta); } 63 | void operator() (ADD_PADDING pad) { get_context().buffer().template fill(pad.pad_size, pad.filler); } 64 | void operator() (SNAPSHOT ss) { get_context().put_snapshot(ss); } 65 | 66 | template constexpr std::size_t operator() (GET_LENGTH, IE const& ie) const noexcept 67 | { 68 | if constexpr (AMultiField) 69 | { 70 | std::size_t len = 0; 71 | for (auto& v : ie) { len += field_length(v, *this); } 72 | CODEC_TRACE("length(%s)*%zu = %zu", name(), ie.count(), len); 73 | return len; 74 | } 75 | else if constexpr (AHasSize) 76 | { 77 | CODEC_TRACE("length(%s) = %zu", name(), std::size_t(ie.size())); 78 | return ie.size(); 79 | } 80 | else 81 | { 82 | std::size_t const len = bits_to_bytes(IE::traits::bits); 83 | CODEC_TRACE("length(%s) = %zu", name(), len); 84 | return len; 85 | } 86 | } 87 | 88 | //IE_TAG/IE_LEN 89 | template void operator() (IE const& ie, IE_TAG) 90 | { (*this)(ie, typename IE::ie_type{}); } 91 | template void operator() (IE const& ie, IE_LEN) 92 | { (*this)(ie, typename IE::ie_type{}); } 93 | 94 | //IE_NULL 95 | template void operator() (IE const&, IE_NULL) 96 | { CODEC_TRACE("NULL[%s]: %s", name(), get_context().buffer().toString()); } 97 | 98 | //IE_VALUE 99 | template void operator() (IE const& ie, IE_VALUE) 100 | { 101 | constexpr auto NUM_BITS = IE::traits::bits + IE::traits::offset; 102 | constexpr auto NUM_BYTES = bits_to_bytes(NUM_BITS); 103 | uint8_t* out = get_context().buffer().template advance_bits(); 104 | if constexpr (IE::traits::offset == 0 && (IE::traits::bits % 8) == 0) 105 | { 106 | put_bytes(ie.get_encoded(), out); 107 | } 108 | else 109 | { 110 | auto val = size_t(ie.get_encoded()) << (NUM_BYTES * 8 - NUM_BITS); 111 | if constexpr (IE::traits::offset) 112 | { 113 | constexpr uint8_t MASK = ~((1 << (8 - IE::traits::offset)) - 1); 114 | val |= ((*out & MASK) << ((NUM_BYTES - 1) * 8)); 115 | } 116 | put_bytes(val, out); 117 | } 118 | CODEC_TRACE("V=%zXh %zu@%zu bits[%s]: %s", std::size_t(ie.get_encoded()), IE::traits::bits, IE::traits::offset, name(), get_context().buffer().toString()); 119 | } 120 | 121 | //IE_OCTET_STRING 122 | template void operator() (IE const& ie, IE_OCTET_STRING) 123 | { 124 | uint8_t* out = get_context().buffer().template advance(ie.size()); 125 | octets::copy(out, ie.data(), ie.size()); 126 | CODEC_TRACE("STR[%s] %zu octets: %s", name(), ie.size(), get_context().buffer().toString()); 127 | } 128 | 129 | private: 130 | ENC_CTX& m_ctx; 131 | }; 132 | 133 | } //end: namespace med 134 | -------------------------------------------------------------------------------- /med/concepts.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | concepts used in the library 4 | 5 | @copyright Denis Priyomov 2016-2022 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "meta/typelist.hpp" 13 | 14 | #include 15 | #include 16 | 17 | namespace med { 18 | 19 | template 20 | concept APointer = std::is_pointer_v; 21 | 22 | template 23 | concept AHasSize = requires(T const& v) 24 | { 25 | { v.size() } -> std::integral; 26 | }; 27 | 28 | template 29 | concept ADataContainer = AHasSize && requires(T v) 30 | { 31 | { v.data() } -> APointer; 32 | }; 33 | 34 | template 35 | concept ALength = requires(T v) 36 | { 37 | typename T::length_type; 38 | }; 39 | 40 | template 41 | concept AHasSetLength = requires(T& v) 42 | { 43 | { v.set_length(0) }; 44 | }; 45 | 46 | template 47 | concept AHasIeType = requires(T v) 48 | { 49 | typename T::ie_type; 50 | }; 51 | 52 | template 53 | concept AHasFieldType = requires(T v) 54 | { 55 | typename T::field_type; 56 | }; 57 | 58 | template 59 | struct get_field_type 60 | { 61 | using type = T; 62 | }; 63 | template 64 | struct get_field_type 65 | { 66 | using type = typename get_field_type::type; 67 | }; 68 | template using get_field_type_t = typename get_field_type::type; 69 | 70 | template 71 | concept AField = requires(T v) 72 | { 73 | { v.is_set() } -> std::same_as; 74 | { v.clear() }; 75 | //TODO: support strings or numeric-values 76 | //{ v.set_encoded(std::declval()) } -> std::same_as; 77 | //{ v.get_encoded() } -> std::same_as; 78 | }; 79 | 80 | template 81 | concept AMultiField = requires(T v) 82 | { 83 | typename T::field_value; 84 | { v.count() } -> std::unsigned_integral; 85 | // { v.is_set() } -> std::same_as; 86 | // { v.empty() } -> std::same_as; 87 | // { v.clear() }; 88 | // { v.pop_back() }; 89 | // { v.push_back() } -> std::same_as; 90 | }; 91 | 92 | //marker for the optional IEs 93 | struct optional_t {}; 94 | 95 | template 96 | concept AOptional = std::is_base_of_v; 97 | template 98 | concept AMandatory = !std::is_base_of_v; 99 | 100 | //checks if T looks like a functor to test condition of a field presense 101 | template 102 | concept ACondition = requires(T v) 103 | { 104 | { v(std::false_type{}) } -> std::same_as; 105 | }; 106 | 107 | template 108 | concept AHasCondition = requires(T v) 109 | { 110 | typename T::condition; 111 | }; 112 | 113 | // user-provided functor to extract field count 114 | template 115 | concept ACountGetter = requires(T v) 116 | { 117 | { v(std::false_type{}) } -> std::unsigned_integral; 118 | }; 119 | 120 | template 121 | concept ACounter = requires(T v) 122 | { 123 | typename T::counter_type; 124 | }; 125 | 126 | template 127 | concept AHasCountGetter = requires(T v) 128 | { 129 | typename T::count_getter; 130 | }; 131 | 132 | template 133 | concept AHasCount = requires(T const& v) 134 | { 135 | { v.count() } -> std::unsigned_integral; 136 | }; 137 | 138 | template 139 | concept ASetter = AField && requires(IE& ie, FUNC setter) 140 | { 141 | setter(ie, std::false_type{}); 142 | }; 143 | 144 | template 145 | concept AHasSetterType = requires(T v) 146 | { 147 | typename T::setter_type; 148 | }; 149 | 150 | //TODO: more concepts for setters/conditions/counters/etc. 151 | 152 | template 153 | concept ATag = requires(T v) 154 | { 155 | { T::match(0) } -> std::same_as; 156 | }; 157 | 158 | template 159 | concept AHasGetTag = requires(T const& v) 160 | { 161 | { v.get_tag() } -> std::convertible_to; 162 | }; 163 | 164 | template 165 | concept Arithmetic = std::floating_point || std::integral; 166 | 167 | template 168 | concept AValueTraits = requires (T v) 169 | { 170 | typename T::value_type; 171 | { T::bits + 0 } -> std::unsigned_integral; 172 | }; 173 | 174 | template 175 | concept AEmptyTypeList = meta::list_is_empty_v; 176 | 177 | template concept APredefinedValue = (T::is_defined == true); 178 | 179 | template 180 | concept AHasMetaInfo = !AEmptyTypeList; 181 | 182 | template 183 | concept AAllocator = requires (T v) 184 | { 185 | { v.allocate(std::size_t{}, std::size_t{}) } -> std::convertible_to; 186 | }; 187 | 188 | template 189 | concept APresentIn = std::same_as> or HAY::template has(); 190 | 191 | template 192 | concept ASameAs = (std::is_same_v || ...); 193 | 194 | } //end: namespace med 195 | -------------------------------------------------------------------------------- /ut/copy.cpp: -------------------------------------------------------------------------------- 1 | #include "ut.hpp" 2 | 3 | namespace cp { 4 | 5 | struct var_intern : med::octet_string, med::min<0>> {}; 6 | struct var_extern : med::octet_string, med::max<8>> {}; 7 | struct fix_intern : med::octet_string> {}; 8 | struct fix_extern : med::octet_string> {}; 9 | 10 | struct octs : med::sequence< 11 | O< T<1>, L, var_intern >, 12 | O< T<2>, L, var_extern >, 13 | O< T<3>, L, fix_intern >, 14 | O< T<4>, L, fix_extern > 15 | >{}; 16 | 17 | struct byte : med::value {}; 18 | struct word : med::value {}; 19 | struct dword : med::value {}; 20 | 21 | struct hdr : med::sequence< 22 | M, 23 | M 24 | > 25 | { 26 | auto get_tag() const { return get().get(); } 27 | void set_tag(uint8_t v) { return ref().set(v); } 28 | }; 29 | 30 | struct cho : med::choice< 31 | M, byte>, 32 | M, word>, 33 | M, dword> 34 | >{}; 35 | struct hdr_cho : med::choice< hdr 36 | , M, L, var_intern> 37 | , M, L, fix_extern> 38 | > 39 | { 40 | }; 41 | 42 | 43 | struct seq : med::sequence< 44 | M< T<0xF1>, byte, med::min<2>, med::inf >, //*[2,*) 45 | M< T<0xF2>, L, word, med::inf >, //(fixed)) 46 | M< med::counter_t, dword, med::inf > //C*[1,*) 47 | >{}; 48 | 49 | struct seq_r : med::sequence< 50 | M< T<0xE1>, dword, med::min<2>, med::inf >, 51 | M< T<0xE2>, L, byte, med::min<2>, med::inf >, 52 | M< med::counter_t, word, med::min<2>, med::inf > 53 | >{}; 54 | 55 | } //end: namespace cp 56 | 57 | TEST(copy, seq_same) 58 | { 59 | uint8_t const encoded[] = { 60 | 0xF1, 0x13, //TV 61 | 0xF1, 0x37, //TV 62 | 0xF2, 2, 0xFE, 0xE1, //TLV 63 | 0xF2, 2, 0xAB, 0xBA, //TLV 64 | 1, 0xDE, 0xAD, 0xBE, 0xEF, //CV 65 | }; 66 | 67 | cp::seq src_msg; 68 | cp::seq dst_msg; 69 | 70 | 71 | uint8_t dec_buf[128]; 72 | { 73 | med::allocator alloc{dec_buf}; 74 | med::decoder_context ctx{ encoded, &alloc }; 75 | decode(med::octet_decoder{ctx}, src_msg); 76 | //fails when need allocator but not provided one 77 | ASSERT_THROW(dst_msg.copy(src_msg), med::exception); 78 | //succeed with allocator 79 | dst_msg.copy(src_msg, ctx); 80 | } 81 | 82 | { 83 | uint8_t buffer[128]; 84 | med::encoder_context<> ctx{ buffer }; 85 | 86 | encode(med::octet_encoder{ctx}, dst_msg); 87 | EXPECT_EQ(sizeof(encoded), ctx.buffer().get_offset()); 88 | EXPECT_TRUE(Matches(encoded, buffer)); 89 | } 90 | } 91 | 92 | TEST(copy, seq_diff) 93 | { 94 | cp::seq src_msg; 95 | cp::seq_r dst_msg; 96 | uint8_t dec_buf[256]; 97 | 98 | { 99 | uint8_t const encoded[] = { 100 | 0xF1, 0x13, //TV(byte) 101 | 0xF1, 0x37, //TV(byte) 102 | 0xF2, 2, 0xFE, 0xE1, //TLV(word) 103 | 0xF2, 2, 0xAB, 0xBA, //TLV(word) 104 | 2, 0xDE, 0xAD, 0xBE, 0xEF, //CV(dword) 105 | 0xC0, 0x01, 0xCA, 0xFE, //CV(dword) 106 | }; 107 | 108 | med::allocator alloc{dec_buf}; 109 | med::decoder_context ctx{ encoded, &alloc }; 110 | decode(med::octet_decoder{ctx}, src_msg); 111 | dst_msg.copy(src_msg, ctx); 112 | } 113 | 114 | { 115 | // M< T<0xE1>, dword, med::min<2>, med::inf >, 116 | // M< T<0xE2>, L, byte, med::min<2>, med::inf >, 117 | // M< med::counter_t, word, med::min<2>, med::inf > 118 | uint8_t const encoded[] = { 119 | 0xE1, 0xDE, 0xAD, 0xBE, 0xEF, 120 | 0xE1, 0xC0, 0x01, 0xCA, 0xFE, 121 | 0xE2, 1, 0x13, 122 | 0xE2, 1, 0x37, 123 | 2, 0xFE, 0xE1, 124 | 0xAB, 0xBA, 125 | }; 126 | 127 | uint8_t buffer[128]; 128 | med::encoder_context<> ctx{ buffer }; 129 | 130 | encode(med::octet_encoder{ctx}, dst_msg); 131 | EXPECT_EQ(sizeof(encoded), ctx.buffer().get_offset()); 132 | EXPECT_TRUE(Matches(encoded, buffer)); 133 | } 134 | } 135 | 136 | TEST(copy, choice) 137 | { 138 | cp::cho src; 139 | src.ref().set(0xABBA); 140 | 141 | cp::cho dst; 142 | dst.ref().set(1); 143 | auto const* pb = dst.get(); 144 | auto const* pw = dst.get(); 145 | ASSERT_NE(nullptr, pb); 146 | ASSERT_EQ(nullptr, pw); 147 | dst.copy(src); 148 | 149 | pb = dst.get(); 150 | pw = dst.get(); 151 | ASSERT_EQ(nullptr, pb); 152 | ASSERT_NE(nullptr, pw); 153 | EXPECT_EQ(0xABBA, pw->get()); 154 | 155 | src.clear(); 156 | EXPECT_FALSE(src.is_set()); 157 | EXPECT_TRUE(dst.is_set()); 158 | src.copy_to(dst); 159 | EXPECT_TRUE(dst.is_set()); 160 | } 161 | 162 | TEST(copy, choice_hdr) 163 | { 164 | cp::hdr_cho src; 165 | src.header().ref().set(0xDADA); 166 | uint8_t const data[] = {1,2,3,4}; 167 | src.ref().set(data); 168 | 169 | 170 | cp::hdr_cho dst; 171 | dst.header().ref().set(0xBABA); 172 | dst.ref().set(data); 173 | src.copy_to(dst); 174 | 175 | auto const* pv = dst.get(); 176 | EXPECT_EQ(0xDADA, dst.header().get().get()); 177 | ASSERT_NE(nullptr, pv); 178 | //EXPECT_EQ(0xABBA, pw->get()); 179 | } 180 | -------------------------------------------------------------------------------- /med/meta/foreach.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | @file 4 | foreach 5 | 6 | @copyright Denis Priyomov 2019 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/ctstring) 9 | */ 10 | #include 11 | 12 | #include "typelist.hpp" 13 | 14 | namespace med::meta { 15 | 16 | namespace detail { 17 | 18 | template struct foreach; 19 | 20 | template 21 | struct foreach 22 | { 23 | template 24 | static constexpr void exec(F&& f, Args&&... args) 25 | { 26 | f.template apply(std::forward(args)...); 27 | foreach::template exec(std::forward(f), std::forward(args)...); 28 | } 29 | 30 | template 31 | static constexpr void exec_prev(F&& f, Args&&... args) 32 | { 33 | f.template apply(std::forward(args)...); 34 | foreach::template exec_prev(std::forward(f), std::forward(args)...); 35 | } 36 | }; 37 | 38 | template <> 39 | struct foreach<> 40 | { 41 | template 42 | static constexpr void exec(F&&, Args&&...) {} 43 | 44 | template 45 | static constexpr void exec_prev(F&&, Args&&...) {} 46 | }; 47 | 48 | } //end: namespace detail 49 | 50 | template 51 | constexpr auto foreach(F&& f, Ts&&... args) 52 | { 53 | return [] 54 | class List, class... Elems, class Func, class... Args> 55 | (List&&, Func&& f, Args&&... args) 56 | { 57 | return detail::foreach::exec(std::forward(f), std::forward(args)...); 58 | } 59 | (L{}, std::forward(f), std::forward(args)...); 60 | } 61 | 62 | template 63 | constexpr auto foreach_prev(F&& f, Ts&&... args) 64 | { 65 | return [] 66 | class List, class... Elems, class Func, class... Args> 67 | (List&&, Func&& f, Args&&... args) 68 | { 69 | return detail::foreach::template exec_prev(std::forward(f), std::forward(args)...); 70 | } 71 | (L{}, std::forward(f), std::forward(args)...); 72 | } 73 | 74 | ///////////////////////////////////////////// 75 | 76 | namespace detail { 77 | 78 | template 79 | struct fold; 80 | 81 | template 82 | struct fold 83 | { 84 | template 85 | static constexpr auto exec(F&& f, Args&&... args) 86 | { 87 | auto const r1 = f.template apply(std::forward(args)...); 88 | auto const r2 = fold::template exec(std::forward(f), std::forward(args)...); 89 | return f.op(r1, r2); 90 | } 91 | }; 92 | 93 | template <> 94 | struct fold<> 95 | { 96 | template 97 | static constexpr auto exec(F&& f, Args&&... args) 98 | { 99 | return f.template apply(std::forward(args)...); 100 | } 101 | }; 102 | 103 | } //end: namespace detail 104 | 105 | template 106 | constexpr auto fold(F&& f, Ts&&... args) 107 | { 108 | return [] 109 | class List, class... Elems, class Func, class... Args> 110 | (List&&, Func&& f, Args&&... args) 111 | { 112 | return detail::fold::exec(std::forward(f), std::forward(args)...); 113 | } 114 | (L{}, std::forward(f), std::forward(args)...); 115 | } 116 | 117 | ///////////////////////////////////////////// 118 | 119 | namespace detail { 120 | 121 | template 122 | struct for_if; 123 | 124 | template 125 | struct for_if 126 | { 127 | template 128 | static constexpr auto exec(F&& f, Args&&... args) 129 | { 130 | if (f.template check(std::forward(args)...)) 131 | { 132 | return f.template apply(std::forward(args)...); 133 | } 134 | else 135 | { 136 | return for_if::template exec(std::forward(f), std::forward(args)...); 137 | } 138 | } 139 | }; 140 | 141 | template <> 142 | struct for_if<> 143 | { 144 | template 145 | static constexpr auto exec(F&& f, Args&&... args) 146 | { 147 | return f.template apply(std::forward(args)...); 148 | } 149 | }; 150 | 151 | template class L, class... Elems, class F, class... Args> 152 | constexpr auto for_if_impl(L&&, F&& f, Args&&... args) 153 | { 154 | return for_if::exec(std::forward(f), std::forward(args)...); 155 | } 156 | 157 | } //end: namespace detail 158 | 159 | template 160 | constexpr auto for_if(F&& f, Args&&... args) 161 | { 162 | return detail::for_if_impl(L{}, std::forward(f), std::forward(args)...); 163 | } 164 | template 165 | constexpr auto for_if(bool cond, F&& f, Args&&... args) 166 | { 167 | return cond 168 | ? detail::for_if_impl(L{}, std::forward(f), std::forward(args)...) 169 | : detail::for_if_impl(typelist{}, std::forward(f), std::forward(args)...); 170 | } 171 | 172 | } //end: namespace med::meta 173 | -------------------------------------------------------------------------------- /med/encode.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | encoding entry point 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "config.hpp" 15 | #include "exception.hpp" 16 | #include "optional.hpp" 17 | #include "snapshot.hpp" 18 | #include "padding.hpp" 19 | #include "length.hpp" 20 | #include "name.hpp" 21 | #include "meta/typelist.hpp" 22 | 23 | 24 | namespace med { 25 | 26 | //structure layer 27 | namespace sl { 28 | 29 | //Tag 30 | template 31 | constexpr void encode_tag(ENCODER& encoder) 32 | { 33 | TAG_TYPE const ie{}; 34 | CODEC_TRACE("%s=%zX[%s]", __FUNCTION__, std::size_t(ie.get_encoded()), name()); 35 | encoder(ie, IE_TAG{}); 36 | } 37 | 38 | template 39 | constexpr void ie_encode(ENCODER& encoder, IE const& ie) 40 | { 41 | using META_INFO = typename TYPE_CTX::meta_info_type; 42 | using EXP_TAG = typename TYPE_CTX::explicit_tag_type; 43 | using EXP_LEN = typename TYPE_CTX::explicit_length_type; 44 | 45 | if constexpr (not meta::list_is_empty_v) 46 | { 47 | using mi = meta::list_first_t; 48 | using info_t = get_info_t; 49 | using exp_tag_t = conditional_t, info_t, EXP_TAG>; 50 | using exp_len_t = conditional_t, info_t, EXP_LEN>; 51 | using ctx = type_context, exp_tag_t, exp_len_t>; 52 | CODEC_TRACE("%s[%s]<%s:%s>: %s", __FUNCTION__, name(), name(), name(), class_name()); 53 | 54 | if constexpr (mi::kind == mik::TAG) 55 | { 56 | if constexpr (!APresentIn) 57 | { 58 | encode_tag(encoder); 59 | } 60 | else 61 | { 62 | CODEC_TRACE("skip explicit T[%s]", name()); 63 | } 64 | } 65 | else if constexpr (mi::kind == mik::LEN) 66 | { 67 | using len_t = info_t; 68 | auto len = sl::ie_length(ie, encoder); 69 | CODEC_TRACE("LV[%s]=%zX%c", name(), len, AMultiField?'*':' '); 70 | using dependency_t = get_dependency_t; 71 | if constexpr (!std::is_void_v) 72 | { 73 | auto const delta = len_t::dependency(ie.template get()); 74 | len -= delta; 75 | CODEC_TRACE("adjusted by %d L=%zXh [%s] dependent on %s", -delta, len, name(), name()); 76 | } 77 | 78 | if constexpr (APresentIn) 79 | { 80 | //TODO: a way to avoid cast? 81 | auto& ie_len = const_cast(ie).template ref(); 82 | length_to_value(ie_len, len); 83 | CODEC_TRACE("explicit LV[%s]=%zX", name(), std::size_t(ie_len.get_encoded())); 84 | } 85 | else 86 | { 87 | len_t ie_len; 88 | length_to_value(ie_len, len); 89 | encoder(ie_len, IE_LEN{}); 90 | } 91 | 92 | using pad_traits = typename get_padding::type; 93 | if constexpr (!std::is_void_v) 94 | { 95 | CODEC_TRACE("padded len_type=%s...:", name()); 96 | using pad_t = typename ENCODER::template padder_type; 97 | pad_t pad{encoder}; 98 | ie_encode(encoder, ie); 99 | pad.add_padding(); 100 | return; 101 | } 102 | } 103 | 104 | ie_encode(encoder, ie); 105 | } 106 | else 107 | { 108 | using ie_type = typename IE::ie_type; 109 | CODEC_TRACE("%s[%.30s]<%s:%s>: %s in %s", __FUNCTION__, name(), name(), name(), name(), name()); 110 | if constexpr (AContainer) 111 | { 112 | //special case for printer 113 | if constexpr (requires { typename ENCODER::container_encoder; }) 114 | { 115 | typename ENCODER::container_encoder{}(encoder, ie); 116 | } 117 | else 118 | { 119 | //TODO: inconsistent between choice and others 120 | //NOTE! EXP_TAG should be the 1st IE 121 | if constexpr (std::is_same_v && !std::is_void_v) 122 | { 123 | CODEC_TRACE(">>> %s<%s:%s>", name(), name(), name()); 124 | ie.template encode>(encoder); 125 | CODEC_TRACE("<<< %s<%s:%s>", name(), name(), name()); 126 | } 127 | else 128 | { 129 | CODEC_TRACE(">>> %s", name()); 130 | ie.encode(encoder); 131 | CODEC_TRACE("<<< %s", name()); 132 | } 133 | } 134 | } 135 | else 136 | { 137 | if constexpr (!std::is_same_v>) 138 | { 139 | put_snapshot(encoder, ie); 140 | } 141 | encoder(ie, ie_type{}); 142 | } 143 | } 144 | } 145 | 146 | } //end: namespace sl 147 | 148 | template 149 | constexpr void encode(ENCODER&& encoder, IE const& ie) 150 | { 151 | using META_INFO = meta::produce_info_t; 152 | CODEC_TRACE("mi=%s by %s for %s", class_name(), class_name(), class_name()); 153 | sl::ie_encode>(encoder, ie); 154 | } 155 | 156 | } //end: namespace med 157 | -------------------------------------------------------------------------------- /med/asn/ids.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | @file 5 | ASN.1 common constants and identifiers 6 | 7 | @copyright Denis Priyomov 2018 8 | Distributed under the MIT License 9 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 10 | */ 11 | 12 | #include 13 | 14 | #include "../traits.hpp" 15 | 16 | namespace med::asn { 17 | 18 | /* X.680 19 | 8.1: A tag is specified by giving a class and a number within the class. 20 | 8.2: The number is a non-negative integer, specified in decimal notation. 21 | 8.3 Restrictions on tags assigned by the user of ASN.1 are specified in 31.2 22 | NOTE: There is no formal difference between use of tags from the other three classes. Where 23 | application class tags are employed, a private or context-specific class tag could generally be 24 | applied instead, as a matter of user choice and style. The presence of the three classes is 25 | largely for historical reasons. 26 | 8.5 Some encoding rules require a canonical order for tags. 27 | 8.6 The canonical order for tags is based on the outermost tag of each type and is defined as follows: 28 | a) those elements or alternatives with universal class tags shall appear first, followed by those 29 | with application class tags, followed by those with context-specific tags, followed by those with 30 | private class tags; 31 | b) within each class of tags, the elements or alternatives shall appear in ascending order of their 32 | tag numbers. 33 | */ 34 | //NOTE: natural numbering to match the canonical order and X.690:Table 1 - Encoding of class of tag 35 | enum class tg_class : uint8_t 36 | { 37 | UNIVERSAL = 0b00, 38 | APPLICATION = 0b01, 39 | CONTEXT_SPECIFIC = 0b10, 40 | PRIVATE = 0b11, 41 | }; 42 | 43 | /* 44 | 3.8.77 tag: Additional information, separate from the abstract values of the type, which is 45 | associated with every ASN.1 type and which can be changed or augmented by a type prefix. 46 | NOTE – Tag information is used in some encoding rules to ensure that encodings are not ambiguous. 47 | Tag information differs from encoding instructions because tag information is associated with all 48 | ASN.1 types, even if they do not have a type prefix. 49 | 3.8.78 tagged types: A type defined by referencing a single existing type and a tag. 50 | 3.8.79 tagging: Assigning a new tag to a type, replacing or adding to the existing (possibly the default) tag. 51 | 3.8.87 type prefix: Part of the ASN.1 notation that can be used to assign an encoding instruction 52 | or a tag to a type. 53 | 54 | 9.1 An encoding instruction is assigned to a type using either a type prefix (31.3) or an encoding 55 | control section (54). 56 | 9.5 Multiple encoding instructions with the same or with different encoding references may be 57 | assigned to a type (using either or both of type prefixes and an encoding control section). 58 | Encoding instructions assigned with a given encoding reference are independent from those 59 | assigned with a different encoding reference, and from any use of a type prefix to perform tagging. 60 | 9.7 If an encoding instruction is assigned to the "Type" in a "TypeAssignment", it becomes 61 | associated with the type, and is applied wherever the "typereference" of the "TypeAssignment" 62 | is used. This includes use in other modules through the export and import statements. 63 | 64 | 31.1.2 A prefixed type is either a "TaggedType" or an "EncodingPrefixedType". 65 | 31.1.3 A prefixed type which is a tagged type is mainly of use where X680 requires the use of types 66 | with distinct tags (in seq, set, choice). The use of a "TagDefault" of AUTOMATIC TAGS in a module 67 | allows this to be accomplished without the explicit appearance of "TaggedType" in that module. 68 | 31.1.4 The assignment of an encoding instruction using an "EncodingPrefixedType" is only relevant to 69 | the encodings identified by the associated encoding reference and has no effect on the abstract 70 | values of the type. 71 | 31.1.5 NOTE – Specification would be simpler, however, historically, tagging was introduced 1st. 72 | Tagging also differs syntactically from assignment of encoding instructions: the specification 73 | that tagging is EXPLICIT or IMPLICIT occurs following the closing "]" of the tag. 74 | */ 75 | 76 | //Table 1 – Universal class tag assignments 77 | enum tg_value : uint8_t 78 | { 79 | BOOLEAN = 1, 80 | INTEGER = 2, 81 | BIT_STRING = 3, 82 | OCTET_STRING = 4, 83 | NULL_TYPE = 5, 84 | OBJECT_IDENTIFIER = 6, 85 | OBJECT_DESCRIPTOR = 7, 86 | EXTERNAL = 8, 87 | REAL = 9, 88 | ENUMERATED = 10, 89 | EMBEDDED_PDV = 11, 90 | UTF8_STRING = 12, 91 | RELATIVE_OID = 13, 92 | SEQUENCE = 16, 93 | SET = 17, 94 | //Character string types 95 | NUMERIC_STRING = 18, 96 | PRINTABLE_STRING = 19, 97 | T61_STRING = 20, 98 | VIDEOTEX_STRING = 21, 99 | IA5_STRING = 22, 100 | GRAPHIC_STRING = 25, 101 | VISIBLE_STRING = 26, 102 | GENERAL_STRING = 27, 103 | UNIVERSAL_STRING = 28, 104 | CHARACTER_STRING = 29, 105 | BMP_STRING = 30, 106 | //Time 107 | UTC_TIME = 23, 108 | GENERALIZED_TIME = 24, 109 | //Date 110 | DATE = 31, 111 | TIME_OF_DAY = 32, 112 | DATE_TIME = 33, 113 | DURATION = 34, 114 | //Internationalized Resource Identifiers 115 | OID_IRI = 35, 116 | RELATIVE_OID_IRI = 36, 117 | }; 118 | 119 | 120 | template 121 | struct traits 122 | { 123 | static constexpr auto AsnTagValue = TAG; 124 | static constexpr auto AsnTagClass = CLASS; 125 | }; 126 | 127 | 128 | } //end: namespace med::asn 129 | -------------------------------------------------------------------------------- /ut/choice.cpp: -------------------------------------------------------------------------------- 1 | #include "traits.hpp" 2 | #include "ut.hpp" 3 | 4 | namespace cho { 5 | 6 | //low nibble selector 7 | template 8 | struct LT : med::value>> {}; 9 | 10 | struct HI : med::value> {}; 11 | struct LO : med::value> {}; 12 | struct HL : med::sequence< 13 | M< HI >, 14 | M< LO > 15 | > 16 | {}; 17 | 18 | /* Binary Coded Decimal, i.e. each nibble is 0..9 19 | xxxx.xxxx 20 | TAG 1 21 | xxxx.xxxx|xxxx.xxxx 22 | TAG 1 2 F 23 | xxxx.xxxx|xxxx.xxxx 24 | TAG 1 2 3 25 | xxxx.xxxx|xxxx.xxxx|xxxx.xxxx 26 | TAG 1 2 3 4 F 27 | xxxx.xxxx|xxxx.xxxx|xxxx.xxxx 28 | TAG 1 2 3 4 5 29 | */ 30 | template 31 | struct BCD : med::sequence< 32 | M< LO >, 33 | O< HL, med::max<2> > 34 | >, med::add_meta_info< med::add_tag> > 35 | { 36 | static_assert(0 == (TAG & 0xF0), "LOW NIBBLE TAG EXPECTED"); 37 | static constexpr uint8_t END = 0xF; 38 | 39 | bool set(std::span data) 40 | { 41 | if (1 <= data.size() && data.size() <= 5) 42 | { 43 | auto it = data.begin(), ite = data.end(); 44 | this->template ref().set(*it++); 45 | while (it != ite) 46 | { 47 | auto* p = this->template ref().push_back(); 48 | p->template ref().set(*it++); 49 | p->template ref().set(it != ite ? *it++ : END); 50 | } 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | size_t size() const noexcept 57 | { 58 | size_t res = this->template get().is_set() ? 1 : 0; 59 | if (auto* p = this->template get()) 60 | { 61 | res += 2 * p->count(); 62 | res -= ((*p->last() & END) == END); 63 | } 64 | return res; 65 | } 66 | }; 67 | 68 | struct BCD_1 : BCD<1> 69 | { 70 | static constexpr char const* name() { return "BCD-1"; } 71 | }; 72 | struct BCD_2 : BCD<2> 73 | { 74 | static constexpr char const* name() { return "BCD-2"; } 75 | }; 76 | 77 | //nibble selected choice field 78 | struct FLD_NSCHO : med::choice< 79 | M< BCD_1 >, 80 | M< BCD_2 > 81 | > 82 | { 83 | }; 84 | 85 | struct ANY_TAG : med::value 86 | { 87 | static constexpr char const* name() { return "ANY"; } 88 | static constexpr bool match(value_type) { return true; } 89 | }; 90 | 91 | struct ANY_DATA : med::octet_string<> 92 | { 93 | static constexpr char const* name() { return "RAW data"; } 94 | }; 95 | 96 | struct UNKNOWN : med::sequence< 97 | M< ANY_TAG >, 98 | M< ANY_DATA > 99 | > 100 | { 101 | using container_t::get; 102 | 103 | ANY_TAG::value_type get() const { return this->get().get(); } 104 | void set(ANY_TAG::value_type v) { this->ref().set(v); } 105 | 106 | std::size_t size() const { return this->get().size(); } 107 | uint8_t const* data() const { return this->get().data(); } 108 | void set(std::size_t len, void const* data) { this->ref().set(len, data); } 109 | }; 110 | 111 | 112 | struct U8 : med::value{}; 113 | struct U16 : med::value{}; 114 | struct U32 : med::value{}; 115 | 116 | //choice based on plain value selector 117 | struct plain : med::choice< 118 | M< C<0x00>, L, U8 >, 119 | M< C<0x02>, L, U16 >, 120 | M< C<0x04>, L, U32 >, 121 | M< ANY_TAG, L, UNKNOWN > 122 | > 123 | {}; 124 | 125 | using PLAIN = M; 126 | 127 | } //end: namespace cho 128 | 129 | using namespace std::string_view_literals; 130 | using namespace cho; 131 | 132 | #if 1 133 | TEST(choice, plain) 134 | { 135 | uint8_t buffer[32]; 136 | med::encoder_context<> ctx{ buffer }; 137 | med::octet_encoder encoder{ctx}; 138 | 139 | PLAIN msg; 140 | EXPECT_EQ(0, msg.calc_length(encoder)); 141 | msg.ref().set(0); 142 | EXPECT_EQ(3, msg.calc_length(encoder)); 143 | msg.ref().set(0); 144 | EXPECT_EQ(4, msg.calc_length(encoder)); 145 | 146 | constexpr uint16_t magic = 0x1234; 147 | msg.ref().set(magic); 148 | encode(encoder, msg); 149 | EXPECT_STRCASEEQ("04 02 02 12 34 ", as_string(ctx.buffer())); 150 | 151 | PLAIN dmsg; 152 | med::decoder_context<> dctx; 153 | dctx.reset(ctx.buffer().used()); 154 | decode(med::octet_decoder{dctx}, dmsg); 155 | auto* pf = dmsg.get(); 156 | ASSERT_NE(nullptr, pf); 157 | EXPECT_EQ(magic, pf->get()); 158 | 159 | EXPECT_TRUE(pf->is_set()); 160 | 161 | ctx.buffer().reset(buffer, sizeof(buffer)); 162 | encode(encoder, dmsg); 163 | EXPECT_STRCASEEQ("04 02 02 12 34 ", as_string(ctx.buffer())); 164 | 165 | dmsg.clear(); 166 | //TODO: gcc-11.1.0 bug? 167 | // EXPECT_FALSE(pf->is_set()); 168 | } 169 | 170 | TEST(choice, any) 171 | { 172 | uint8_t encoded[] = {6, 3, 4, 5,6,7,8}; 173 | med::decoder_context<> ctx{encoded}; 174 | PLAIN msg; 175 | decode(med::octet_decoder{ctx}, msg); 176 | 177 | auto* pf = msg.get(); 178 | ASSERT_NE(nullptr, pf); 179 | EXPECT_EQ(3, pf->get().get()); 180 | EXPECT_EQ(4, pf->get().size()); 181 | 182 | uint8_t buffer[32]; 183 | med::encoder_context<> ectx{ buffer }; 184 | med::octet_encoder encoder{ectx}; 185 | encode(encoder, msg); 186 | EXPECT_STRCASEEQ("06 03 04 05 06 07 08 ", as_string(ectx.buffer())); 187 | } 188 | #endif 189 | #if 1 190 | TEST(choice, nibble_tag) 191 | { 192 | uint8_t buffer[32]; 193 | med::encoder_context<> ctx{ buffer }; 194 | med::octet_encoder encoder{ctx}; 195 | 196 | FLD_NSCHO msg; 197 | uint8_t const bcd[] = {3, 4, 5, 6}; 198 | auto& c = msg.ref(); 199 | //c.ref>().set(3); 200 | //EXPECT_EQ(1, c.ref>().get()); 201 | c.set(bcd); 202 | encode(encoder, msg); 203 | EXPECT_STRCASEEQ("13 45 6F ", as_string(ctx.buffer())); 204 | decltype(msg) dmsg; 205 | med::decoder_context<> dctx{ctx.buffer().used()}; 206 | decode(med::octet_decoder{dctx}, dmsg); 207 | auto* pf = dmsg.get(); 208 | ASSERT_NE(nullptr, pf); 209 | //EXPECT_EQ(msg.get()->size(), pf->size()); 210 | EXPECT_TRUE(msg == dmsg); 211 | 212 | EXPECT_TRUE(pf->is_set()); 213 | dmsg.clear(); 214 | EXPECT_FALSE(pf->is_set()); 215 | } 216 | #endif 217 | //NOTE: choice compound is tested in length.cpp ppp::proto 218 | -------------------------------------------------------------------------------- /med/asn/asn.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | @file 5 | ASN.1 6 | 7 | @copyright Denis Priyomov 2018 8 | Distributed under the MIT License 9 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 10 | */ 11 | #include "ids.hpp" 12 | #include "value.hpp" 13 | #include "bit_string.hpp" 14 | #include "sequence.hpp" 15 | #include "set.hpp" 16 | #include "concepts.hpp" 17 | 18 | namespace med::asn { 19 | 20 | template //TRAITs is asn::traits 21 | struct value_t : value, add_meta_info...>{}; 22 | 23 | template 24 | using boolean_t = value_t; 25 | using boolean = boolean_t>; 26 | 27 | template 28 | struct null_t : empty<>, add_meta_info...> {}; 29 | using null = null_t>; 30 | 31 | //NOTE! ASN assumes the integer is signed! don't use unsigned ever 32 | using integer = value_t>; 33 | 34 | template 35 | using enumerated_t = value_t; 36 | using enumerated = enumerated_t>; 37 | 38 | template 39 | using real_t = value_t; 40 | using real = real_t>; 41 | 42 | template 43 | struct bit_string_t : med::bit_string<>, add_meta_info...> {}; 44 | using bit_string = bit_string_t>; 45 | 46 | template 47 | struct octet_string_t : med::octet_string, add_meta_info...> {}; 48 | using octet_string = octet_string_t>; 49 | 50 | template 51 | struct sequence_t : med::sequence 52 | { 53 | using meta_info = META_INFO; 54 | }; 55 | template 56 | using sequence = sequence_t< 57 | meta::typelist>>, 58 | IES... 59 | >; 60 | 61 | template 62 | using sequence_of_t = multi_field; 63 | template 64 | using sequence_of = sequence_of_t< 65 | meta::typelist>>, 66 | IE, CMAX 67 | >; 68 | 69 | template 70 | struct set_t : med::set 71 | { 72 | using meta_info = META_INFO; 73 | }; 74 | template 75 | using set = set_t< 76 | meta::typelist>>, 77 | IES... 78 | >; 79 | 80 | /* 81 | NOTE: due to exessively bloat ASN.1 specs there are sequence-of and set-of. 82 | Both are about repeated *single* type (multi_field in terms of MED). 83 | And in all encoding rules except for DER/COER these ASN-types are encoded the same 84 | (but different default ASN-classes in BER). 85 | In DER/COER the elements of set-of are ordered increasingly by value prior to encoding. 86 | */ 87 | template 88 | using set_of_t = multi_field; 89 | template 90 | using set_of = set_of_t< 91 | meta::typelist>>, 92 | IE, CMAX 93 | >; 94 | 95 | template 96 | struct choice_t : med::choice 97 | { 98 | using meta_info = META_INFO; 99 | }; 100 | template 101 | using choice = choice_t< 102 | meta::typelist<>, 103 | IES... 104 | >; 105 | 106 | using subid_t = uintmax_t; 107 | //TODO: join CMAX and TRAITS and filter out if 1st specified 108 | //Relative OID has at least 1 component 109 | template 110 | using relative_oid_t = multi_field< 111 | value, 1, CMAX, 112 | meta::typelist...> 113 | >; 114 | template 115 | using relative_oid = relative_oid_t>; 116 | 117 | namespace detail { 118 | 119 | //another example of marvelous ASN.1 (what?!1...) 120 | static constexpr subid_t OID_ROOT_FACTOR = 40; 121 | 122 | //extract root (1st component) from the 1st subidentifier 123 | constexpr uint8_t oid_root(subid_t v) 124 | { 125 | return (v < 2 * OID_ROOT_FACTOR) ? (v / OID_ROOT_FACTOR) : 2; 126 | } 127 | 128 | //extract subroot (2nd component) from the 1st subidentifier 129 | constexpr subid_t oid_subroot(subid_t v) 130 | { 131 | return v - OID_ROOT_FACTOR * oid_root(v); 132 | } 133 | 134 | //construct subidentifier from two 1st components 135 | constexpr subid_t oid_make_1st(uint8_t root, subid_t subroot) 136 | { 137 | if ((root < 2 && subroot >= OID_ROOT_FACTOR) 138 | || root > 2) 139 | { 140 | MED_THROW_EXCEPTION(invalid_value, __FUNCTION__, subroot); 141 | } 142 | return root * OID_ROOT_FACTOR + subroot; 143 | } 144 | 145 | } //end: namespace detail 146 | 147 | //OID has at least 2 components 148 | template 149 | struct object_identifier_t : relative_oid_t 150 | { 151 | //returns 1st two components 152 | std::pair root() const 153 | { 154 | subid_t const v = this->empty() ? 0 : this->first()->get(); 155 | return std::pair{detail::oid_root(v), detail::oid_subroot(v)}; 156 | } 157 | void root(subid_t root_value, subid_t subroot_value) 158 | { 159 | if (this->empty()) { this->push_back(); } 160 | this->first()->set(detail::oid_make_1st(root_value, subroot_value)); 161 | } 162 | }; 163 | 164 | template 165 | using object_identifier = object_identifier_t>; 166 | 167 | 168 | template struct is_oid : std::false_type { }; 169 | template requires (!AHasMetaInfo) 170 | struct is_oid : std::true_type { }; 171 | template constexpr bool is_oid_v = is_oid::value; 172 | 173 | template struct is_seqof : std::false_type { }; 174 | template requires AHasMetaInfo 175 | struct is_seqof : std::true_type { }; 176 | template constexpr bool is_seqof_v = is_seqof::value; 177 | 178 | } //end: namespace med::asn 179 | -------------------------------------------------------------------------------- /med/length.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | length type definition and traits 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "exception.hpp" 13 | #include "ie_type.hpp" 14 | #include "field.hpp" 15 | #include "state.hpp" 16 | #include "value_traits.hpp" 17 | #include "name.hpp" 18 | #include "meta/typelist.hpp" 19 | #include "padding.hpp" 20 | #include "concepts.hpp" 21 | #include "accessor.hpp" 22 | 23 | 24 | namespace med { 25 | 26 | template 27 | struct length_t 28 | { 29 | using length_type = LENGTH; 30 | }; 31 | 32 | namespace detail { 33 | 34 | template 35 | concept AHasGetLength = requires(T const& v) 36 | { 37 | { v.get_length() } -> std::integral; 38 | }; 39 | 40 | template 41 | struct get_dependency { using type = void; }; 42 | template requires requires(T v){ typename T::dependency_type; } 43 | struct get_dependency 44 | { 45 | using type = typename T::dependency_type; 46 | }; 47 | 48 | } //end: namespace detail 49 | 50 | template 51 | using get_dependency_t = typename detail::get_dependency::type; 52 | 53 | 54 | namespace sl { 55 | 56 | template 57 | constexpr std::size_t ie_length(IE const& ie, ENCODER& encoder) noexcept 58 | { 59 | using META_INFO = typename TYPE_CTX::meta_info_type; 60 | using EXP_TAG = typename TYPE_CTX::explicit_tag_type; 61 | using EXP_LEN = typename TYPE_CTX::explicit_length_type; 62 | 63 | std::size_t len = 0; 64 | 65 | if constexpr (not meta::list_is_empty_v) 66 | { 67 | using mi = meta::list_first_t; 68 | using info_t = get_info_t; 69 | using exp_tag_t = conditional_t, info_t, EXP_TAG>; 70 | using exp_len_t = conditional_t, info_t, EXP_LEN>; 71 | 72 | //TODO: pass calculated length to length_t when sizeof(len) depends on value like in ASN.1 BER 73 | CODEC_TRACE("%s[%s]<%s:%s>: %s", __FUNCTION__, name(), name(), name(), name()); 74 | len += ie_length, exp_tag_t, exp_len_t>>(ie, encoder); 75 | if constexpr (mi::kind == mik::LEN) 76 | { 77 | //TODO: involve codec to get length type + may need to set its value like for BER 78 | using pad_traits = typename get_padding::type; 79 | if constexpr (!std::is_void_v) 80 | { 81 | using pad_t = typename ENCODER::template padder_type; 82 | #ifdef CODEC_TRACE_ENABLE 83 | auto const add_len = pad_t::calc_padding_size(len); 84 | CODEC_TRACE("padded len_type=%s: %zu + %zu = %zu", name(), size_t(len), add_len, len + add_len); 85 | len += add_len; 86 | #else 87 | len += pad_t::calc_padding_size(len); 88 | #endif 89 | } 90 | } 91 | //calc length of LEN or TAG itself 92 | len += ie_length, EXP_TAG, EXP_LEN>>(info_t{}, encoder); 93 | } 94 | else //data itself 95 | { 96 | CODEC_TRACE("%s[%s]<%s:%s> - DATA", __FUNCTION__, name(), name(), name()); 97 | if constexpr (AContainer) 98 | { 99 | using ctx = type_context, EXP_TAG, EXP_LEN>; 100 | CODEC_TRACE("%s[%.30s]%s<%s:%s>: %s", __FUNCTION__, name(), AMultiField?"*":"", name(), name(), name()); 101 | len += ie.template calc_length(encoder); 102 | CODEC_TRACE("%s[%s] : len(SEQ) = %zu", __FUNCTION__, name(), len); 103 | } 104 | //NOTE: can't unroll multi-field here because for ASN.1 the OID and SEQENCE-OF 105 | //are based on multi-field but need different length calculation thus it's passed 106 | //directly to encoder 107 | else if constexpr (ASameAs, EXP_TAG, EXP_LEN>) 108 | { 109 | CODEC_TRACE("%s[%s] : skip explicit", __FUNCTION__, name()); 110 | } 111 | else 112 | { 113 | //CODEC_TRACE("<%s:%s> : %s", class_name(), class_name(), class_name()); 114 | len += encoder(GET_LENGTH{}, ie); 115 | CODEC_TRACE("%s[%s] : len(VAL) = %zu", __FUNCTION__, name(), len); 116 | } 117 | } 118 | CODEC_TRACE("%s[%s]<%s:%s> -> %zu", __FUNCTION__, name(), name(), name(), len); 119 | return len; 120 | } 121 | 122 | } //end: namespace sl 123 | 124 | template 125 | constexpr std::size_t field_length(IE const& ie, ENCODER& encoder) noexcept 126 | { 127 | using mi = meta::produce_info_t; 128 | //CODEC_TRACE("%s[%s]", __FUNCTION__, name()); 129 | return sl::ie_length>(ie, encoder); 130 | } 131 | 132 | 133 | template 134 | constexpr void length_to_value(FIELD& field, std::size_t len) 135 | { 136 | //set the length IE with the value 137 | if constexpr (AHasSetLength) 138 | { 139 | if constexpr (std::is_same_v) 140 | { 141 | if (not field.set_length(len)) 142 | { 143 | MED_THROW_EXCEPTION(invalid_value, name(), len) 144 | } 145 | } 146 | else 147 | { 148 | field.set_length(len); 149 | } 150 | } 151 | else if constexpr (std::is_same_v) 152 | { 153 | if (not field.set_encoded(len)) 154 | { 155 | MED_THROW_EXCEPTION(invalid_value, name(), len) 156 | } 157 | } 158 | else 159 | { 160 | field.set_encoded(len); 161 | } 162 | CODEC_TRACE("L=%zXh(%zX) [%s]:", len, std::size_t(field.get_encoded()), name()); 163 | } 164 | 165 | template 166 | constexpr std::size_t value_to_length(FIELD& field) 167 | { 168 | //use proper length accessor 169 | if constexpr (detail::AHasGetLength) 170 | { 171 | std::size_t const len = field.get_length(); 172 | CODEC_TRACE("LV[%s]=%zu", name(), len); 173 | return len; 174 | } 175 | else 176 | { 177 | std::size_t const len = field.get_encoded(); 178 | CODEC_TRACE("LV[%s]=%zu", name(), len); 179 | return len; 180 | } 181 | } 182 | 183 | } //end: namespace med 184 | -------------------------------------------------------------------------------- /med/printer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | special case of encoder to print IEs via user-provided sink 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | 13 | #include "encode.hpp" 14 | #include "exception.hpp" 15 | #include "name.hpp" 16 | #include "sl/octet_info.hpp" 17 | 18 | /* 19 | struct SinkSample 20 | { 21 | template 22 | void on_value(std::size_t depth, char const* name, T const& value) 23 | { 24 | printf("%*c%s=%u (%Xh)\n", depth, ' ', name, value, value); 25 | } 26 | 27 | void on_custom(std::size_t depth, char const* name, std::string const& s) 28 | { 29 | printf("%*c%s=%s\n", depth, ' ', name, s.c_str()); 30 | } 31 | 32 | void on_error(char const* err) 33 | { 34 | printf("%s\n", err); 35 | } 36 | }; 37 | */ 38 | 39 | namespace med { 40 | 41 | //MAX_LINE - max length of a single output of IE.print 42 | template 43 | class printer : public sl::octet_info 44 | { 45 | template 46 | using int_t = std::integral_constant; 47 | 48 | template 49 | struct has_print 50 | { 51 | //check for: void IE::print(char (&sz)[N]) 52 | template 53 | static auto test(P* p) -> decltype(std::declval().print(*p), int_t<2>{}); 54 | 55 | //check for: some_type IE::print() 56 | template 57 | static std::enable_if_t().print())>, int_t<1>> 58 | test(P* p); 59 | 60 | template 61 | static int_t<0> test(...); 62 | 63 | using type = decltype(test(nullptr)); 64 | }; 65 | 66 | public: 67 | using allocator_type = const null_allocator; 68 | 69 | class container_encoder 70 | { 71 | public: 72 | template 73 | void operator()(printer& me, IE const& ie) 74 | { 75 | if constexpr (AHasName) 76 | { 77 | return print_named(me, ie, typename has_print::type{}); 78 | } 79 | else 80 | { 81 | //treat un-named container as logical group -> depth not changing 82 | return ie.encode(me); 83 | } 84 | } 85 | 86 | private: 87 | //customized prints 88 | template 89 | void print_named(printer& me, IE const& ie, int_t<2> pt) 90 | { 91 | me.print_named(ie, pt); 92 | } 93 | 94 | template 95 | void print_named(printer& me, IE const& ie, int_t<1> pt) 96 | { 97 | me.print_named(ie, pt); 98 | } 99 | 100 | //no customized print, change depth level 101 | template 102 | void print_named(printer& me, IE const& ie, int_t<0>) 103 | { 104 | me.m_sink.on_container(me.m_depth, name()); 105 | auto const depth = me.m_depth++; 106 | CODEC_TRACE("depth -> %zu < max=%zu", me.m_depth, me.m_max_depth); 107 | if (0 == me.m_max_depth || me.m_max_depth > me.m_depth) { ie.encode(me); } 108 | me.m_depth = depth; 109 | CODEC_TRACE("depth <- %zu", depth); 110 | } 111 | }; 112 | 113 | 114 | printer(SINK&& sink, std::size_t max_depth) noexcept 115 | : m_sink{ std::forward(sink) } 116 | , m_max_depth{ max_depth } 117 | { 118 | } 119 | 120 | //primitive 121 | template 122 | constexpr void operator() (IE const& ie, IE_TYPE const&) 123 | { 124 | if constexpr (AHasName) 125 | { 126 | print_named(ie, typename has_print::type{}); 127 | } 128 | } 129 | 130 | //state 131 | constexpr void operator() (SNAPSHOT) { } 132 | // length encoder 133 | template constexpr std::size_t operator()(GET_LENGTH, IE const &) { return 0; } 134 | 135 | private: 136 | friend class container_encoder; 137 | 138 | //customized print 2 139 | template 140 | void print_named(IE const& ie, int_t<2>) 141 | { 142 | char sz[MAX_LINE]; 143 | ie.print(sz); 144 | m_sink.on_custom(m_depth, name(), sz); 145 | } 146 | 147 | //customized print 1 148 | template 149 | void print_named(IE const& ie, int_t<1>) 150 | { 151 | m_sink.on_custom(m_depth, name(), ie.print()); 152 | } 153 | 154 | //regular print 155 | template 156 | void print_named(IE const& ie, int_t<0>) 157 | { 158 | m_sink.on_value(m_depth, IE::name(), ie.get()); 159 | } 160 | 161 | SINK m_sink; 162 | std::size_t m_depth {0}; 163 | std::size_t const m_max_depth; 164 | }; 165 | 166 | //print named IEs only up to given depth (not including, i.e. < max_depth) 167 | //or full depth when max_depth=0 168 | template 169 | void print(SINK&& sink, IE const& ie, std::size_t max_depth = 0) 170 | { 171 | try 172 | { 173 | encode(printer{std::forward(sink), max_depth}, ie); 174 | } 175 | catch (exception const& ex) 176 | { 177 | sink.on_error(ex.what()); 178 | } 179 | } 180 | 181 | 182 | 183 | template 184 | struct dumper : public sl::octet_info 185 | { 186 | using allocator_type = const null_allocator; 187 | 188 | struct container_encoder 189 | { 190 | template 191 | void operator()(dumper& me, IE const& ie) 192 | { 193 | me.m_sink.on_container(me.m_depth, name()); 194 | auto const depth = me.m_depth++; 195 | ie.encode(me); 196 | me.m_depth = depth; 197 | } 198 | }; 199 | 200 | explicit dumper(SINK&& sink) : m_sink{ std::forward(sink) } {} 201 | 202 | //primitives 203 | template 204 | void operator() (IE const& ie, PRIMITIVE) { m_sink.on_value(m_depth, name(), ie.get()); } 205 | 206 | //state 207 | constexpr void operator() (SNAPSHOT) const noexcept { } 208 | //length encoder 209 | template constexpr std::size_t operator()(GET_LENGTH, IE const &) { return 0; } 210 | 211 | template constexpr void operator() (IE const&, IE_TAG) const noexcept {} 212 | template constexpr void operator() (IE const&, IE_LEN) const noexcept {} 213 | 214 | SINK m_sink; 215 | std::size_t m_depth {0}; 216 | }; 217 | 218 | //print all (named and not) IEs in full depth 219 | template 220 | void print_all(SINK&& sink, IE const& ie) 221 | { 222 | dumper d{std::forward(sink)}; 223 | try 224 | { 225 | encode(d, ie); 226 | } 227 | catch (exception const& ex) 228 | { 229 | d.m_sink.on_error(ex.what()); 230 | } 231 | } 232 | 233 | } //namespace med 234 | -------------------------------------------------------------------------------- /ut/print.cpp: -------------------------------------------------------------------------------- 1 | #include "printer.hpp" 2 | 3 | #include "ut.hpp" 4 | #include "ut_proto.hpp" 5 | 6 | namespace prn { 7 | 8 | struct U8 : med::value 9 | { 10 | static constexpr char const* name() { return "U8"; } 11 | }; 12 | 13 | struct U16 : med::value 14 | { 15 | static constexpr char const* name() { return "U16"; } 16 | }; 17 | 18 | 19 | //customized print of container 20 | template 21 | struct DEEP_SEQ : med::sequence< M, M > 22 | { 23 | static constexpr char const* name() { return "DeepSeq"; } 24 | template void print(char (&sz)[N]) const 25 | { 26 | std::snprintf(sz, N, "%u#%04X", this->template get().get(), this->template get().get()); 27 | } 28 | }; 29 | 30 | template 31 | struct DEEP_SEQ1 : med::sequence< M> > 32 | { 33 | static constexpr char const* name() { return "DeepSeq1"; } 34 | }; 35 | 36 | template 37 | struct DEEP_SEQ2 : med::sequence< M> > 38 | { 39 | static constexpr char const* name() { return "DeepSeq2"; } 40 | }; 41 | 42 | template 43 | struct DEEP_SEQ3 : med::sequence< M> > 44 | { 45 | static constexpr char const* name() { return "DeepSeq3"; } 46 | }; 47 | template 48 | struct DEEP_SEQ4 : med::sequence< M> > 49 | { 50 | static constexpr char const* name() { return "DeepSeq4"; } 51 | }; 52 | 53 | struct DEEP_MSG : med::sequence< 54 | M< DEEP_SEQ<0> >, 55 | M< DEEP_SEQ1<0> >, 56 | M< DEEP_SEQ2<0> >, 57 | M< DEEP_SEQ3<0> >, 58 | M< DEEP_SEQ4<0> >, 59 | M< DEEP_SEQ<1> >, 60 | M< DEEP_SEQ1<1> >, 61 | M< DEEP_SEQ2<1> >, 62 | M< DEEP_SEQ3<1> >, 63 | M< DEEP_SEQ4<1> > 64 | > 65 | { 66 | static constexpr char const* name() { return "DeepMsg"; } 67 | }; 68 | 69 | } //end: namespace prn 70 | 71 | TEST(print, container) 72 | { 73 | uint8_t const encoded[] = { 74 | 1,0,2, 75 | 2,0,3, 76 | 3,0,4, 77 | 5,0,6, 78 | 7,0,8, 79 | 80 | 11,0,12, 81 | 12,0,13, 82 | 13,0,14, 83 | 15,0,16, 84 | 17,0,18, 85 | }; 86 | 87 | prn::DEEP_MSG msg; 88 | med::decoder_context<> ctx; 89 | 90 | ctx.reset(encoded, sizeof(encoded)); 91 | decode(med::octet_decoder{ctx}, msg); 92 | 93 | std::size_t const cont_num[6] = { 21, 1, 9, 15, 19, 21 }; 94 | std::size_t const cust_num[6] = { 10, 0, 2, 4, 6, 8 }; 95 | 96 | for (std::size_t level = 0; level < std::extent_v; ++level) 97 | { 98 | dummy_sink d{0}; 99 | 100 | med::print(d, msg, level); 101 | EXPECT_EQ(cont_num[level], d.num_on_container); 102 | EXPECT_EQ(cust_num[level], d.num_on_custom); 103 | EXPECT_EQ(0, d.num_on_value); 104 | } 105 | } 106 | 107 | TEST(print_all, container) 108 | { 109 | uint8_t const encoded[] = { 110 | 1,0,2, 111 | 2,0,3, 112 | 3,0,4, 113 | 5,0,6, 114 | 7,0,8, 115 | 116 | 11,0,12, 117 | 12,0,13, 118 | 13,0,14, 119 | 15,0,16, 120 | 17,0,18, 121 | }; 122 | 123 | prn::DEEP_MSG msg; 124 | med::decoder_context<> ctx; 125 | 126 | ctx.reset(encoded, sizeof(encoded)); 127 | decode(med::octet_decoder{ctx}, msg); 128 | 129 | dummy_sink d{0}; 130 | med::print_all(d, msg); 131 | EXPECT_EQ(31, d.num_on_container); 132 | EXPECT_EQ(20, d.num_on_value); 133 | ASSERT_EQ(0, d.num_on_custom); 134 | } 135 | 136 | struct EncData 137 | { 138 | std::vector encoded; 139 | }; 140 | 141 | std::ostream& operator<<(std::ostream& os, EncData const& param) 142 | { 143 | return os << param.encoded.size() << " bytes"; 144 | } 145 | 146 | class PrintUt : public ::testing::TestWithParam 147 | { 148 | public: 149 | 150 | protected: 151 | PROTO m_proto; 152 | med::decoder_context<> m_ctx; 153 | 154 | private: 155 | virtual void SetUp() override 156 | { 157 | EncData const& param = GetParam(); 158 | m_ctx.reset(param.encoded.data(), param.encoded.size()); 159 | } 160 | }; 161 | 162 | 163 | TEST_P(PrintUt, levels) 164 | { 165 | decode(med::octet_decoder{m_ctx}, m_proto); 166 | 167 | dummy_sink d{0}; 168 | med::print(d, m_proto, 1); 169 | 170 | std::size_t const num_prints[2][3] = { 171 | {1, 0, 0}, 172 | {2, 1, 1}, 173 | }; 174 | 175 | EXPECT_EQ(num_prints[0][0], d.num_on_container); 176 | EXPECT_EQ(num_prints[0][1], d.num_on_custom); 177 | EXPECT_EQ(num_prints[0][2], d.num_on_value); 178 | EXPECT_EQ(0, d.num_on_error); 179 | 180 | med::print(d, m_proto, 2); 181 | EXPECT_EQ(num_prints[1][0], d.num_on_container); 182 | EXPECT_LE(num_prints[1][1], d.num_on_custom); 183 | EXPECT_LE(num_prints[1][2], d.num_on_value); 184 | EXPECT_EQ(0, d.num_on_error); 185 | } 186 | 187 | TEST_P(PrintUt, incomplete) 188 | { 189 | EncData const& param = GetParam(); 190 | m_ctx.reset(param.encoded.data(), 2); 191 | 192 | EXPECT_THROW(decode(med::octet_decoder{m_ctx}, m_proto), med::exception); 193 | 194 | dummy_sink d{0}; 195 | med::print(d, m_proto); 196 | 197 | std::size_t const num_prints[4] = { 198 | #ifdef CODEC_TRACE_ENABLE 199 | 1, 0, 2, 1 200 | #else 201 | 1, 0, 1, 1 202 | #endif 203 | }; 204 | 205 | EXPECT_EQ(num_prints[0], d.num_on_container); 206 | EXPECT_EQ(num_prints[1], d.num_on_custom); 207 | EXPECT_GE(num_prints[2], d.num_on_value); 208 | EXPECT_EQ(num_prints[3], d.num_on_error); 209 | } 210 | 211 | EncData const test_prints[] = { 212 | { 213 | { 1 //MSG_SEQ 214 | , 37 215 | , 0x21, 0x35, 0xD9 216 | , 3, 0xDA, 0xBE, 0xEF 217 | , 0x42, 4, 0xFE, 0xE1, 0xAB, 0xBA 218 | , 0x51, 0x01, 0x02, 0x03, 0x04 219 | , 0,1, 2, 0x21, 0,3 220 | , 0x12, 4, 't', 'e', 's', 't', '.', 't', 'h', 'i', 's', '!' 221 | } 222 | }, 223 | { 224 | { 4 //MSG_SET 225 | , 0, 0x22, 9, 't', 'e', 's', 't', '.', 't', 'h', 'i', 's' 226 | , 0, 0x89, 0xFE, 0xE1, 0xAB, 0xBA 227 | , 0, 0x49, 3, 0xDA, 0xBE, 0xEF 228 | , 0, 0x21, 2, 0x35, 0xD9 229 | , 0, 0x0b, 0x11 230 | } 231 | }, 232 | { 233 | { 0x14 //MSG_MSET 234 | , 0, 0x22, 9, 't', 'e', 's', 't', '.', 't', 'h', 'i', 's' 235 | , 0, 0x22, 7, 't', 'e', 's', 't', '.', 'i', 't' 236 | , 0, 0x89, 0xFE, 0xE1, 0xAB, 0xBA 237 | , 0, 0x0b, 0x11 238 | , 0, 0x49, 3, 0xDA, 0xBE, 0xEF 239 | , 0, 0x49, 3, 0x22, 0xBE, 0xEF 240 | , 0, 0x21, 2, 0x35, 0xD9 241 | , 0, 0x21, 2, 0x35, 0xDA 242 | , 0, 0x89, 0xAB, 0xBA, 0xC0, 0x01 243 | , 0, 0x0b, 0x12 244 | , 0, 0x0c, 0x13 245 | } 246 | }, 247 | { 248 | { 0x11 //MSG_MSEQ 249 | , 37, 38 //M< FLD_UC, med::arity<2> > 250 | , 0x21,0x35,0xD9, 0x21,0x35,0xDA //M< T<0x21>, FLD_U16, med::arity<2> > 251 | , 3, 0xDA, 0xBE, 0xEF //M< L, FLD_U24, med::arity<2> > 252 | , 3, 0x22, 0xBE, 0xEF 253 | , 0x42, 4, 0xFE, 0xE1, 0xAB, 0xBA //M< T<0x42>, L, FLD_IP, med::max<2> > 254 | , 0x51, 0x01, 0x02, 0x03, 0x04 //M< T<0x51>, FLD_DW, med::max<2> > 255 | , 0,1, 1, 0x21, 0,2 //M <=2 256 | , 0x60, 1, 0x06 257 | } 258 | }, 259 | }; 260 | 261 | //INSTANTIATE_TEST_CASE_P(print, PrintUt, ::testing::ValuesIn(test_prints)); 262 | INSTANTIATE_TEST_SUITE_P(print, PrintUt, ::testing::ValuesIn(test_prints)); 263 | 264 | -------------------------------------------------------------------------------- /med/bit_string.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | bit string IE definition 4 | 5 | @copyright Denis Priyomov 2019 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "octet_string.hpp" 13 | 14 | namespace med { 15 | 16 | namespace detail { 17 | 18 | template 19 | struct bitstr_traits : EXT_TRAITS... 20 | { 21 | static constexpr std::size_t min_bits = MIN_BITS; 22 | static constexpr std::size_t max_bits = MAX_BITS; 23 | using value_type = uint8_t; 24 | }; 25 | 26 | } 27 | 28 | //variable length bits 29 | class bits_variable 30 | { 31 | public: 32 | bool is_set() const { return m_least_bits > 0; } 33 | 34 | //total number of bytes = ceil(number of bits) 35 | std::size_t size() const { return m_num_bytes; } 36 | nbits num_of_bits() const { return nbits{8*size() - (8 - m_least_bits)}; } 37 | nbits least_bits() const { return nbits{m_least_bits}; } 38 | 39 | uint8_t const* data() const { return inplace() && is_set() ? m_data.internal : m_data.external; } 40 | 41 | std::size_t uint() const 42 | { 43 | if (not inplace()) { throw invalid_value("too many bits", std::size_t(num_of_bits())); } 44 | std::size_t v = m_data.internal[0]; 45 | for (std::size_t n = 1; n < size(); ++n) { v = (v << 8) | m_data.internal[n]; } 46 | return v >> (8 - m_least_bits); 47 | } 48 | void uint(nbits num_bits, std::size_t v) 49 | { 50 | set_nums(num_bits); 51 | v <<= 8 - m_least_bits; 52 | for (std::size_t n = 0; n < size(); ++n) 53 | { m_data.internal[n] = uint8_t(v >> 8*(size() - n - 1)); } 54 | } 55 | 56 | void clear() { m_num_bytes = 0; m_least_bits = 0; m_data.external = nullptr; } 57 | 58 | void assign_bits(void const* b, nbits num_bits) 59 | { 60 | set_nums::max()>(num_bits); 61 | if (inplace()) { std::memcpy(&m_data.internal, b, m_num_bytes); } 62 | else { m_data.external = static_cast(b); } 63 | } 64 | 65 | private: 66 | bool inplace() const { return m_num_bytes <= sizeof(m_data.internal); } 67 | 68 | template 69 | void set_nums(nbits num_bits) 70 | { 71 | auto const num_bytes = bits_to_bytes(std::size_t(num_bits)); 72 | if (num_bytes > MAX) { throw invalid_value("number of bits", std::size_t(num_bits)); } 73 | 74 | m_num_bytes = static_cast(num_bytes); 75 | m_least_bits = calc_least_bits(std::size_t(num_bits)); 76 | } 77 | 78 | uint16_t m_num_bytes {0}; 79 | uint8_t m_least_bits {0}; //0 - not set, or 1..8 80 | 81 | union 82 | { 83 | uint8_t const* external {nullptr}; 84 | uint8_t internal[sizeof(uint64_t)]; 85 | } m_data; 86 | }; 87 | 88 | //fixed length bits 89 | template 90 | class bits_fixed; 91 | 92 | template requires (NBITS > 8*sizeof(uint64_t)) 93 | class bits_fixed 94 | { 95 | public: 96 | bool is_set() const { return nullptr != data(); } 97 | 98 | static constexpr std::size_t size() { return bits_to_bytes(NBITS); } 99 | static constexpr nbits num_of_bits() { return nbits{NBITS}; } 100 | static constexpr nbits least_bits() { return nbits{calc_least_bits(NBITS)}; } 101 | uint8_t const* data() const { return m_data; } 102 | 103 | void clear() { m_data = nullptr; } 104 | void assign_bits(void const* b, nbits) { m_data = static_cast(b); } 105 | 106 | private: 107 | uint8_t const* m_data {nullptr}; 108 | }; 109 | 110 | template requires (NBITS <= 8*sizeof(uint64_t)) 111 | class bits_fixed 112 | { 113 | public: 114 | bool is_set() const { return m_set; } 115 | 116 | static constexpr std::size_t size() { return bits_to_bytes(NBITS); } 117 | static constexpr nbits num_of_bits() { return nbits{NBITS}; } 118 | static constexpr nbits least_bits() { return nbits{calc_least_bits(NBITS)}; } 119 | uint8_t const* data() const { return is_set() ? m_data : nullptr; } 120 | 121 | void clear() { m_set = false; } 122 | 123 | std::size_t uint() const 124 | { 125 | std::size_t v = m_data[0]; 126 | for (std::size_t n = 1; n < size(); ++n) { v = (v << 8) | m_data[n]; } 127 | return v >> (8 - uint8_t(least_bits())); 128 | } 129 | void uint(std::size_t v) 130 | { 131 | v <<= 8 - uint8_t(least_bits()); 132 | for (std::size_t n = 0; n < size(); ++n) 133 | { m_data[n] = uint8_t(v >> 8*(size() - n - 1)); } 134 | m_set = true; 135 | } 136 | 137 | void assign_bits(void const* b, nbits) 138 | { 139 | std::memcpy(&m_data, b, size()); 140 | m_set = true; 141 | } 142 | 143 | private: 144 | bool m_set {false}; 145 | uint8_t m_data[size()]; 146 | }; 147 | 148 | template 149 | struct bit_string_impl : IE 150 | { 151 | using traits = TRAITS; 152 | using value_type = conditional_t, bits_variable>; 153 | using base_t = bit_string_impl; 154 | 155 | constexpr std::size_t size() const { return m_value.size(); } 156 | 157 | auto* data() const { return m_value.data(); } 158 | void clear() { m_value.clear(); } 159 | 160 | template 161 | void copy(base_t const& from, ARGS&&...) { m_value = from.m_value; } 162 | 163 | bool set(std::size_t nbits, void const* data) { return set_encoded(nbits, data); } 164 | //bool set(std::size_t nbits, std::size_t val) { return set_encoded(0, nullptr); } 165 | 166 | //NOTE: do not override! 167 | bool set_encoded(std::size_t nb, void const* data) 168 | { 169 | if (nb >= traits::min_bits && nb <= traits::max_bits) 170 | { 171 | m_value.assign_bits(data, nbits{nb}); 172 | return is_set(); 173 | } 174 | CODEC_TRACE("ERROR: bits=%zu !=[%zu..%zu]", nb, traits::min_bits, traits::max_bits); 175 | return false; 176 | } 177 | 178 | value_type const& get() const { return m_value; } 179 | bool is_set() const { return m_value.is_set(); } 180 | explicit operator bool() const { return is_set(); } 181 | 182 | private: 183 | value_type m_value; 184 | }; 185 | 186 | constexpr std::size_t MAX_BITS = 8*std::numeric_limits::max(); 187 | 188 | template 189 | struct bit_string : bit_string_impl> {}; 190 | 191 | template 192 | struct bit_string, EXT_TRAITS...> 193 | : bit_string_impl> {}; 194 | 195 | template 196 | struct bit_string, EXT_TRAITS...> 197 | : bit_string_impl> {}; 198 | 199 | template 200 | struct bit_string, max, EXT_TRAITS...> 201 | : bit_string_impl> {}; 202 | 203 | } //end: namespace med 204 | -------------------------------------------------------------------------------- /med/value.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | IE to represent generic integral value 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include "units.hpp" 15 | #include "ie_type.hpp" 16 | #include "name.hpp" 17 | #include "exception.hpp" 18 | #include "concepts.hpp" 19 | 20 | 21 | namespace med { 22 | 23 | //traits representing a fixed value of particular size (in bits/bytes/int) 24 | //which is predefined during encode and decode 25 | template 26 | struct fixed : value_traits 27 | { 28 | static constexpr typename value_traits::value_type value = VAL; 29 | }; 30 | 31 | //traits representing initialized value with a default 32 | template 33 | struct init : value_traits 34 | { 35 | static constexpr typename value_traits::value_type value = VAL; 36 | }; 37 | 38 | 39 | namespace detail { 40 | 41 | /** 42 | * plain numeric value 43 | */ 44 | template 45 | struct numeric_value : IE 46 | { 47 | using traits = TRAITS; 48 | using value_type = typename traits::value_type; 49 | using base_t = numeric_value; 50 | 51 | value_type get() const noexcept { return get_encoded(); } 52 | auto set(value_type v) noexcept { return set_encoded(v); } 53 | 54 | //NOTE: do not override! 55 | static constexpr bool is_defined = false; 56 | value_type get_encoded() const noexcept { return m_value; } 57 | void set_encoded(value_type v) noexcept { m_value = v; m_set = true; } 58 | void clear() noexcept { m_set = false; } 59 | bool is_set() const noexcept { return m_set; } 60 | explicit operator bool() const noexcept { return is_set(); } 61 | template 62 | void copy(base_t const& from, ARGS&&...)noexcept{ m_value = from.m_value; m_set = from.m_set; } 63 | 64 | #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 9) 65 | #pragma GCC diagnostic push 66 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 67 | #endif 68 | bool operator==(numeric_value const& rhs) const noexcept 69 | { 70 | auto const res = (is_set() == rhs.is_set()) 71 | && (!is_set() || get_encoded() == rhs.get_encoded()); 72 | #ifdef CODEC_TRACE_ENABLE 73 | if (!res) 74 | { 75 | if (is_set() != rhs.is_set()) CODEC_TRACE("??? is_set differs: %d != %d", is_set(), rhs.is_set()); 76 | else CODEC_TRACE("??? value differs: %zXh != %#zXh", size_t(get_encoded()), size_t(rhs.get_encoded())); 77 | } 78 | #endif //CODEC_TRACE_ENABLE 79 | return res; 80 | } 81 | #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 9) 82 | #pragma GCC diagnostic pop 83 | #endif 84 | 85 | private: 86 | bool m_set{false}; 87 | value_type m_value{}; 88 | }; 89 | 90 | /** 91 | * plain fixed integral value 92 | * gives error if decoded value doesn't match the fixed one 93 | */ 94 | template 95 | struct const_value : IE 96 | { 97 | using traits = TRAITS; 98 | using value_type = typename traits::value_type; 99 | using base_t = const_value; 100 | 101 | static constexpr void clear() { } 102 | static constexpr value_type get() { return get_encoded(); } 103 | 104 | 105 | //NOTE: do not override! 106 | static constexpr bool is_defined = true; 107 | explicit operator bool() const { return is_set(); } 108 | static constexpr value_type get_encoded() { return traits::value; } 109 | static constexpr bool set_encoded(value_type v) { return traits::value == v; } 110 | static constexpr bool is_set() { return true; } 111 | static constexpr bool match(value_type v) { return traits::value == v; } 112 | template 113 | static constexpr void copy(base_t const&, ARGS&&...){ } 114 | 115 | bool operator==(base_t const&) const noexcept { return true; } //equal to itself by definition 116 | }; 117 | 118 | /** 119 | * plain initialized/set field 120 | * decoded even if doesn't match initial value 121 | */ 122 | template 123 | struct init_value : IE 124 | { 125 | using traits = TRAITS; 126 | using value_type = typename traits::value_type; 127 | using base_t = init_value; 128 | 129 | constexpr void clear() { set_encoded(traits::value); } 130 | constexpr value_type get() const { return get_encoded(); } 131 | constexpr auto set(value_type v) { return set_encoded(v); } 132 | //NOTE: do not override! 133 | static constexpr bool is_defined = true; 134 | constexpr value_type get_encoded() const noexcept { return m_value; } 135 | constexpr void set_encoded(value_type v) { m_value = v; } 136 | bool is_default() const noexcept { return get_encoded() == traits::value; } 137 | static constexpr bool is_set() noexcept { return true; } 138 | explicit operator bool() const noexcept { return is_set(); } 139 | template 140 | void copy(base_t const& from, ARGS&&...) { m_value = from.m_value; } 141 | 142 | #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 9) 143 | #pragma GCC diagnostic push 144 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 145 | #endif 146 | bool operator==(base_t const& rhs) const noexcept 147 | { 148 | auto const res = get_encoded() == rhs.get_encoded(); 149 | #ifdef CODEC_TRACE_ENABLE 150 | if (!res) 151 | { 152 | CODEC_TRACE("??? value differs: %#zX != %#zX", size_t(get_encoded()), size_t(rhs.get_encoded())); 153 | } 154 | #endif //CODEC_TRACE_ENABLE 155 | return res; 156 | } 157 | #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ < 9) 158 | #pragma GCC diagnostic pop 159 | #endif 160 | 161 | private: 162 | value_type m_value {traits::value}; 163 | }; 164 | 165 | //meta-function to select proper implementation of numeric value 166 | template struct value_selector; 167 | 168 | template 169 | concept AValueDefined = std::is_same_v>; 170 | 171 | template 172 | struct value_selector> 173 | { 174 | using type = const_value>; 175 | }; 176 | 177 | template 178 | struct value_selector> 179 | { 180 | using type = init_value>; 181 | }; 182 | 183 | } //end: namespace detail 184 | 185 | template 186 | using as_writable_t = detail::numeric_value; 187 | 188 | /** 189 | * generic value - a facade for numeric_values above 190 | */ 191 | template 192 | struct value; 193 | 194 | template 195 | struct value : detail::value_selector::type {}; 196 | 197 | template 198 | struct value 199 | : detail::numeric_value> {}; 200 | 201 | template 202 | struct value, EXT_TRAITS...> 203 | : detail::numeric_value, EXT_TRAITS...>> {}; 204 | 205 | template 206 | struct value, EXT_TRAITS...> 207 | : detail::numeric_value, EXT_TRAITS...>> {}; 208 | 209 | } //namespace med 210 | -------------------------------------------------------------------------------- /med/container.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | base container class used in sequence and set 4 | 5 | @copyright Denis Priyomov 2016-2017 6 | Distributed under the MIT License 7 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "debug.hpp" 13 | #include "accessor.hpp" 14 | #include "length.hpp" 15 | #include "concepts.hpp" 16 | #include "sl/field_copy.hpp" 17 | #include "meta/typelist.hpp" 18 | #include "meta/foreach.hpp" 19 | 20 | namespace med { 21 | 22 | namespace sl { 23 | 24 | struct cont_clear 25 | { 26 | template 27 | static void apply(SEQ& s) { static_cast(s).clear(); } 28 | }; 29 | 30 | struct cont_copy 31 | { 32 | template 33 | static void apply(TO& to, FROM const& from, ARGS&&... args) 34 | { 35 | using field_t = get_field_type_t; 36 | auto const& from_field = from.m_ies.template as(); 37 | if (from_field.is_set()) 38 | { 39 | auto& to_field = to.m_ies.template as(); 40 | if constexpr (AMultiField) 41 | { 42 | to_field.clear(); 43 | for (auto const& rhs : from_field) 44 | { 45 | auto* p = to_field.push_back(std::forward(args)...); 46 | p->copy(rhs, std::forward(args)...); 47 | } 48 | } 49 | else 50 | { 51 | to_field.copy(from_field, std::forward(args)...); 52 | } 53 | } 54 | } 55 | }; 56 | 57 | template 58 | struct cont_len 59 | { 60 | using EXP_TAG = typename TYPE_CTX::explicit_tag_type; 61 | using EXP_LEN = typename TYPE_CTX::explicit_length_type; 62 | 63 | static constexpr std::size_t op(std::size_t r1, std::size_t r2) { return r1 + r2; } 64 | 65 | template 66 | static std::size_t apply(SEQ const& seq, ENCODER& encoder) 67 | { 68 | using mi = meta::produce_info_t; 69 | using ctx = type_context; 70 | if constexpr (AMultiField) //TODO: duplicated in encoder 71 | { 72 | IE const& ie = seq; 73 | std::size_t len = 0; 74 | for (auto& v : ie) { len += sl::ie_length(v, encoder); } 75 | CODEC_TRACE("cont_len[%s]*%zu len=%zu", name(), ie.count(), len); 76 | return len; 77 | } 78 | else 79 | { 80 | if constexpr (ASameAs, EXP_TAG, EXP_LEN>) 81 | { 82 | CODEC_TRACE("cont_len[%s] skip explicit", name()); 83 | return 0; 84 | } 85 | else if constexpr (AMandatory || AHasSetterType) 86 | { 87 | IE const& ie = seq; 88 | auto const len = sl::ie_length(ie, encoder); 89 | CODEC_TRACE("cont_len[%s] len=%zu", name(), len); 90 | return len; 91 | } 92 | else 93 | { 94 | IE const& ie = seq; 95 | auto const len = ie.is_set() ? sl::ie_length(ie, encoder) : 0; 96 | CODEC_TRACE("cont_len[%s] len=%zu", name(), len); 97 | return len; 98 | } 99 | } 100 | } 101 | 102 | template 103 | static constexpr std::size_t apply(SEQ const&, ENCODER&) { return 0; } 104 | }; 105 | 106 | struct cont_is 107 | { 108 | static constexpr bool op(bool r1, bool r2) { return r1 || r2; } 109 | 110 | template 111 | static constexpr bool apply(SEQ const& seq) 112 | { 113 | //optional or mandatory field w/ setter => can be set implicitly 114 | if constexpr (AOptional || AHasSetterType) 115 | { 116 | CODEC_TRACE("O/S[%s] is_set=%d", name(), static_cast(seq).is_set()); 117 | return static_cast(seq).is_set(); 118 | } 119 | else //mandatory field w/o setter => s.b. set explicitly 120 | { 121 | CODEC_TRACE("M[%s]%s is_set=%d", name(), (APredefinedValue ? "[init/const]":""), static_cast(seq).is_set()); 122 | if constexpr (APredefinedValue) //don't account predefined IEs like init/const 123 | { 124 | return false; 125 | } 126 | else 127 | { 128 | return static_cast(seq).is_set(); 129 | } 130 | } 131 | } 132 | 133 | template 134 | static constexpr bool apply(SEQ const&) { return false; } 135 | }; 136 | 137 | struct cont_eq 138 | { 139 | static constexpr bool op(bool r1, bool r2) { return r1 && r2; } 140 | 141 | template 142 | static constexpr bool apply(SEQ const& lhs, SEQ const& rhs) 143 | { 144 | return static_cast(lhs) == static_cast(rhs); 145 | } 146 | 147 | template 148 | static constexpr bool apply(SEQ const&, SEQ const&) { return true; } 149 | }; 150 | 151 | //----------------------------------------------------------------------- 152 | template 153 | constexpr std::size_t field_arity() 154 | { 155 | if constexpr (AMultiField) 156 | { 157 | return IE::max; 158 | } 159 | else 160 | { 161 | return 1; 162 | } 163 | } 164 | 165 | } //end: namespace sl 166 | 167 | template 168 | class container : public IE 169 | { 170 | public: 171 | using container_t = container; 172 | using ies_types = meta::typelist; 173 | 174 | template 175 | static constexpr bool has() { return not std::is_void_v>>; } 176 | 177 | template 178 | decltype(auto) ref() 179 | { 180 | static_assert(!std::is_const_v, "ATTEMPT TO COPY FROM CONST REF"); 181 | auto& ie = m_ies.template as(); 182 | using IE = std::remove_cvref_t; 183 | if constexpr (AMultiField) 184 | { 185 | return static_cast(ie); 186 | } 187 | else 188 | { 189 | return static_cast(ie); 190 | } 191 | } 192 | 193 | template 194 | decltype(auto) get() const 195 | { 196 | auto& ie = m_ies.template as(); 197 | return get_field(ie); 198 | } 199 | 200 | template 201 | std::size_t count() const { return field_count(m_ies.template as()); } 202 | 203 | template 204 | static constexpr std::size_t arity() { return sl::field_arity>>(); } 205 | 206 | template 207 | void clear() { m_ies.template as().clear(); } 208 | void clear() { meta::foreach(sl::cont_clear{}, this->m_ies); } 209 | bool is_set() const { return meta::fold(sl::cont_is{}, this->m_ies); } 210 | template 211 | std::size_t calc_length(auto& enc) const { return meta::fold(sl::cont_len{}, this->m_ies, enc); } 212 | template > 213 | std::size_t calc_length(auto& enc) const { return calc_length(enc); } 214 | 215 | template 216 | void copy(FROM const& from, ARGS&&... args) 217 | { meta::foreach(sl::cont_copy{}, *this, from, std::forward(args)...); } 218 | 219 | template 220 | void copy_to(TO& to, ARGS&&... args) const 221 | { meta::foreach(sl::cont_copy{}, to, *this, std::forward(args)...); } 222 | 223 | bool operator==(container const& rhs) const { return meta::fold(sl::cont_eq{}, this->m_ies, rhs.m_ies); } 224 | 225 | protected: 226 | friend struct sl::cont_copy; 227 | 228 | struct ies_t : IES... 229 | { 230 | template 231 | decltype(auto) as() const 232 | { 233 | using IE = meta::find_t>; 234 | static_assert(!std::is_void(), "NO SUCH FIELD"); 235 | return static_cast(*this); 236 | } 237 | 238 | template 239 | decltype(auto) as() 240 | { 241 | using IE = meta::find_t>; 242 | static_assert(!std::is_void(), "NO SUCH FIELD"); 243 | return static_cast(*this); 244 | } 245 | }; 246 | 247 | ies_t m_ies; 248 | }; 249 | 250 | } //end: namespace med 251 | -------------------------------------------------------------------------------- /med/meta/typelist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | @file 4 | tiny metaprogramming type list 5 | 6 | @copyright Denis Priyomov 2019 7 | Distributed under the MIT License 8 | (See accompanying file LICENSE or visit https://github.com/cppden/med) 9 | */ 10 | #include 11 | #include 12 | 13 | namespace med { 14 | 15 | namespace detail { 16 | 17 | template struct if_t; 18 | template<> struct if_t 19 | { 20 | template using type = THEN; 21 | }; 22 | template<> struct if_t 23 | { 24 | template using type = ELSE; 25 | }; 26 | 27 | } //end: namespace detail 28 | 29 | template 30 | using conditional_t = typename detail::if_t::template type; 31 | 32 | namespace meta { 33 | 34 | template struct typelist{}; 35 | 36 | /* --- list size --- */ 37 | template struct list_size 38 | { 39 | static_assert(std::is_same_v>, "not a list"); 40 | }; 41 | template