/* * Copyright (c) 2021 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "TraceHandlers.h" #include #include #include #include #include #include #include #include // For `s` std::string literal suffix using namespace std::string_literals; namespace chip { namespace trace { #if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED namespace { // Handles the output from the trace handlers. class TraceOutput { public: ~TraceOutput() { UnregisterAllStreams(); } void RegisterStream(TraceStream * stream) { std::lock_guard guard(mLock); mStreams.push_back(stream); } void UnregisterAllStreams() { for (auto stream : mStreams) { delete stream; } mStreams.clear(); } void StartEvent(const std::string & label) { std::lock_guard guard(mLock); for (auto stream : mStreams) { stream->StartEvent(label); } } void AddField(const std::string & tag, const std::string & data) { std::lock_guard guard(mLock); for (auto stream : mStreams) { stream->AddField(tag, data); } } void FinishEvent() { std::lock_guard guard(mLock); for (auto stream : mStreams) { stream->FinishEvent(); } } bool HasStreamAvailable() const { std::lock_guard guard(mLock); return (mStreams.size()); } private: mutable std::mutex mLock; std::vector mStreams; }; TraceOutput gTraceOutputs; std::string AsJsonKey(const std::string & key, const std::string & value, bool prepend_comma) { return (prepend_comma ? ", "s : ""s) + "\""s + key + "\": " + value; } std::string AsFirstJsonKey(const std::string & key, const std::string & value) { return AsJsonKey(key, value, /* prepend_comma = */ false); } std::string AsNextJsonKey(const std::string & key, const std::string & value) { return AsJsonKey(key, value, /* prepend_comma = */ true); } std::string AsJsonString(const std::string & value) { return "\""s + value + "\""s; } std::string AsJsonString(const Transport::PeerAddress * peerAddress) { char convBuf[Transport::PeerAddress::kMaxToStringSize] = { 0 }; peerAddress->ToString(convBuf); return AsJsonString(convBuf); } std::string AsJsonBool(bool isTrue) { return isTrue ? "true"s : "false"s; } std::string AsJsonHexString(const uint8_t * buf, size_t bufLen) { // Avoid hex conversion that would fail on empty if (bufLen == 0) { return AsJsonString(""); } // Fill a buffer long enough for the hex conversion, that will be overwritten std::vector hexBuf(2 * bufLen, '\0'); CHIP_ERROR status = Encoding::BytesToLowercaseHexBuffer(buf, bufLen, hexBuf.data(), hexBuf.size()); // Static conditions exist that should ensure never failing. Catch failure in an assert. if (status != CHIP_NO_ERROR) { ChipLogError(Support, "Unexpected failure: %" CHIP_ERROR_FORMAT, status.Format()); VerifyOrDie(status == CHIP_NO_ERROR); } return AsJsonString(std::string(hexBuf.data(), hexBuf.size())); } std::string PacketHeaderToJson(const PacketHeader * packetHeader) { std::string jsonBody; uint32_t messageCounter = packetHeader->GetMessageCounter(); jsonBody += AsFirstJsonKey("msg_counter", std::to_string(messageCounter)); const Optional & sourceNodeId = packetHeader->GetSourceNodeId(); if (sourceNodeId.HasValue()) { jsonBody += AsNextJsonKey("source_node_id", std::to_string(sourceNodeId.Value())); } uint16_t sessionId = packetHeader->GetSessionId(); const Optional & groupId = packetHeader->GetDestinationGroupId(); const Optional & destNodeId = packetHeader->GetDestinationNodeId(); if (packetHeader->IsValidGroupMsg()) { if (groupId.HasValue()) { jsonBody += AsNextJsonKey("group_id", std::to_string(groupId.Value())); } jsonBody += AsNextJsonKey("group_key_hash", std::to_string(sessionId)); } else if (destNodeId.HasValue()) { jsonBody += AsNextJsonKey("dest_node_id", std::to_string(destNodeId.Value())); } jsonBody += AsNextJsonKey("session_id", std::to_string(sessionId)); uint8_t messageFlags = packetHeader->GetMessageFlags(); jsonBody += AsNextJsonKey("msg_flags", std::to_string(messageFlags)); uint8_t securityFlags = packetHeader->GetSecurityFlags(); jsonBody += AsNextJsonKey("security_flags", std::to_string(securityFlags)); return jsonBody; } std::string PayloadHeaderToJson(const PayloadHeader * payloadHeader) { std::string jsonBody; uint8_t exchangeFlags = payloadHeader->GetExchangeFlags(); jsonBody += AsFirstJsonKey("exchange_flags", std::to_string(exchangeFlags)); uint16_t exchangeId = payloadHeader->GetExchangeID(); jsonBody += AsNextJsonKey("exchange_id", std::to_string(exchangeId)); uint32_t protocolId = payloadHeader->GetProtocolID().ToFullyQualifiedSpecForm(); jsonBody += AsNextJsonKey("protocol_id", std::to_string(protocolId)); uint8_t protocolOpcode = payloadHeader->GetMessageType(); jsonBody += AsNextJsonKey("protocol_opcode", std::to_string(protocolOpcode)); bool isInitiator = payloadHeader->IsInitiator(); jsonBody += AsNextJsonKey("is_initiator", AsJsonBool(isInitiator)); bool needsAck = payloadHeader->NeedsAck(); jsonBody += AsNextJsonKey("is_ack_requested", AsJsonBool(needsAck)); const Optional & acknowledgedMessageCounter = payloadHeader->GetAckMessageCounter(); if (acknowledgedMessageCounter.HasValue()) { jsonBody += AsNextJsonKey("acknowledged_msg_counter", std::to_string(acknowledgedMessageCounter.Value())); } return jsonBody; } void SecureMessageSentHandler(const TraceSecureMessageSentData * eventData) { if (!gTraceOutputs.HasStreamAvailable()) { return; } std::string jsonBody = "{ \"direction\": \"outbound\", "; jsonBody += AsFirstJsonKey("peer_address", AsJsonString(eventData->peerAddress)); jsonBody += ", "; jsonBody += PacketHeaderToJson(eventData->packetHeader); jsonBody += ", "; jsonBody += PayloadHeaderToJson(eventData->payloadHeader); jsonBody += ", "; jsonBody += AsFirstJsonKey("payload_size", std::to_string(eventData->packetSize)); jsonBody += ", "; jsonBody += AsFirstJsonKey("payload_hex", AsJsonHexString(eventData->packetPayload, eventData->packetSize)); jsonBody += "}"; gTraceOutputs.StartEvent(std::string{ kTraceMessageEvent } + "." + kTraceMessageSentDataFormat); gTraceOutputs.AddField("json", jsonBody); gTraceOutputs.FinishEvent(); } void SecureMessageReceivedHandler(const TraceSecureMessageReceivedData * eventData) { if (!gTraceOutputs.HasStreamAvailable()) { return; } std::string jsonBody = "{ \"direction\": \"inbound\", "; jsonBody += AsFirstJsonKey("peer_address", AsJsonString(eventData->peerAddress)); jsonBody += ", "; jsonBody += PacketHeaderToJson(eventData->packetHeader); jsonBody += ", "; jsonBody += PayloadHeaderToJson(eventData->payloadHeader); jsonBody += ", "; jsonBody += AsFirstJsonKey("payload_size", std::to_string(eventData->packetSize)); jsonBody += ", "; jsonBody += AsFirstJsonKey("payload_hex", AsJsonHexString(eventData->packetPayload, eventData->packetSize)); jsonBody += "}"; gTraceOutputs.StartEvent(std::string{ kTraceMessageEvent } + "." + kTraceMessageReceivedDataFormat); gTraceOutputs.AddField("json", jsonBody); gTraceOutputs.FinishEvent(); // Note that `eventData->session` is currently ignored. } void TraceHandler(const char * type, const void * data, size_t size) { if ((std::string{ type } == kTraceMessageSentDataFormat) && (size == sizeof(TraceSecureMessageSentData))) { SecureMessageSentHandler(reinterpret_cast(data)); } else if ((std::string{ type } == kTraceMessageReceivedDataFormat) && (size == sizeof(TraceSecureMessageReceivedData))) { SecureMessageReceivedHandler(reinterpret_cast(data)); } } } // namespace void AddTraceStream(TraceStream * stream) { gTraceOutputs.RegisterStream(stream); } void InitTrace() { SetTransportTraceHook(TraceHandler); } void DeInitTrace() { gTraceOutputs.UnregisterAllStreams(); } #else void AddTraceStream(TraceStream *) {} void InitTrace() {} void DeInitTrace() {} #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED } // namespace trace } // namespace chip