Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 : #include <lib/format/protocol_decoder.h>
19 :
20 : #include <lib/format/FlatTree.h>
21 : #include <lib/format/FlatTreePosition.h>
22 :
23 : namespace chip {
24 : namespace Decoders {
25 :
26 : namespace {
27 :
28 : using namespace chip::TLV;
29 :
30 : using chip::StringBuilderBase;
31 : using chip::TLVMeta::AttributeTag;
32 : using chip::TLVMeta::ClusterTag;
33 : using chip::TLVMeta::CommandTag;
34 : using chip::TLVMeta::ConstantValueTag;
35 : using chip::TLVMeta::EventTag;
36 : using chip::TLVMeta::ItemType;
37 :
38 : class ByTag
39 : {
40 : public:
41 602 : constexpr ByTag(Tag tag) : mTag(tag) {}
42 2853 : bool operator()(const chip::TLVMeta::ItemInfo & item) { return item.tag == mTag; }
43 :
44 : private:
45 : const Tag mTag;
46 : };
47 :
48 228 : CHIP_ERROR FormatCurrentValue(const TLVReader & reader, chip::StringBuilderBase & out)
49 : {
50 228 : switch (reader.GetType())
51 : {
52 0 : case kTLVType_SignedInteger: {
53 : int64_t sVal;
54 0 : ReturnErrorOnFailure(reader.Get(sVal));
55 0 : out.AddFormat("%" PRIi64, sVal);
56 0 : break;
57 : }
58 195 : case kTLVType_UnsignedInteger: {
59 : uint64_t uVal;
60 195 : ReturnErrorOnFailure(reader.Get(uVal));
61 195 : out.AddFormat("%" PRIu64, uVal);
62 195 : break;
63 : }
64 18 : case kTLVType_Boolean: {
65 : bool bVal;
66 18 : ReturnErrorOnFailure(reader.Get(bVal));
67 18 : out.Add(bVal ? "true" : "false");
68 18 : break;
69 : }
70 0 : case kTLVType_FloatingPointNumber: {
71 : double fpVal;
72 0 : ReturnErrorOnFailure(reader.Get(fpVal));
73 0 : out.AddFormat("%lf", fpVal);
74 0 : break;
75 : }
76 2 : case kTLVType_UTF8String: {
77 2 : const uint8_t * strbuf = nullptr;
78 2 : ReturnErrorOnFailure(reader.GetDataPtr(strbuf));
79 2 : out.AddFormat("\"%-.*s\"", static_cast<int>(reader.GetLength()), strbuf);
80 2 : break;
81 : }
82 10 : case kTLVType_ByteString: {
83 10 : const uint8_t * strbuf = nullptr;
84 10 : ReturnErrorOnFailure(reader.GetDataPtr(strbuf));
85 10 : out.Add("hex:");
86 388 : for (uint32_t i = 0; i < reader.GetLength(); i++)
87 : {
88 378 : out.AddFormat("%02X", strbuf[i]);
89 : }
90 10 : break;
91 : }
92 3 : case kTLVType_Null:
93 3 : out.Add("NULL");
94 3 : break;
95 :
96 0 : case kTLVType_NotSpecified:
97 0 : out.Add("Not Specified");
98 0 : break;
99 0 : default:
100 0 : out.Add("???");
101 0 : break;
102 : }
103 :
104 228 : return CHIP_NO_ERROR;
105 : }
106 :
107 : // Returns a null terminated string containing the current reader value
108 228 : void PrettyPrintCurrentValue(const TLVReader & reader, chip::StringBuilderBase & out, PayloadDecoderBase::DecodePosition & position)
109 : {
110 228 : CHIP_ERROR err = FormatCurrentValue(reader, out);
111 :
112 228 : if (err != CHIP_NO_ERROR)
113 : {
114 0 : out.AddFormat("ERR: %" CHIP_ERROR_FORMAT, err.Format());
115 16 : return;
116 : }
117 :
118 228 : auto data = position.Get();
119 228 : if (data == nullptr)
120 : {
121 16 : return;
122 : }
123 :
124 : // Report enum values in human readable form
125 212 : if (data->type == ItemType::kEnum && (reader.GetType() == kTLVType_UnsignedInteger))
126 : {
127 5 : uint64_t value = 0;
128 5 : VerifyOrReturn(reader.Get(value) == CHIP_NO_ERROR);
129 :
130 5 : position.Enter(ByTag(ConstantValueTag(value)));
131 5 : auto enum_data = position.Get();
132 5 : if (enum_data != nullptr)
133 : {
134 5 : out.Add(" == ").Add(enum_data->name);
135 : }
136 5 : position.Exit();
137 : }
138 :
139 212 : if (data->type == ItemType::kBitmap && (reader.GetType() == kTLVType_UnsignedInteger))
140 : {
141 2 : uint64_t value = 0;
142 2 : VerifyOrReturn(reader.Get(value) == CHIP_NO_ERROR);
143 :
144 2 : uint64_t bit = 0x01;
145 2 : bool first = true;
146 130 : for (unsigned i = 0; i < 64; i++, bit <<= 1)
147 : {
148 128 : if ((value & bit) == 0)
149 : {
150 123 : continue;
151 : }
152 :
153 : // NOTE: this only can select individual bits;
154 5 : position.Enter(ByTag(ConstantValueTag(bit)));
155 5 : auto bitmap_data = position.Get();
156 5 : if (bitmap_data == nullptr)
157 : {
158 0 : position.Exit();
159 0 : continue;
160 : }
161 :
162 : // Try to pretty print the value
163 5 : if (first)
164 : {
165 2 : out.Add(" == ");
166 2 : first = false;
167 : }
168 : else
169 : {
170 3 : out.Add(" | ");
171 : }
172 :
173 5 : out.Add(bitmap_data->name);
174 5 : value = value & (~bit);
175 :
176 5 : position.Exit();
177 : }
178 :
179 2 : if (!first && value)
180 : {
181 : // Only append if some constants were found.
182 0 : out.AddFormat(" | 0x%08" PRIX64, value);
183 : }
184 : }
185 :
186 212 : out.AddMarkerIfOverflow();
187 : }
188 :
189 : } // namespace
190 :
191 28 : PayloadDecoderBase::PayloadDecoderBase(const PayloadDecoderInitParams & params, StringBuilderBase & nameBuilder,
192 28 : StringBuilderBase & valueBuilder) :
193 28 : mProtocol(params.GetProtocol()),
194 28 : mMessageType(params.GetMessageType()), mNameBuilder(nameBuilder), mValueBuilder(valueBuilder),
195 :
196 28 : mPayloadPosition(params.GetProtocolDecodeTree(), params.GetProtocolDecodeTreeSize()),
197 56 : mIMContentPosition(params.GetClusterDecodeTree(), params.GetClusterDecodeTreeSize())
198 28 : {}
199 :
200 28 : void PayloadDecoderBase::StartDecoding(const TLVReader & reader)
201 : {
202 28 : mReader = reader;
203 28 : mPayloadPosition.ResetToTop();
204 28 : mIMContentPosition.ResetToTop();
205 28 : mCurrentNesting = 0;
206 28 : mClusterId = 0;
207 28 : mAttributeId = 0;
208 28 : mEventId = 0;
209 28 : mCommandId = 0;
210 28 : mState = State::kStarting;
211 28 : }
212 :
213 605 : bool PayloadDecoderBase::Next(PayloadEntry & entry)
214 : {
215 605 : switch (mState)
216 : {
217 28 : case State::kStarting:
218 28 : NextFromStarting(entry);
219 28 : return true;
220 516 : case State::kValueRead:
221 516 : NextFromValueRead(entry);
222 516 : return true;
223 33 : case State::kContentRead:
224 33 : NextFromContentRead(entry);
225 33 : return true;
226 28 : case State::kDone:
227 28 : return false;
228 : }
229 : // should never happen
230 0 : return false;
231 : }
232 :
233 28 : void PayloadDecoderBase::NextFromStarting(PayloadEntry & entry)
234 : {
235 : // Find the right protocol (fake cluster id)
236 28 : mPayloadPosition.Enter(ByTag(ClusterTag(0xFFFF0000 | mProtocol.ToFullyQualifiedSpecForm())));
237 28 : mPayloadPosition.Enter(ByTag(AttributeTag(mMessageType)));
238 :
239 28 : auto data = mPayloadPosition.Get();
240 28 : if (data == nullptr)
241 : {
242 : // do not try to decode unknown data. assume binary
243 2 : mNameBuilder.Reset().AddFormat("PROTO(0x%" PRIX32 ", 0x%X)", mProtocol.ToFullyQualifiedSpecForm(), mMessageType);
244 2 : mValueBuilder.Reset().Add("UNKNOWN");
245 2 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
246 2 : mState = State::kDone;
247 7 : return;
248 : }
249 :
250 : // name is known (so valid protocol)
251 26 : if (mReader.GetTotalLength() == 0)
252 : {
253 4 : mState = State::kDone;
254 4 : entry = PayloadEntry::SimpleValue(data->name, "");
255 4 : return;
256 : }
257 :
258 22 : if (data->type == ItemType::kProtocolBinaryData)
259 : {
260 1 : mState = State::kDone;
261 1 : entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
262 1 : return;
263 : }
264 :
265 21 : CHIP_ERROR err = mReader.Next(kTLVType_Structure, AnonymousTag());
266 21 : if (err != CHIP_NO_ERROR)
267 : {
268 0 : mValueBuilder.Reset().AddFormat("ERROR getting Anonymous Structure TLV: %" CHIP_ERROR_FORMAT, err.Format());
269 0 : mState = State::kDone;
270 0 : entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
271 0 : return;
272 : }
273 :
274 21 : EnterContainer(entry);
275 : }
276 :
277 166 : void PayloadDecoderBase::ExitContainer(PayloadEntry & entry)
278 : {
279 166 : entry = PayloadEntry::NestingExit();
280 :
281 166 : if (mCurrentNesting > 0)
282 : {
283 166 : if (mState == State::kContentRead)
284 : {
285 11 : mIMContentPosition.Exit();
286 11 : if (mIMContentPosition.DescendDepth() <= 1)
287 : {
288 : // Lever for actual content is 2: cluster::attr/cmd/ev
289 7 : mState = State::kValueRead;
290 7 : mPayloadPosition.Exit();
291 : }
292 : }
293 : else
294 : {
295 155 : mPayloadPosition.Exit();
296 : }
297 166 : CHIP_ERROR err = mReader.ExitContainer(mNestingEnters[--mCurrentNesting]);
298 166 : if (err != CHIP_NO_ERROR)
299 : {
300 0 : mValueBuilder.Reset().AddFormat("ERROR: %" CHIP_ERROR_FORMAT, err.Format());
301 0 : mNameBuilder.Reset().AddFormat("END CONTAINER");
302 0 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
303 0 : mState = State::kDone;
304 0 : return;
305 : }
306 : }
307 :
308 166 : if (mCurrentNesting == 0)
309 : {
310 21 : mState = State::kDone;
311 : }
312 : }
313 :
314 167 : bool PayloadDecoderBase::ReaderEnterContainer(PayloadEntry & entry)
315 : {
316 167 : if (mCurrentNesting >= kMaxDecodeDepth)
317 : {
318 1 : mValueBuilder.AddFormat("NESTING DEPTH REACHED");
319 1 : mReader.GetTag().AppendTo(mNameBuilder.Reset());
320 1 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
321 1 : return false;
322 : }
323 :
324 : TLVType containerType;
325 166 : CHIP_ERROR err = mReader.EnterContainer(containerType);
326 166 : if (err != CHIP_NO_ERROR)
327 : {
328 0 : mValueBuilder.AddFormat("ERROR entering container: %" CHIP_ERROR_FORMAT, err.Format());
329 0 : mReader.GetTag().AppendTo(mNameBuilder.Reset()); // assume enter is not done, so tag is correct
330 0 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
331 0 : mState = State::kDone;
332 0 : return false;
333 : }
334 :
335 166 : mNestingEnters[mCurrentNesting++] = containerType;
336 :
337 166 : return true;
338 : }
339 :
340 160 : void PayloadDecoderBase::EnterContainer(PayloadEntry & entry)
341 : {
342 : // Tag fetch must be done BEFORE entering container
343 160 : chip::TLV::Tag tag = mReader.GetTag();
344 :
345 160 : VerifyOrReturn(ReaderEnterContainer(entry));
346 :
347 159 : const chip::TLVMeta::ItemInfo * data = nullptr;
348 :
349 159 : if (mState == State::kContentRead)
350 : {
351 4 : data = mIMContentPosition.Get();
352 : }
353 : else
354 : {
355 155 : mState = State::kValueRead;
356 155 : data = mPayloadPosition.Get();
357 : }
358 :
359 159 : if (data == nullptr)
360 : {
361 32 : tag.AppendTo(mNameBuilder.Reset());
362 32 : entry = PayloadEntry::NestingEnter(mNameBuilder.c_str());
363 : }
364 : else
365 : {
366 127 : entry = PayloadEntry::NestingEnter(data->name);
367 : }
368 : }
369 :
370 33 : void PayloadDecoderBase::NextFromContentRead(PayloadEntry & entry)
371 : {
372 33 : CHIP_ERROR err = mReader.Next();
373 33 : if (err == CHIP_END_OF_TLV)
374 : {
375 11 : ExitContainer(entry);
376 20 : return;
377 : }
378 :
379 22 : if (err != CHIP_NO_ERROR)
380 : {
381 0 : mValueBuilder.Reset().AddFormat("ERROR on TLV Next: %" CHIP_ERROR_FORMAT, err.Format());
382 0 : entry = PayloadEntry::SimpleValue("TLV_ERR", mValueBuilder.c_str());
383 0 : mState = State::kDone;
384 0 : return;
385 : }
386 :
387 22 : if (mCurrentNesting > 0 && mNestingEnters[mCurrentNesting - 1] == kTLVType_List)
388 : {
389 : // Spec A5.3: `The members of a list may be encoded with any form of tag, including an anonymous tag.`
390 : // TLVMeta always uses Anonymous
391 0 : mIMContentPosition.Enter(ByTag(AnonymousTag()));
392 : }
393 : else
394 : {
395 22 : mIMContentPosition.Enter(ByTag(mReader.GetTag()));
396 : }
397 22 : auto data = mIMContentPosition.Get();
398 :
399 22 : if (data != nullptr)
400 : {
401 15 : if (data->type == ItemType::kProtocolBinaryData)
402 : {
403 0 : mIMContentPosition.Exit();
404 0 : entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
405 0 : return;
406 : }
407 : }
408 :
409 22 : if (TLVTypeIsContainer(mReader.GetType()))
410 : {
411 4 : EnterContainer(entry);
412 4 : return;
413 : }
414 :
415 18 : PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mIMContentPosition);
416 18 : mIMContentPosition.Exit();
417 :
418 18 : if (data == nullptr)
419 : {
420 5 : mReader.GetTag().AppendTo(mNameBuilder.Reset());
421 5 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
422 5 : return;
423 : }
424 :
425 13 : entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
426 : }
427 :
428 24 : void PayloadDecoderBase::MoveToContent(PayloadEntry & entry)
429 : {
430 24 : if (!mIMContentPosition.HasValidTree())
431 : {
432 9 : mPayloadPosition.Exit();
433 9 : return;
434 : }
435 :
436 15 : VerifyOrDie((entry.GetType() == PayloadEntry::IMPayloadType::kAttribute) ||
437 : (entry.GetType() == PayloadEntry::IMPayloadType::kCommand) ||
438 : (entry.GetType() == PayloadEntry::IMPayloadType::kEvent));
439 :
440 15 : mNameBuilder.Reset();
441 :
442 15 : mIMContentPosition.ResetToTop();
443 15 : mIMContentPosition.Enter(ByTag(ClusterTag(entry.GetClusterId())));
444 15 : auto data = mIMContentPosition.Get();
445 15 : if (data != nullptr)
446 : {
447 14 : mNameBuilder.AddFormat("%s::", data->name);
448 : }
449 : else
450 : {
451 1 : mNameBuilder.AddFormat("0x%" PRIx32 "::", entry.GetClusterId());
452 : }
453 :
454 15 : uint32_t id = 0;
455 15 : const char * id_type = "UNKNOWN";
456 :
457 15 : switch (entry.GetType())
458 : {
459 11 : case PayloadEntry::IMPayloadType::kAttribute:
460 11 : mIMContentPosition.Enter(ByTag(AttributeTag(entry.GetAttributeId())));
461 11 : id = entry.GetAttributeId();
462 11 : id_type = "ATTR";
463 11 : break;
464 2 : case PayloadEntry::IMPayloadType::kCommand:
465 2 : mIMContentPosition.Enter(ByTag(CommandTag(entry.GetCommandId())));
466 2 : id = entry.GetCommandId();
467 2 : id_type = "CMD";
468 2 : break;
469 2 : case PayloadEntry::IMPayloadType::kEvent:
470 2 : mIMContentPosition.Enter(ByTag(EventTag(entry.GetEventId())));
471 2 : id = entry.GetEventId();
472 2 : id_type = "EV";
473 2 : break;
474 0 : default:
475 : // never happens: verified all case above covered.
476 0 : break;
477 : }
478 :
479 15 : data = mIMContentPosition.Get();
480 15 : if (data != nullptr)
481 : {
482 14 : mNameBuilder.AddFormat("%s", data->name);
483 : }
484 : else
485 : {
486 1 : mNameBuilder.AddFormat("%s(0x%" PRIx32 ")", id_type, id);
487 : }
488 :
489 15 : if (TLVTypeIsContainer(mReader.GetType()))
490 : {
491 7 : mState = State::kContentRead;
492 7 : entry = PayloadEntry::NestingEnter(mNameBuilder.c_str());
493 7 : ReaderEnterContainer(entry);
494 : }
495 : else
496 : {
497 8 : PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mIMContentPosition);
498 8 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
499 :
500 : // Can simply exit, only one value to return
501 8 : mPayloadPosition.Exit();
502 : }
503 : }
504 :
505 516 : void PayloadDecoderBase::NextFromValueRead(PayloadEntry & entry)
506 : {
507 516 : CHIP_ERROR err = mReader.Next();
508 516 : if (err == CHIP_END_OF_TLV)
509 : {
510 155 : ExitContainer(entry);
511 325 : return;
512 : }
513 :
514 361 : if (err != CHIP_NO_ERROR)
515 : {
516 0 : mValueBuilder.Reset().AddFormat("ERROR on TLV Next: %" CHIP_ERROR_FORMAT, err.Format());
517 0 : entry = PayloadEntry::SimpleValue("TLV_ERR", mValueBuilder.c_str());
518 0 : mState = State::kDone;
519 0 : return;
520 : }
521 :
522 : // Attempt to find information about the current tag
523 361 : mPayloadPosition.Enter(ByTag(mReader.GetTag()));
524 361 : auto data = mPayloadPosition.Get();
525 :
526 : // handle special types
527 361 : if (data != nullptr)
528 : {
529 319 : if (data->type == ItemType::kProtocolBinaryData)
530 : {
531 0 : mPayloadPosition.Exit();
532 0 : entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
533 0 : return;
534 : }
535 :
536 319 : if (data->type == ItemType::kProtocolPayloadAttribute)
537 : {
538 20 : entry = PayloadEntry::AttributePayload(mClusterId, mAttributeId);
539 20 : MoveToContent(entry);
540 20 : return;
541 : }
542 :
543 299 : if (data->type == ItemType::kProtocolPayloadCommand)
544 : {
545 2 : entry = PayloadEntry::CommandPayload(mClusterId, mCommandId);
546 2 : MoveToContent(entry);
547 2 : return;
548 : }
549 :
550 297 : if (data->type == ItemType::kProtocolPayloadEvent)
551 : {
552 2 : entry = PayloadEntry::EventPayload(mClusterId, mEventId);
553 2 : MoveToContent(entry);
554 2 : return;
555 : }
556 : }
557 :
558 337 : if (TLVTypeIsContainer(mReader.GetType()))
559 : {
560 135 : EnterContainer(entry);
561 135 : return;
562 : }
563 :
564 202 : if (data == nullptr)
565 : {
566 11 : mReader.GetTag().AppendTo(mNameBuilder.Reset());
567 11 : PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mPayloadPosition);
568 11 : entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
569 11 : mPayloadPosition.Exit();
570 11 : return;
571 : }
572 :
573 : // at this point, data is "simple data" or "simple data with meaning"
574 :
575 191 : const chip::TLVMeta::ItemInfo * info = nullptr;
576 191 : switch (data->type)
577 : {
578 41 : case ItemType::kProtocolClusterId:
579 41 : mReader.Get(mClusterId);
580 41 : mIMContentPosition.ResetToTop();
581 41 : mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
582 41 : info = mIMContentPosition.Get();
583 41 : break;
584 36 : case ItemType::kProtocolAttributeId:
585 36 : mReader.Get(mAttributeId);
586 36 : mIMContentPosition.ResetToTop();
587 36 : mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
588 36 : mIMContentPosition.Enter(ByTag(AttributeTag(mAttributeId)));
589 36 : info = mIMContentPosition.Get();
590 36 : break;
591 3 : case ItemType::kProtocolCommandId:
592 3 : mReader.Get(mCommandId);
593 3 : mIMContentPosition.ResetToTop();
594 3 : mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
595 3 : mIMContentPosition.Enter(ByTag(CommandTag(mCommandId)));
596 3 : info = mIMContentPosition.Get();
597 3 : break;
598 2 : case ItemType::kProtocolEventId:
599 2 : mReader.Get(mEventId);
600 2 : mIMContentPosition.ResetToTop();
601 2 : mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
602 2 : mIMContentPosition.Enter(ByTag(EventTag(mEventId)));
603 2 : info = mIMContentPosition.Get();
604 2 : break;
605 109 : default:
606 109 : break;
607 : }
608 :
609 191 : PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mPayloadPosition);
610 191 : if (info != nullptr)
611 : {
612 46 : mValueBuilder.Add(" == '").Add(info->name).Add("'");
613 : }
614 :
615 191 : mPayloadPosition.Exit();
616 191 : entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
617 : }
618 :
619 : } // namespace Decoders
620 : } // namespace chip
|