/src/connectedhomeip/examples/common/tracing/TraceHandlers.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021 Project CHIP Authors |
3 | | * All rights reserved. |
4 | | * |
5 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
6 | | * you may not use this file except in compliance with the License. |
7 | | * You may obtain a copy of the License at |
8 | | * |
9 | | * http://www.apache.org/licenses/LICENSE-2.0 |
10 | | * |
11 | | * Unless required by applicable law or agreed to in writing, software |
12 | | * distributed under the License is distributed on an "AS IS" BASIS, |
13 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | * See the License for the specific language governing permissions and |
15 | | * limitations under the License. |
16 | | * |
17 | | */ |
18 | | #include "TraceHandlers.h" |
19 | | |
20 | | #include <mutex> |
21 | | #include <stdint.h> |
22 | | #include <string> |
23 | | #include <vector> |
24 | | |
25 | | #include <lib/support/BytesToHex.h> |
26 | | #include <lib/support/CodeUtils.h> |
27 | | #include <lib/support/logging/CHIPLogging.h> |
28 | | #include <transport/TraceMessage.h> |
29 | | |
30 | | // For `s` std::string literal suffix |
31 | | using namespace std::string_literals; |
32 | | |
33 | | namespace chip { |
34 | | namespace trace { |
35 | | |
36 | | #if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED |
37 | | namespace { |
38 | | |
39 | | // Handles the output from the trace handlers. |
40 | | class TraceOutput |
41 | | { |
42 | | public: |
43 | 0 | ~TraceOutput() { UnregisterAllStreams(); } |
44 | | |
45 | | void RegisterStream(TraceStream * stream) |
46 | 0 | { |
47 | 0 | std::lock_guard<std::mutex> guard(mLock); |
48 | 0 | mStreams.push_back(stream); |
49 | 0 | } |
50 | | |
51 | | void UnregisterAllStreams() |
52 | 0 | { |
53 | 0 | for (auto stream : mStreams) |
54 | 0 | { |
55 | 0 | delete stream; |
56 | 0 | } |
57 | 0 | mStreams.clear(); |
58 | 0 | } |
59 | | |
60 | | void StartEvent(const std::string & label) |
61 | 0 | { |
62 | 0 | std::lock_guard<std::mutex> guard(mLock); |
63 | 0 | for (auto stream : mStreams) |
64 | 0 | { |
65 | 0 | stream->StartEvent(label); |
66 | 0 | } |
67 | 0 | } |
68 | | |
69 | | void AddField(const std::string & tag, const std::string & data) |
70 | 0 | { |
71 | 0 | std::lock_guard<std::mutex> guard(mLock); |
72 | 0 | for (auto stream : mStreams) |
73 | 0 | { |
74 | 0 | stream->AddField(tag, data); |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | void FinishEvent() |
79 | 0 | { |
80 | 0 | std::lock_guard<std::mutex> guard(mLock); |
81 | 0 | for (auto stream : mStreams) |
82 | 0 | { |
83 | 0 | stream->FinishEvent(); |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | | bool HasStreamAvailable() const |
88 | 0 | { |
89 | 0 | std::lock_guard<std::mutex> guard(mLock); |
90 | 0 | return (mStreams.size()); |
91 | 0 | } |
92 | | |
93 | | private: |
94 | | mutable std::mutex mLock; |
95 | | std::vector<TraceStream *> mStreams; |
96 | | }; |
97 | | |
98 | | TraceOutput gTraceOutputs; |
99 | | |
100 | | std::string AsJsonKey(const std::string & key, const std::string & value, bool prepend_comma) |
101 | 0 | { |
102 | 0 | return (prepend_comma ? ", "s : ""s) + "\""s + key + "\": " + value; |
103 | 0 | } |
104 | | |
105 | | std::string AsFirstJsonKey(const std::string & key, const std::string & value) |
106 | 0 | { |
107 | 0 | return AsJsonKey(key, value, /* prepend_comma = */ false); |
108 | 0 | } |
109 | | |
110 | | std::string AsNextJsonKey(const std::string & key, const std::string & value) |
111 | 0 | { |
112 | 0 | return AsJsonKey(key, value, /* prepend_comma = */ true); |
113 | 0 | } |
114 | | |
115 | | std::string AsJsonString(const std::string & value) |
116 | 0 | { |
117 | 0 | return "\""s + value + "\""s; |
118 | 0 | } |
119 | | |
120 | | std::string AsJsonString(const Transport::PeerAddress * peerAddress) |
121 | 0 | { |
122 | 0 | char convBuf[Transport::PeerAddress::kMaxToStringSize] = { 0 }; |
123 | |
|
124 | 0 | peerAddress->ToString(convBuf); |
125 | 0 | return AsJsonString(convBuf); |
126 | 0 | } |
127 | | |
128 | | std::string AsJsonBool(bool isTrue) |
129 | 0 | { |
130 | 0 | return isTrue ? "true"s : "false"s; |
131 | 0 | } |
132 | | |
133 | | std::string AsJsonHexString(const uint8_t * buf, size_t bufLen) |
134 | 0 | { |
135 | | // Avoid hex conversion that would fail on empty |
136 | 0 | if (bufLen == 0) |
137 | 0 | { |
138 | 0 | return AsJsonString(""); |
139 | 0 | } |
140 | | |
141 | | // Fill a buffer long enough for the hex conversion, that will be overwritten |
142 | 0 | std::vector<char> hexBuf(2 * bufLen, '\0'); |
143 | |
|
144 | 0 | CHIP_ERROR status = Encoding::BytesToLowercaseHexBuffer(buf, bufLen, hexBuf.data(), hexBuf.size()); |
145 | | |
146 | | // Static conditions exist that should ensure never failing. Catch failure in an assert. |
147 | 0 | if (status != CHIP_NO_ERROR) |
148 | 0 | { |
149 | 0 | ChipLogError(Support, "Unexpected failure: %" CHIP_ERROR_FORMAT, status.Format()); |
150 | 0 | VerifyOrDie(status == CHIP_NO_ERROR); |
151 | 0 | } |
152 | | |
153 | 0 | return AsJsonString(std::string(hexBuf.data(), hexBuf.size())); |
154 | 0 | } |
155 | | |
156 | | std::string PacketHeaderToJson(const PacketHeader * packetHeader) |
157 | 0 | { |
158 | 0 | std::string jsonBody; |
159 | |
|
160 | 0 | uint32_t messageCounter = packetHeader->GetMessageCounter(); |
161 | 0 | jsonBody += AsFirstJsonKey("msg_counter", std::to_string(messageCounter)); |
162 | |
|
163 | 0 | const Optional<NodeId> & sourceNodeId = packetHeader->GetSourceNodeId(); |
164 | 0 | if (sourceNodeId.HasValue()) |
165 | 0 | { |
166 | 0 | jsonBody += AsNextJsonKey("source_node_id", std::to_string(sourceNodeId.Value())); |
167 | 0 | } |
168 | |
|
169 | 0 | uint16_t sessionId = packetHeader->GetSessionId(); |
170 | 0 | const Optional<GroupId> & groupId = packetHeader->GetDestinationGroupId(); |
171 | 0 | const Optional<NodeId> & destNodeId = packetHeader->GetDestinationNodeId(); |
172 | 0 | if (packetHeader->IsValidGroupMsg()) |
173 | 0 | { |
174 | 0 | if (groupId.HasValue()) |
175 | 0 | { |
176 | 0 | jsonBody += AsNextJsonKey("group_id", std::to_string(groupId.Value())); |
177 | 0 | } |
178 | |
|
179 | 0 | jsonBody += AsNextJsonKey("group_key_hash", std::to_string(sessionId)); |
180 | 0 | } |
181 | 0 | else if (destNodeId.HasValue()) |
182 | 0 | { |
183 | 0 | jsonBody += AsNextJsonKey("dest_node_id", std::to_string(destNodeId.Value())); |
184 | 0 | } |
185 | |
|
186 | 0 | jsonBody += AsNextJsonKey("session_id", std::to_string(sessionId)); |
187 | |
|
188 | 0 | uint8_t messageFlags = packetHeader->GetMessageFlags(); |
189 | 0 | jsonBody += AsNextJsonKey("msg_flags", std::to_string(messageFlags)); |
190 | |
|
191 | 0 | uint8_t securityFlags = packetHeader->GetSecurityFlags(); |
192 | 0 | jsonBody += AsNextJsonKey("security_flags", std::to_string(securityFlags)); |
193 | |
|
194 | 0 | return jsonBody; |
195 | 0 | } |
196 | | |
197 | | std::string PayloadHeaderToJson(const PayloadHeader * payloadHeader) |
198 | 0 | { |
199 | |
|
200 | 0 | std::string jsonBody; |
201 | |
|
202 | 0 | uint8_t exchangeFlags = payloadHeader->GetExchangeFlags(); |
203 | 0 | jsonBody += AsFirstJsonKey("exchange_flags", std::to_string(exchangeFlags)); |
204 | |
|
205 | 0 | uint16_t exchangeId = payloadHeader->GetExchangeID(); |
206 | 0 | jsonBody += AsNextJsonKey("exchange_id", std::to_string(exchangeId)); |
207 | |
|
208 | 0 | uint32_t protocolId = payloadHeader->GetProtocolID().ToFullyQualifiedSpecForm(); |
209 | 0 | jsonBody += AsNextJsonKey("protocol_id", std::to_string(protocolId)); |
210 | |
|
211 | 0 | uint8_t protocolOpcode = payloadHeader->GetMessageType(); |
212 | 0 | jsonBody += AsNextJsonKey("protocol_opcode", std::to_string(protocolOpcode)); |
213 | |
|
214 | 0 | bool isInitiator = payloadHeader->IsInitiator(); |
215 | 0 | jsonBody += AsNextJsonKey("is_initiator", AsJsonBool(isInitiator)); |
216 | |
|
217 | 0 | bool needsAck = payloadHeader->NeedsAck(); |
218 | 0 | jsonBody += AsNextJsonKey("is_ack_requested", AsJsonBool(needsAck)); |
219 | |
|
220 | 0 | const Optional<uint32_t> & acknowledgedMessageCounter = payloadHeader->GetAckMessageCounter(); |
221 | 0 | if (acknowledgedMessageCounter.HasValue()) |
222 | 0 | { |
223 | 0 | jsonBody += AsNextJsonKey("acknowledged_msg_counter", std::to_string(acknowledgedMessageCounter.Value())); |
224 | 0 | } |
225 | |
|
226 | 0 | return jsonBody; |
227 | 0 | } |
228 | | |
229 | | void SecureMessageSentHandler(const TraceSecureMessageSentData * eventData) |
230 | 0 | { |
231 | 0 | if (!gTraceOutputs.HasStreamAvailable()) |
232 | 0 | { |
233 | 0 | return; |
234 | 0 | } |
235 | | |
236 | 0 | std::string jsonBody = "{ \"direction\": \"outbound\", "; |
237 | 0 | jsonBody += AsFirstJsonKey("peer_address", AsJsonString(eventData->peerAddress)); |
238 | 0 | jsonBody += ", "; |
239 | 0 | jsonBody += PacketHeaderToJson(eventData->packetHeader); |
240 | 0 | jsonBody += ", "; |
241 | 0 | jsonBody += PayloadHeaderToJson(eventData->payloadHeader); |
242 | 0 | jsonBody += ", "; |
243 | 0 | jsonBody += AsFirstJsonKey("payload_size", std::to_string(eventData->packetSize)); |
244 | 0 | jsonBody += ", "; |
245 | 0 | jsonBody += AsFirstJsonKey("payload_hex", AsJsonHexString(eventData->packetPayload, eventData->packetSize)); |
246 | 0 | jsonBody += "}"; |
247 | |
|
248 | 0 | gTraceOutputs.StartEvent(std::string{ kTraceMessageEvent } + "." + kTraceMessageSentDataFormat); |
249 | 0 | gTraceOutputs.AddField("json", jsonBody); |
250 | 0 | gTraceOutputs.FinishEvent(); |
251 | 0 | } |
252 | | |
253 | | void SecureMessageReceivedHandler(const TraceSecureMessageReceivedData * eventData) |
254 | 0 | { |
255 | 0 | if (!gTraceOutputs.HasStreamAvailable()) |
256 | 0 | { |
257 | 0 | return; |
258 | 0 | } |
259 | | |
260 | 0 | std::string jsonBody = "{ \"direction\": \"inbound\", "; |
261 | 0 | jsonBody += AsFirstJsonKey("peer_address", AsJsonString(eventData->peerAddress)); |
262 | 0 | jsonBody += ", "; |
263 | 0 | jsonBody += PacketHeaderToJson(eventData->packetHeader); |
264 | 0 | jsonBody += ", "; |
265 | 0 | jsonBody += PayloadHeaderToJson(eventData->payloadHeader); |
266 | 0 | jsonBody += ", "; |
267 | 0 | jsonBody += AsFirstJsonKey("payload_size", std::to_string(eventData->packetSize)); |
268 | 0 | jsonBody += ", "; |
269 | 0 | jsonBody += AsFirstJsonKey("payload_hex", AsJsonHexString(eventData->packetPayload, eventData->packetSize)); |
270 | 0 | jsonBody += "}"; |
271 | |
|
272 | 0 | gTraceOutputs.StartEvent(std::string{ kTraceMessageEvent } + "." + kTraceMessageReceivedDataFormat); |
273 | 0 | gTraceOutputs.AddField("json", jsonBody); |
274 | 0 | gTraceOutputs.FinishEvent(); |
275 | | |
276 | | // Note that `eventData->session` is currently ignored. |
277 | 0 | } |
278 | | |
279 | | void TraceHandler(const char * type, const void * data, size_t size) |
280 | 0 | { |
281 | 0 | if ((std::string{ type } == kTraceMessageSentDataFormat) && (size == sizeof(TraceSecureMessageSentData))) |
282 | 0 | { |
283 | 0 | SecureMessageSentHandler(reinterpret_cast<const TraceSecureMessageSentData *>(data)); |
284 | 0 | } |
285 | 0 | else if ((std::string{ type } == kTraceMessageReceivedDataFormat) && (size == sizeof(TraceSecureMessageReceivedData))) |
286 | 0 | { |
287 | 0 | SecureMessageReceivedHandler(reinterpret_cast<const TraceSecureMessageReceivedData *>(data)); |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | } // namespace |
292 | | |
293 | | void AddTraceStream(TraceStream * stream) |
294 | 0 | { |
295 | 0 | gTraceOutputs.RegisterStream(stream); |
296 | 0 | } |
297 | | |
298 | | void InitTrace() |
299 | 0 | { |
300 | 0 | SetTransportTraceHook(TraceHandler); |
301 | 0 | } |
302 | | |
303 | | void DeInitTrace() |
304 | 0 | { |
305 | 0 | gTraceOutputs.UnregisterAllStreams(); |
306 | 0 | } |
307 | | |
308 | | #else |
309 | | void AddTraceStream(TraceStream *) {} |
310 | | void InitTrace() {} |
311 | | void DeInitTrace() {} |
312 | | #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED |
313 | | |
314 | | } // namespace trace |
315 | | } // namespace chip |