1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_tokenizer/encode_args.h"
16
17 #include <algorithm>
18 #include <cstring>
19
20 #include "pw_preprocessor/compiler.h"
21 #include "pw_varint/varint.h"
22
23 static_assert((PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 4) ||
24 (PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 8),
25 "PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES must be 4 or 8");
26
27 namespace pw {
28 namespace tokenizer {
29 namespace {
30
31 // Declare the types as an enum for convenience.
32 enum class ArgType : uint8_t {
33 kInt = PW_TOKENIZER_ARG_TYPE_INT,
34 kInt64 = PW_TOKENIZER_ARG_TYPE_INT64,
35 kDouble = PW_TOKENIZER_ARG_TYPE_DOUBLE,
36 kString = PW_TOKENIZER_ARG_TYPE_STRING,
37 };
38
EncodeInt(int value,const span<std::byte> & output)39 size_t EncodeInt(int value, const span<std::byte>& output) {
40 // Use the 64-bit function to avoid instantiating both 32-bit and 64-bit.
41 return pw_tokenizer_EncodeInt64(value, output.data(), output.size());
42 }
43
EncodeInt64(int64_t value,const span<std::byte> & output)44 size_t EncodeInt64(int64_t value, const span<std::byte>& output) {
45 return pw_tokenizer_EncodeInt64(value, output.data(), output.size());
46 }
47
EncodeFloat(float value,const span<std::byte> & output)48 size_t EncodeFloat(float value, const span<std::byte>& output) {
49 if (output.size() < sizeof(value)) {
50 return 0;
51 }
52 std::memcpy(output.data(), &value, sizeof(value));
53 return sizeof(value);
54 }
55
EncodeString(const char * string,const span<std::byte> & output)56 size_t EncodeString(const char* string, const span<std::byte>& output) {
57 // The top bit of the status byte indicates if the string was truncated.
58 static constexpr size_t kMaxStringLength = 0x7Fu;
59
60 if (output.empty()) { // At least one byte is needed for the status/size.
61 return 0;
62 }
63
64 if (string == nullptr) {
65 string = "NULL";
66 }
67
68 // Subtract 1 to save room for the status byte.
69 const size_t max_bytes =
70 std::min(static_cast<size_t>(output.size()), kMaxStringLength) - 1;
71
72 // Scan the string to find out how many bytes to copy.
73 size_t bytes_to_copy = 0;
74 std::byte overflow_bit = std::byte(0);
75
76 while (string[bytes_to_copy] != '\0') {
77 if (bytes_to_copy == max_bytes) {
78 overflow_bit = std::byte('\x80');
79 break;
80 }
81 bytes_to_copy += 1;
82 }
83
84 output[0] = static_cast<std::byte>(bytes_to_copy) | overflow_bit;
85 std::memcpy(output.data() + 1, string, bytes_to_copy);
86
87 return bytes_to_copy + 1; // include the status byte in the total
88 }
89
90 } // namespace
91
EncodeArgs(pw_tokenizer_ArgTypes types,va_list args,span<std::byte> output)92 size_t EncodeArgs(pw_tokenizer_ArgTypes types,
93 va_list args,
94 span<std::byte> output) {
95 size_t arg_count = types & PW_TOKENIZER_TYPE_COUNT_MASK;
96 types >>= PW_TOKENIZER_TYPE_COUNT_SIZE_BITS;
97
98 size_t encoded_bytes = 0;
99 while (arg_count != 0u) {
100 // How many bytes were encoded; 0 indicates that there wasn't enough space.
101 size_t argument_bytes = 0;
102
103 switch (static_cast<ArgType>(types & 0b11u)) {
104 case ArgType::kInt:
105 argument_bytes = EncodeInt(va_arg(args, int), output);
106 break;
107 case ArgType::kInt64:
108 argument_bytes = EncodeInt64(va_arg(args, int64_t), output);
109 break;
110 case ArgType::kDouble:
111 argument_bytes =
112 EncodeFloat(static_cast<float>(va_arg(args, double)), output);
113 break;
114 case ArgType::kString:
115 argument_bytes = EncodeString(va_arg(args, const char*), output);
116 break;
117 }
118
119 // If zero bytes were encoded, the encoding buffer is full.
120 if (argument_bytes == 0u) {
121 break;
122 }
123
124 output = output.subspan(argument_bytes);
125 encoded_bytes += argument_bytes;
126
127 arg_count -= 1;
128 types >>= 2; // each argument type is encoded in two bits
129 }
130
131 return encoded_bytes;
132 }
133
pw_tokenizer_EncodeArgs(pw_tokenizer_ArgTypes types,va_list args,void * output_buffer,size_t output_buffer_size)134 extern "C" size_t pw_tokenizer_EncodeArgs(pw_tokenizer_ArgTypes types,
135 va_list args,
136 void* output_buffer,
137 size_t output_buffer_size) {
138 return EncodeArgs(types,
139 args,
140 span<std::byte>(static_cast<std::byte*>(output_buffer),
141 output_buffer_size));
142 }
143
144 } // namespace tokenizer
145 } // namespace pw
146