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 <cinttypes>
16 #include <cstdint>
17 #include <cstring>
18 
19 #include "gtest/gtest.h"
20 #include "pw_tokenizer/tokenize_to_global_handler.h"
21 #include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
22 #include "pw_tokenizer_private/tokenize_test.h"
23 
24 namespace pw::tokenizer {
25 namespace {
26 
27 // Constructs an array with the hashed string followed by the provided bytes.
28 template <uint8_t... data, size_t kSize>
ExpectedData(const char (& format)[kSize],uint32_t token_mask=std::numeric_limits<uint32_t>::max ())29 constexpr auto ExpectedData(
30     const char (&format)[kSize],
31     uint32_t token_mask = std::numeric_limits<uint32_t>::max()) {
32   const uint32_t value = Hash(format) & token_mask;
33   return std::array<uint8_t, sizeof(uint32_t) + sizeof...(data)>{
34       static_cast<uint8_t>(value & 0xff),
35       static_cast<uint8_t>(value >> 8 & 0xff),
36       static_cast<uint8_t>(value >> 16 & 0xff),
37       static_cast<uint8_t>(value >> 24 & 0xff),
38       data...};
39 }
40 
41 // Test fixture for both global handler functions. Both need a global message
42 // buffer. To keep the message buffers separate, template this on the derived
43 // class type.
44 template <typename Impl>
45 class GlobalMessage : public ::testing::Test {
46  public:
SetMessage(const uint8_t * message,size_t size)47   static void SetMessage(const uint8_t* message, size_t size) {
48     ASSERT_LE(size, sizeof(message_));
49     std::memcpy(message_, message, size);
50     message_size_bytes_ = size;
51   }
52 
53  protected:
GlobalMessage()54   GlobalMessage() {
55     std::memset(message_, 0, sizeof(message_));
56     message_size_bytes_ = 0;
57   }
58 
59   static uint8_t message_[256];
60   static size_t message_size_bytes_;
61 };
62 
63 template <typename Impl>
64 uint8_t GlobalMessage<Impl>::message_[256] = {};
65 template <typename Impl>
66 size_t GlobalMessage<Impl>::message_size_bytes_ = 0;
67 
68 class TokenizeToGlobalHandler : public GlobalMessage<TokenizeToGlobalHandler> {
69 };
70 
TEST_F(TokenizeToGlobalHandler,Variety)71 TEST_F(TokenizeToGlobalHandler, Variety) {
72   PW_TOKENIZE_TO_GLOBAL_HANDLER("%x%lld%1.2f%s", 0, 0ll, -0.0, "");
73   const auto expected =
74       ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
75   ASSERT_EQ(expected.size(), message_size_bytes_);
76   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
77 }
78 
TEST_F(TokenizeToGlobalHandler,Strings)79 TEST_F(TokenizeToGlobalHandler, Strings) {
80   PW_TOKENIZE_TO_GLOBAL_HANDLER("The answer is: %s", "5432!");
81   constexpr std::array<uint8_t, 10> expected =
82       ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
83   ASSERT_EQ(expected.size(), message_size_bytes_);
84   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
85 }
86 
TEST_F(TokenizeToGlobalHandler,Domain_Strings)87 TEST_F(TokenizeToGlobalHandler, Domain_Strings) {
88   PW_TOKENIZE_TO_GLOBAL_HANDLER_DOMAIN(
89       "TEST_DOMAIN", "The answer is: %s", "5432!");
90   constexpr std::array<uint8_t, 10> expected =
91       ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
92   ASSERT_EQ(expected.size(), message_size_bytes_);
93   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
94 }
95 
TEST_F(TokenizeToGlobalHandler,Mask)96 TEST_F(TokenizeToGlobalHandler, Mask) {
97   PW_TOKENIZE_TO_GLOBAL_HANDLER_MASK(
98       "TEST_DOMAIN", 0x00FFF000, "The answer is: %s", "5432!");
99   constexpr std::array<uint8_t, 10> expected =
100       ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s", 0x00FFF000);
101   ASSERT_EQ(expected.size(), message_size_bytes_);
102   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
103 }
104 
TEST_F(TokenizeToGlobalHandler,C_SequentialZigZag)105 TEST_F(TokenizeToGlobalHandler, C_SequentialZigZag) {
106   pw_tokenizer_ToGlobalHandlerTest_SequentialZigZag();
107 
108   constexpr std::array<uint8_t, 18> expected =
109       ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
110           TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
111   ASSERT_EQ(expected.size(), message_size_bytes_);
112   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
113 }
114 
pw_tokenizer_HandleEncodedMessage(const uint8_t * encoded_message,size_t size_bytes)115 extern "C" void pw_tokenizer_HandleEncodedMessage(
116     const uint8_t* encoded_message, size_t size_bytes) {
117   TokenizeToGlobalHandler::SetMessage(encoded_message, size_bytes);
118 }
119 
120 class TokenizeToGlobalHandlerWithPayload
121     : public GlobalMessage<TokenizeToGlobalHandlerWithPayload> {
122  public:
SetPayload(pw_tokenizer_Payload payload)123   static void SetPayload(pw_tokenizer_Payload payload) {
124     payload_ = static_cast<intptr_t>(payload);
125   }
126 
127  protected:
TokenizeToGlobalHandlerWithPayload()128   TokenizeToGlobalHandlerWithPayload() { payload_ = {}; }
129 
130   static intptr_t payload_;
131 };
132 
133 intptr_t TokenizeToGlobalHandlerWithPayload::payload_;
134 
TEST_F(TokenizeToGlobalHandlerWithPayload,Variety)135 TEST_F(TokenizeToGlobalHandlerWithPayload, Variety) {
136   ASSERT_NE(payload_, 123);
137 
138   const auto expected =
139       ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
140 
141   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
142       static_cast<pw_tokenizer_Payload>(123),
143       "%x%lld%1.2f%s",
144       0,
145       0ll,
146       -0.0,
147       "");
148   ASSERT_EQ(expected.size(), message_size_bytes_);
149   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
150   EXPECT_EQ(payload_, 123);
151 
152   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
153       static_cast<pw_tokenizer_Payload>(-543),
154       "%x%lld%1.2f%s",
155       0,
156       0ll,
157       -0.0,
158       "");
159   ASSERT_EQ(expected.size(), message_size_bytes_);
160   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
161   EXPECT_EQ(payload_, -543);
162 }
163 
164 constexpr std::array<uint8_t, 10> kExpected =
165     ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
166 
TEST_F(TokenizeToGlobalHandlerWithPayload,Strings_ZeroPayload)167 TEST_F(TokenizeToGlobalHandlerWithPayload, Strings_ZeroPayload) {
168   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD({}, "The answer is: %s", "5432!");
169 
170   ASSERT_EQ(kExpected.size(), message_size_bytes_);
171   EXPECT_EQ(std::memcmp(kExpected.data(), message_, kExpected.size()), 0);
172   EXPECT_EQ(payload_, 0);
173 }
174 
TEST_F(TokenizeToGlobalHandlerWithPayload,Strings_NonZeroPayload)175 TEST_F(TokenizeToGlobalHandlerWithPayload, Strings_NonZeroPayload) {
176   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
177       static_cast<pw_tokenizer_Payload>(5432), "The answer is: %s", "5432!");
178 
179   ASSERT_EQ(kExpected.size(), message_size_bytes_);
180   EXPECT_EQ(std::memcmp(kExpected.data(), message_, kExpected.size()), 0);
181   EXPECT_EQ(payload_, 5432);
182 }
183 
TEST_F(TokenizeToGlobalHandlerWithPayload,Domain_Strings)184 TEST_F(TokenizeToGlobalHandlerWithPayload, Domain_Strings) {
185   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD_DOMAIN(
186       "TEST_DOMAIN",
187       static_cast<pw_tokenizer_Payload>(5432),
188       "The answer is: %s",
189       "5432!");
190   ASSERT_EQ(kExpected.size(), message_size_bytes_);
191   EXPECT_EQ(std::memcmp(kExpected.data(), message_, kExpected.size()), 0);
192   EXPECT_EQ(payload_, 5432);
193 }
194 
TEST_F(TokenizeToGlobalHandlerWithPayload,Mask)195 TEST_F(TokenizeToGlobalHandlerWithPayload, Mask) {
196   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD_MASK(
197       "TEST_DOMAIN",
198       0x12345678,
199       static_cast<pw_tokenizer_Payload>(5432),
200       "The answer is: %s",
201       "5432!");
202   constexpr std::array<uint8_t, 10> expected =
203       ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s", 0x12345678);
204   ASSERT_EQ(expected.size(), message_size_bytes_);
205   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
206   EXPECT_EQ(payload_, 5432);
207 }
208 
209 struct Foo {
210   unsigned char a;
211   bool b;
212 };
213 
TEST_F(TokenizeToGlobalHandlerWithPayload,PointerToStack)214 TEST_F(TokenizeToGlobalHandlerWithPayload, PointerToStack) {
215   Foo foo{254u, true};
216 
217   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
218       reinterpret_cast<pw_tokenizer_Payload>(&foo), "Boring!");
219 
220   constexpr auto expected = ExpectedData("Boring!");
221   static_assert(expected.size() == 4);
222   ASSERT_EQ(expected.size(), message_size_bytes_);
223   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
224 
225   Foo* payload_foo = reinterpret_cast<Foo*>(payload_);
226   ASSERT_EQ(&foo, payload_foo);
227   EXPECT_EQ(payload_foo->a, 254u);
228   EXPECT_TRUE(payload_foo->b);
229 }
230 
TEST_F(TokenizeToGlobalHandlerWithPayload,C_SequentialZigZag)231 TEST_F(TokenizeToGlobalHandlerWithPayload, C_SequentialZigZag) {
232   pw_tokenizer_ToGlobalHandlerWithPayloadTest_SequentialZigZag();
233 
234   constexpr std::array<uint8_t, 18> expected =
235       ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
236           TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
237   ASSERT_EQ(expected.size(), message_size_bytes_);
238   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
239   EXPECT_EQ(payload_, 600613);
240 }
241 
pw_tokenizer_HandleEncodedMessageWithPayload(pw_tokenizer_Payload payload,const uint8_t * encoded_message,size_t size_bytes)242 extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload(
243     pw_tokenizer_Payload payload,
244     const uint8_t* encoded_message,
245     size_t size_bytes) {
246   TokenizeToGlobalHandlerWithPayload::SetMessage(encoded_message, size_bytes);
247   TokenizeToGlobalHandlerWithPayload::SetPayload(payload);
248 }
249 
250 // Hijack an internal macro to capture the tokenizer domain.
251 #undef _PW_TOKENIZER_RECORD_ORIGINAL_STRING
252 #define _PW_TOKENIZER_RECORD_ORIGINAL_STRING(token, domain, string) \
253   tokenizer_domain = domain;                                        \
254   string_literal = string
255 
TEST_F(TokenizeToGlobalHandler,Domain_Default)256 TEST_F(TokenizeToGlobalHandler, Domain_Default) {
257   const char* tokenizer_domain = nullptr;
258   const char* string_literal = nullptr;
259 
260   PW_TOKENIZE_TO_GLOBAL_HANDLER("404");
261 
262   EXPECT_STREQ(tokenizer_domain, PW_TOKENIZER_DEFAULT_DOMAIN);
263   EXPECT_STREQ(string_literal, "404");
264 }
265 
TEST_F(TokenizeToGlobalHandler,Domain_Specified)266 TEST_F(TokenizeToGlobalHandler, Domain_Specified) {
267   const char* tokenizer_domain = nullptr;
268   const char* string_literal = nullptr;
269 
270   PW_TOKENIZE_TO_GLOBAL_HANDLER_DOMAIN("www.google.com", "404");
271 
272   EXPECT_STREQ(tokenizer_domain, "www.google.com");
273   EXPECT_STREQ(string_literal, "404");
274 }
275 
TEST_F(TokenizeToGlobalHandlerWithPayload,Domain_Default)276 TEST_F(TokenizeToGlobalHandlerWithPayload, Domain_Default) {
277   const char* tokenizer_domain = nullptr;
278   const char* string_literal = nullptr;
279 
280   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
281       static_cast<pw_tokenizer_Payload>(123), "Wow%s", "???");
282 
283   EXPECT_STREQ(tokenizer_domain, PW_TOKENIZER_DEFAULT_DOMAIN);
284   EXPECT_STREQ(string_literal, "Wow%s");
285 }
286 
TEST_F(TokenizeToGlobalHandlerWithPayload,Domain_Specified)287 TEST_F(TokenizeToGlobalHandlerWithPayload, Domain_Specified) {
288   const char* tokenizer_domain = nullptr;
289   const char* string_literal = nullptr;
290 
291   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD_DOMAIN(
292       "THEDOMAIN", static_cast<pw_tokenizer_Payload>(123), "1234567890");
293 
294   EXPECT_STREQ(tokenizer_domain, "THEDOMAIN");
295   EXPECT_STREQ(string_literal, "1234567890");
296 }
297 
298 }  // namespace
299 }  // namespace pw::tokenizer
300