Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "protozero/protozero_message.h" |
| 18 | |
| 19 | #include <limits> |
| 20 | #include <memory> |
| 21 | #include <utility> |
| 22 | #include <vector> |
| 23 | |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 24 | #include "gtest/gtest.h" |
Oystein Eftevaag | dd727e4 | 2017-12-05 08:49:55 -0800 | [diff] [blame^] | 25 | #include "perfetto_base/logging.h" |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 26 | #include "protozero/src/test/fake_scattered_buffer.h" |
| 27 | |
| 28 | namespace protozero { |
| 29 | |
| 30 | namespace { |
| 31 | |
| 32 | const size_t kChunkSize = 16; |
| 33 | const uint8_t kTestBytes[] = {0, 0, 0, 0, 0x42, 1, 0x42, 0xff, 0x42, 0}; |
| 34 | const char kStartWatermark[] = {'a', 'b', 'c', 'd', '1', '2', '3', '\0'}; |
| 35 | const char kEndWatermark[] = {'9', '8', '7', '6', 'z', 'w', 'y', '\0'}; |
| 36 | |
| 37 | class FakeRootMessage : public ProtoZeroMessage {}; |
| 38 | class FakeChildMessage : public ProtoZeroMessage {}; |
| 39 | |
| 40 | uint32_t SimpleHash(const std::string& str) { |
| 41 | uint32_t hash = 5381; |
| 42 | for (char c : str) |
| 43 | hash = 33 * hash + static_cast<uint32_t>(c); |
| 44 | return hash; |
| 45 | } |
| 46 | |
| 47 | class ProtoZeroMessageTest : public ::testing::Test { |
| 48 | public: |
| 49 | void SetUp() override { |
| 50 | buffer_.reset(new FakeScatteredBuffer(kChunkSize)); |
| 51 | stream_writer_.reset(new ScatteredStreamWriter(buffer_.get())); |
| 52 | readback_pos_ = 0; |
| 53 | } |
| 54 | |
| 55 | void TearDown() override { |
| 56 | // Check that none of the messages created by the text fixtures below did |
| 57 | // under/overflow their heap boundaries. |
| 58 | for (std::unique_ptr<uint8_t[]>& mem : messages_) { |
| 59 | EXPECT_STREQ(kStartWatermark, reinterpret_cast<char*>(mem.get())); |
| 60 | EXPECT_STREQ(kEndWatermark, |
| 61 | reinterpret_cast<char*>(mem.get() + sizeof(kStartWatermark) + |
| 62 | sizeof(ProtoZeroMessage))); |
| 63 | mem.reset(); |
| 64 | } |
| 65 | messages_.clear(); |
| 66 | stream_writer_.reset(); |
| 67 | buffer_.reset(); |
| 68 | } |
| 69 | |
| 70 | FakeRootMessage* NewMessage() { |
| 71 | std::unique_ptr<uint8_t[]> mem( |
| 72 | new uint8_t[sizeof(kStartWatermark) + sizeof(FakeRootMessage) + |
| 73 | sizeof(kEndWatermark)]); |
| 74 | uint8_t* msg_start = mem.get() + sizeof(kStartWatermark); |
| 75 | memcpy(mem.get(), kStartWatermark, sizeof(kStartWatermark)); |
| 76 | memset(msg_start, 0, sizeof(FakeRootMessage)); |
| 77 | memcpy(msg_start + sizeof(FakeRootMessage), kEndWatermark, |
| 78 | sizeof(kEndWatermark)); |
| 79 | messages_.push_back(std::move(mem)); |
| 80 | FakeRootMessage* msg = reinterpret_cast<FakeRootMessage*>(msg_start); |
| 81 | msg->Reset(stream_writer_.get()); |
| 82 | return msg; |
| 83 | } |
| 84 | |
| 85 | size_t GetNumSerializedBytes() { |
| 86 | if (buffer_->chunks().empty()) |
| 87 | return 0; |
| 88 | return buffer_->chunks().size() * kChunkSize - |
| 89 | stream_writer_->bytes_available(); |
| 90 | } |
| 91 | |
| 92 | std::string GetNextSerializedBytes(size_t num_bytes) { |
| 93 | size_t old_readback_pos = readback_pos_; |
| 94 | readback_pos_ += num_bytes; |
| 95 | return buffer_->GetBytesAsString(old_readback_pos, num_bytes); |
| 96 | } |
| 97 | |
| 98 | static void BuildNestedMessages(ProtoZeroMessage* msg, uint32_t depth = 0) { |
| 99 | for (uint32_t i = 1; i <= 128; ++i) |
| 100 | msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes)); |
| 101 | |
| 102 | if (depth < ProtoZeroMessage::kMaxNestingDepth) { |
| 103 | auto* nested_msg = |
| 104 | msg->BeginNestedMessage<FakeChildMessage>(1 + depth * 10); |
| 105 | BuildNestedMessages(nested_msg, depth + 1); |
| 106 | } |
| 107 | |
| 108 | for (uint32_t i = 129; i <= 256; ++i) |
| 109 | msg->AppendVarInt(i, 42); |
| 110 | |
| 111 | if ((depth & 2) == 0) |
| 112 | msg->Finalize(); |
| 113 | } |
| 114 | |
| 115 | private: |
| 116 | std::unique_ptr<FakeScatteredBuffer> buffer_; |
| 117 | std::unique_ptr<ScatteredStreamWriter> stream_writer_; |
| 118 | std::vector<std::unique_ptr<uint8_t[]>> messages_; |
| 119 | size_t readback_pos_; |
| 120 | }; |
| 121 | |
| 122 | TEST_F(ProtoZeroMessageTest, BasicTypesNoNesting) { |
| 123 | ProtoZeroMessage* msg = NewMessage(); |
| 124 | msg->AppendVarInt(1 /* field_id */, 0); |
| 125 | msg->AppendVarInt(2 /* field_id */, std::numeric_limits<uint32_t>::max()); |
| 126 | msg->AppendVarInt(3 /* field_id */, 42); |
| 127 | msg->AppendVarInt(4 /* field_id */, std::numeric_limits<uint64_t>::max()); |
| 128 | msg->AppendFixed(5 /* field_id */, 3.1415f /* float */); |
| 129 | msg->AppendFixed(6 /* field_id */, 3.14159265358979323846 /* double */); |
| 130 | msg->AppendBytes(7 /* field_id */, kTestBytes, sizeof(kTestBytes)); |
| 131 | |
| 132 | // Field ids > 16 are expected to be varint encoded (preamble > 1 byte) |
| 133 | msg->AppendString(257 /* field_id */, "0123456789abcdefABCDEF"); |
| 134 | msg->AppendSignedVarInt(3 /* field_id */, -21); |
| 135 | |
| 136 | EXPECT_EQ(74u, msg->Finalize()); |
| 137 | EXPECT_EQ(74u, GetNumSerializedBytes()); |
| 138 | |
| 139 | // These lines match the serialization of the Append* calls above. |
| 140 | ASSERT_EQ("0800", GetNextSerializedBytes(2)); |
| 141 | ASSERT_EQ("10FFFFFFFF0F", GetNextSerializedBytes(6)); |
| 142 | ASSERT_EQ("182A", GetNextSerializedBytes(2)); |
| 143 | ASSERT_EQ("20FFFFFFFFFFFFFFFFFF01", GetNextSerializedBytes(11)); |
| 144 | ASSERT_EQ("2D560E4940", GetNextSerializedBytes(5)); |
| 145 | ASSERT_EQ("31182D4454FB210940", GetNextSerializedBytes(9)); |
| 146 | ASSERT_EQ("3A0A00000000420142FF4200", GetNextSerializedBytes(12)); |
| 147 | ASSERT_EQ("8A101630313233343536373839616263646566414243444546", |
| 148 | GetNextSerializedBytes(25)); |
| 149 | ASSERT_EQ("1829", GetNextSerializedBytes(2)); |
| 150 | } |
| 151 | |
| 152 | TEST_F(ProtoZeroMessageTest, NestedMessagesSimple) { |
| 153 | ProtoZeroMessage* root_msg = NewMessage(); |
| 154 | root_msg->AppendVarInt(1 /* field_id */, 1); |
| 155 | |
| 156 | FakeChildMessage* nested_msg = |
| 157 | root_msg->BeginNestedMessage<FakeChildMessage>(128 /* field_id */); |
| 158 | ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(nested_msg) % sizeof(void*)); |
| 159 | nested_msg->AppendVarInt(2 /* field_id */, 2); |
| 160 | |
| 161 | nested_msg = |
| 162 | root_msg->BeginNestedMessage<FakeChildMessage>(129 /* field_id */); |
| 163 | nested_msg->AppendVarInt(4 /* field_id */, 2); |
| 164 | |
| 165 | root_msg->AppendVarInt(5 /* field_id */, 3); |
| 166 | |
| 167 | // The expected size of the root message is supposed to be 20 bytes: |
| 168 | // 2 bytes for the varint field (id: 1) (1 for preamble and one for payload) |
| 169 | // 6 bytes for the preamble of the 1st nested message (2 for id, 4 for size) |
| 170 | // 2 bytes for the varint field (id: 2) of the 1st nested message |
| 171 | // 6 bytes for the premable of the 2nd nested message |
| 172 | // 2 bytes for the varint field (id: 4) of the 2nd nested message. |
| 173 | // 2 bytes for the last varint (id : 5) field of the root message. |
| 174 | // Test also that finalization is idempontent and Finalize() can be safely |
| 175 | // called more than once without side effects. |
| 176 | for (int i = 0; i < 3; ++i) { |
| 177 | EXPECT_EQ(20u, root_msg->Finalize()); |
| 178 | EXPECT_EQ(20u, GetNumSerializedBytes()); |
| 179 | } |
| 180 | |
| 181 | ASSERT_EQ("0801", GetNextSerializedBytes(2)); |
| 182 | |
| 183 | ASSERT_EQ("820882808000", GetNextSerializedBytes(6)); |
| 184 | ASSERT_EQ("1002", GetNextSerializedBytes(2)); |
| 185 | |
| 186 | ASSERT_EQ("8A0882808000", GetNextSerializedBytes(6)); |
| 187 | ASSERT_EQ("2002", GetNextSerializedBytes(2)); |
| 188 | |
| 189 | ASSERT_EQ("2803", GetNextSerializedBytes(2)); |
| 190 | } |
| 191 | |
| 192 | // Checks that the size field of root and nested messages is properly written |
| 193 | // on finalization. |
| 194 | TEST_F(ProtoZeroMessageTest, BackfillSizeOnFinalization) { |
| 195 | ProtoZeroMessage* root_msg = NewMessage(); |
| 196 | uint8_t root_msg_size[proto_utils::kMessageLengthFieldSize] = {}; |
| 197 | root_msg->set_size_field( |
| 198 | {&root_msg_size[0], |
| 199 | &root_msg_size[proto_utils::kMessageLengthFieldSize]}); |
| 200 | root_msg->AppendVarInt(1, 0x42); |
| 201 | |
| 202 | FakeChildMessage* nested_msg_1 = |
| 203 | root_msg->BeginNestedMessage<FakeChildMessage>(2); |
| 204 | nested_msg_1->AppendVarInt(3, 0x43); |
| 205 | |
| 206 | FakeChildMessage* nested_msg_2 = |
| 207 | nested_msg_1->BeginNestedMessage<FakeChildMessage>(4); |
| 208 | uint8_t buf200[200]; |
| 209 | memset(buf200, 0x42, sizeof(buf200)); |
| 210 | nested_msg_2->AppendBytes(5, buf200, sizeof(buf200)); |
| 211 | |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 212 | // The value returned by Finalize() should be == the full size of |root_msg|. |
| 213 | EXPECT_EQ(217u, root_msg->Finalize()); |
| 214 | EXPECT_EQ(217u, GetNumSerializedBytes()); |
| 215 | |
| 216 | // However the size written in the size field should take into account the |
| 217 | // inc_size_already_written() call and be equal to 118 - 6 = 112, encoded |
| 218 | // in a rendundant varint encoding of kMessageLengthFieldSize bytes. |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 219 | EXPECT_STREQ("\xD9\x81\x80\x00", reinterpret_cast<char*>(root_msg_size)); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 220 | |
| 221 | // Skip 2 bytes for the 0x42 varint + 1 byte for the |nested_msg_1| preamble. |
| 222 | GetNextSerializedBytes(3); |
| 223 | |
| 224 | // Check that the size of |nested_msg_1| was backfilled. Its size is: |
| 225 | // 203 bytes for |nest_mesg_2| (see below) + 5 bytes for its preamble + |
| 226 | // 2 bytes for the 0x43 varint = 210 bytes. |
| 227 | EXPECT_EQ("D2818000", GetNextSerializedBytes(4)); |
| 228 | |
| 229 | // Skip 2 bytes for the 0x43 varint + 1 byte for the |nested_msg_2| preamble. |
| 230 | GetNextSerializedBytes(3); |
| 231 | |
| 232 | // Check that the size of |nested_msg_2| was backfilled. Its size is: |
| 233 | // 200 bytes (for |buf200|) + 3 bytes for its preamble = 203 bytes. |
| 234 | EXPECT_EQ("CB818000", GetNextSerializedBytes(4)); |
| 235 | } |
| 236 | |
| 237 | TEST_F(ProtoZeroMessageTest, StressTest) { |
| 238 | std::vector<ProtoZeroMessage*> nested_msgs; |
| 239 | |
| 240 | ProtoZeroMessage* root_msg = NewMessage(); |
| 241 | BuildNestedMessages(root_msg); |
| 242 | root_msg->Finalize(); |
| 243 | |
| 244 | // The main point of this test is to stress the code paths and test for |
| 245 | // unexpected crashes of the production code. The actual serialization is |
| 246 | // already covered in the other text fixtures. Keeping just a final smoke test |
| 247 | // here on the full buffer hash. |
| 248 | std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes()); |
| 249 | size_t buf_hash = SimpleHash(full_buf); |
| 250 | EXPECT_EQ(0xfd19cc0a, buf_hash); |
| 251 | } |
| 252 | |
| 253 | TEST_F(ProtoZeroMessageTest, MessageHandle) { |
| 254 | FakeRootMessage* msg1 = NewMessage(); |
| 255 | FakeRootMessage* msg2 = NewMessage(); |
| 256 | FakeRootMessage* msg3 = NewMessage(); |
| 257 | FakeRootMessage* ignored_msg = NewMessage(); |
| 258 | uint8_t msg1_size[proto_utils::kMessageLengthFieldSize] = {}; |
| 259 | uint8_t msg2_size[proto_utils::kMessageLengthFieldSize] = {}; |
| 260 | uint8_t msg3_size[proto_utils::kMessageLengthFieldSize] = {}; |
| 261 | msg1->set_size_field( |
| 262 | {&msg1_size[0], &msg1_size[proto_utils::kMessageLengthFieldSize]}); |
| 263 | msg2->set_size_field( |
| 264 | {&msg2_size[0], &msg2_size[proto_utils::kMessageLengthFieldSize]}); |
| 265 | msg3->set_size_field( |
| 266 | {&msg3_size[0], &msg3_size[proto_utils::kMessageLengthFieldSize]}); |
| 267 | |
| 268 | // Test that the handle going out of scope causes the finalization of the |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 269 | // target message and triggers the optional callback. |
| 270 | size_t callback_arg = 0; |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 271 | { |
| 272 | ProtoZeroMessageHandle<FakeRootMessage> handle1(msg1); |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 273 | handle1.set_on_finalize([&callback_arg](size_t sz) { callback_arg = sz; }); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 274 | handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */); |
| 275 | ASSERT_EQ(0u, msg1_size[0]); |
| 276 | } |
| 277 | ASSERT_EQ(0x83u, msg1_size[0]); |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 278 | ASSERT_EQ(3u, callback_arg); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 279 | |
| 280 | // Test that the handle can be late initialized. |
| 281 | ProtoZeroMessageHandle<FakeRootMessage> handle2(ignored_msg); |
| 282 | handle2 = ProtoZeroMessageHandle<FakeRootMessage>(msg2); |
| 283 | handle2->AppendBytes(1 /* field_id */, kTestBytes, 2 /* size */); |
| 284 | ASSERT_EQ(0u, msg2_size[0]); // |msg2| should not be finalized yet. |
| 285 | |
| 286 | // Test that std::move works and does NOT cause finalization of the moved |
| 287 | // message. |
| 288 | ProtoZeroMessageHandle<FakeRootMessage> handle_swp(ignored_msg); |
| 289 | handle_swp = std::move(handle2); |
| 290 | ASSERT_EQ(0u, msg2_size[0]); // msg2 should be NOT finalized yet. |
| 291 | handle_swp->AppendBytes(2 /* field_id */, kTestBytes, 3 /* size */); |
| 292 | |
| 293 | ProtoZeroMessageHandle<FakeRootMessage> handle3(msg3); |
| 294 | handle3->AppendBytes(1 /* field_id */, kTestBytes, 4 /* size */); |
| 295 | ASSERT_EQ(0u, msg3_size[0]); // msg2 should be NOT finalized yet. |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 296 | callback_arg = 0; |
| 297 | handle3.set_on_finalize([&callback_arg](size_t sz) { callback_arg = sz; }); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 298 | |
| 299 | // Both |handle3| and |handle_swp| point to a valid message (respectively, |
| 300 | // |msg3| and |msg2|). Now move |handle3| into |handle_swp|. |
| 301 | handle_swp = std::move(handle3); |
| 302 | ASSERT_EQ(0x89u, msg2_size[0]); // |msg2| should be finalized at this point. |
| 303 | |
| 304 | // At this point writing into handle_swp should actually write into |msg3|. |
| 305 | ASSERT_EQ(msg3, &*handle_swp); |
| 306 | handle_swp->AppendBytes(2 /* field_id */, kTestBytes, 8 /* size */); |
| 307 | ProtoZeroMessageHandle<FakeRootMessage> another_handle(ignored_msg); |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 308 | ASSERT_EQ(0u, callback_arg); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 309 | handle_swp = std::move(another_handle); |
| 310 | ASSERT_EQ(0x90u, msg3_size[0]); // |msg3| should be finalized at this point. |
Primiano Tucci | 25aff4e | 2017-11-23 12:18:52 +0000 | [diff] [blame] | 311 | ASSERT_EQ(0x10u, callback_arg); |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 312 | |
| 313 | #if PROTOZERO_ENABLE_HANDLE_DEBUGGING() |
Primiano Tucci | d7d1be0 | 2017-10-30 17:41:34 +0000 | [diff] [blame] | 314 | // In developer builds w/ PERFETTO_DCHECK on a finalized message should |
| 315 | // invalidate the handle, in order to early catch bugs in the client code. |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 316 | FakeRootMessage* msg4 = NewMessage(); |
| 317 | ProtoZeroMessageHandle<FakeRootMessage> handle4(msg4); |
| 318 | ASSERT_EQ(msg4, &*handle4); |
| 319 | msg4->Finalize(); |
| 320 | ASSERT_EQ(nullptr, &*handle4); |
| 321 | #endif |
| 322 | |
| 323 | // Test also the behavior of handle with non-root (nested) messages. |
| 324 | |
| 325 | ContiguousMemoryRange size_msg_2; |
| 326 | { |
| 327 | auto* nested_msg_1 = NewMessage()->BeginNestedMessage<FakeChildMessage>(3); |
| 328 | ProtoZeroMessageHandle<FakeChildMessage> child_handle_1(nested_msg_1); |
| 329 | ContiguousMemoryRange size_msg_1 = nested_msg_1->size_field(); |
| 330 | memset(size_msg_1.begin, 0, size_msg_1.size()); |
| 331 | child_handle_1->AppendVarInt(1, 0x11); |
| 332 | |
| 333 | auto* nested_msg_2 = NewMessage()->BeginNestedMessage<FakeChildMessage>(2); |
| 334 | size_msg_2 = nested_msg_2->size_field(); |
| 335 | memset(size_msg_2.begin, 0, size_msg_2.size()); |
| 336 | ProtoZeroMessageHandle<FakeChildMessage> child_handle_2(nested_msg_2); |
| 337 | child_handle_2->AppendVarInt(2, 0xFF); |
| 338 | |
| 339 | // |nested_msg_1| should not be finalized yet. |
| 340 | ASSERT_EQ(0u, size_msg_1.begin[0]); |
| 341 | |
| 342 | // This move should cause |nested_msg_1| to be finalized, but not |
| 343 | // |nested_msg_2|, which will be finalized only after the current scope. |
| 344 | child_handle_1 = std::move(child_handle_2); |
| 345 | ASSERT_EQ(0x82u, size_msg_1.begin[0]); |
| 346 | ASSERT_EQ(0u, size_msg_2.begin[0]); |
| 347 | } |
| 348 | ASSERT_EQ(0x83u, size_msg_2.begin[0]); |
| 349 | } |
| 350 | |
Hector Dearman | 8d8ccd3 | 2017-11-27 16:06:34 +0000 | [diff] [blame] | 351 | TEST_F(ProtoZeroMessageTest, MoveMessageHandle) { |
| 352 | FakeRootMessage* msg = NewMessage(); |
| 353 | uint8_t msg_size[proto_utils::kMessageLengthFieldSize] = {}; |
| 354 | msg->set_size_field( |
| 355 | {&msg_size[0], &msg_size[proto_utils::kMessageLengthFieldSize]}); |
| 356 | |
| 357 | // Test that the handle going out of scope causes the finalization of the |
| 358 | // target message. |
| 359 | { |
| 360 | ProtoZeroMessageHandle<FakeRootMessage> handle1(msg); |
| 361 | ProtoZeroMessageHandle<FakeRootMessage> handle2{}; |
| 362 | handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */); |
| 363 | handle2 = std::move(handle1); |
| 364 | ASSERT_EQ(0u, msg_size[0]); |
| 365 | } |
| 366 | ASSERT_EQ(0x83u, msg_size[0]); |
| 367 | } |
| 368 | |
Primiano Tucci | 2a29ac7 | 2017-10-24 17:47:19 +0100 | [diff] [blame] | 369 | } // namespace |
| 370 | } // namespace protozero |