/*
 *    Copyright (c) 2023, The OpenThread Authors.
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without
 *    modification, are permitted provided that the following conditions are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *    POSSIBILITY OF SUCH DAMAGE.
 */
#include "android/otdaemon_telemetry.hpp"

#include <openthread/border_agent.h>
#include <openthread/nat64.h>
#include <openthread/openthread-system.h>
#include <openthread/thread.h>
#include <openthread/thread_ftd.h>
#include <openthread/trel.h>
#include <openthread/platform/radio.h>

#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
#include <openthread/dnssd_server.h>
#endif
#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
#include <openthread/srp_server.h>
#endif

#include "statslog_threadnetwork.h"
#include "common/code_utils.hpp"
#include "mdns/mdns.hpp"
#include "proto/threadnetwork_atoms.pb.h"

namespace otbr {
namespace Android {
using android::os::statsd::threadnetwork::ThreadnetworkDeviceInfoReported;
using android::os::statsd::threadnetwork::ThreadnetworkTelemetryDataReported;
using android::os::statsd::threadnetwork::ThreadnetworkTopoEntryRepeated;
using TelemetryData = android::os::statsd::threadnetwork::ThreadnetworkTelemetryDataReported;

static uint32_t TelemetryNodeTypeFromRoleAndLinkMode(const otDeviceRole &aRole, const otLinkModeConfig &aLinkModeCfg)
{
    uint32_t nodeType;

    switch (aRole)
    {
    case OT_DEVICE_ROLE_DISABLED:
        nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_DISABLED;
        break;
    case OT_DEVICE_ROLE_DETACHED:
        nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_DETACHED;
        break;
    case OT_DEVICE_ROLE_ROUTER:
        nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_ROUTER;
        break;
    case OT_DEVICE_ROLE_LEADER:
        nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_LEADER;
        break;
    case OT_DEVICE_ROLE_CHILD:
        if (!aLinkModeCfg.mRxOnWhenIdle)
        {
            nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_SLEEPY_END;
        }
        else if (!aLinkModeCfg.mDeviceType)
        {
            // If it's not an FTD, return as minimal end device.
            nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_MINIMAL_END;
        }
        else
        {
            nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_END;
        }
        break;
    default:
        nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_UNSPECIFIED;
    }

    return nodeType;
}

#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
ThreadnetworkTelemetryDataReported::SrpServerState SrpServerStateFromOtSrpServerState(otSrpServerState aSrpServerState)
{
    ThreadnetworkTelemetryDataReported::SrpServerState srpServerState;

    switch (aSrpServerState)
    {
    case OT_SRP_SERVER_STATE_DISABLED:
        srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_DISABLED;
        break;
    case OT_SRP_SERVER_STATE_RUNNING:
        srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_RUNNING;
        break;
    case OT_SRP_SERVER_STATE_STOPPED:
        srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_STOPPED;
        break;
    default:
        srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_UNSPECIFIED;
    }
    return srpServerState;
}

ThreadnetworkTelemetryDataReported::SrpServerAddressMode SrpServerAddressModeFromOtSrpServerAddressMode(
    otSrpServerAddressMode aSrpServerAddressMode)
{
    ThreadnetworkTelemetryDataReported::SrpServerAddressMode srpServerAddressMode;

    switch (aSrpServerAddressMode)
    {
    case OT_SRP_SERVER_ADDRESS_MODE_ANYCAST:
        srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_STATE_ANYCAST;
        break;
    case OT_SRP_SERVER_ADDRESS_MODE_UNICAST:
        srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_UNICAST;
        break;
    default:
        srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_UNSPECIFIED;
    }
    return srpServerAddressMode;
}
#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY

void CopyMdnsResponseCounters(const MdnsResponseCounters                               &from,
                              ThreadnetworkTelemetryDataReported::MdnsResponseCounters *to)
{
    to->set_success_count(from.mSuccess);
    to->set_not_found_count(from.mNotFound);
    to->set_invalid_args_count(from.mInvalidArgs);
    to->set_duplicated_count(from.mDuplicated);
    to->set_not_implemented_count(from.mNotImplemented);
    to->set_unknown_error_count(from.mUnknownError);
    to->set_aborted_count(from.mAborted);
    to->set_invalid_state_count(from.mInvalidState);
}

TelemetryData::Nat64State Nat64StateFromOtNat64State(otNat64State aNat64State)
{
    TelemetryData::Nat64State nat64State;

    switch (aNat64State)
    {
    case OT_NAT64_STATE_DISABLED:
        nat64State = TelemetryData::NAT64_STATE_DISABLED;
        break;
    case OT_NAT64_STATE_NOT_RUNNING:
        nat64State = TelemetryData::NAT64_STATE_NOT_RUNNING;
        break;
    case OT_NAT64_STATE_IDLE:
        nat64State = TelemetryData::NAT64_STATE_IDLE;
        break;
    case OT_NAT64_STATE_ACTIVE:
        nat64State = TelemetryData::NAT64_STATE_ACTIVE;
        break;
    default:
        nat64State = TelemetryData::NAT64_STATE_UNSPECIFIED;
    }

    return nat64State;
}

void RetrieveNat64State(otInstance *aInstance, TelemetryData::WpanBorderRouter *aWpanBorderRouter)
{
    auto nat64State = aWpanBorderRouter->mutable_nat64_state();

    nat64State->set_prefix_manager_state(Nat64StateFromOtNat64State(otNat64GetPrefixManagerState(aInstance)));
    nat64State->set_translator_state(Nat64StateFromOtNat64State(otNat64GetTranslatorState(aInstance)));
}

void RetrieveNat64Counters(otInstance *aInstance, TelemetryData::BorderRoutingCounters *aBorderRoutingCounters)
{
    {
        auto nat64IcmpCounters = aBorderRoutingCounters->mutable_nat64_protocol_counters()->mutable_icmp();
        auto nat64UdpCounters  = aBorderRoutingCounters->mutable_nat64_protocol_counters()->mutable_udp();
        auto nat64TcpCounters  = aBorderRoutingCounters->mutable_nat64_protocol_counters()->mutable_tcp();
        otNat64ProtocolCounters otCounters;

        otNat64GetCounters(aInstance, &otCounters);
        nat64IcmpCounters->set_ipv4_to_ipv6_packets(otCounters.mIcmp.m4To6Packets);
        nat64IcmpCounters->set_ipv4_to_ipv6_bytes(otCounters.mIcmp.m4To6Bytes);
        nat64IcmpCounters->set_ipv6_to_ipv4_packets(otCounters.mIcmp.m6To4Packets);
        nat64IcmpCounters->set_ipv6_to_ipv4_bytes(otCounters.mIcmp.m6To4Bytes);
        nat64UdpCounters->set_ipv4_to_ipv6_packets(otCounters.mUdp.m4To6Packets);
        nat64UdpCounters->set_ipv4_to_ipv6_bytes(otCounters.mUdp.m4To6Bytes);
        nat64UdpCounters->set_ipv6_to_ipv4_packets(otCounters.mUdp.m6To4Packets);
        nat64UdpCounters->set_ipv6_to_ipv4_bytes(otCounters.mUdp.m6To4Bytes);
        nat64TcpCounters->set_ipv4_to_ipv6_packets(otCounters.mTcp.m4To6Packets);
        nat64TcpCounters->set_ipv4_to_ipv6_bytes(otCounters.mTcp.m4To6Bytes);
        nat64TcpCounters->set_ipv6_to_ipv4_packets(otCounters.mTcp.m6To4Packets);
        nat64TcpCounters->set_ipv6_to_ipv4_bytes(otCounters.mTcp.m6To4Bytes);
    }

    {
        auto                 errorCounters = aBorderRoutingCounters->mutable_nat64_error_counters();
        otNat64ErrorCounters otCounters;
        otNat64GetErrorCounters(aInstance, &otCounters);

        errorCounters->mutable_unknown()->set_ipv4_to_ipv6_packets(otCounters.mCount4To6[OT_NAT64_DROP_REASON_UNKNOWN]);
        errorCounters->mutable_unknown()->set_ipv6_to_ipv4_packets(otCounters.mCount6To4[OT_NAT64_DROP_REASON_UNKNOWN]);
        errorCounters->mutable_illegal_packet()->set_ipv4_to_ipv6_packets(
            otCounters.mCount4To6[OT_NAT64_DROP_REASON_ILLEGAL_PACKET]);
        errorCounters->mutable_illegal_packet()->set_ipv6_to_ipv4_packets(
            otCounters.mCount6To4[OT_NAT64_DROP_REASON_ILLEGAL_PACKET]);
        errorCounters->mutable_unsupported_protocol()->set_ipv4_to_ipv6_packets(
            otCounters.mCount4To6[OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO]);
        errorCounters->mutable_unsupported_protocol()->set_ipv6_to_ipv4_packets(
            otCounters.mCount6To4[OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO]);
        errorCounters->mutable_no_mapping()->set_ipv4_to_ipv6_packets(
            otCounters.mCount4To6[OT_NAT64_DROP_REASON_NO_MAPPING]);
        errorCounters->mutable_no_mapping()->set_ipv6_to_ipv4_packets(
            otCounters.mCount6To4[OT_NAT64_DROP_REASON_NO_MAPPING]);
    }
}

void RetrieveBorderAgentInfo(otInstance *aInstance, TelemetryData::BorderAgentInfo *aBorderAgentInfo)
{
    auto baCounters            = aBorderAgentInfo->mutable_border_agent_counters();
    auto otBorderAgentCounters = *otBorderAgentGetCounters(aInstance);

    baCounters->set_epskc_activations(otBorderAgentCounters.mEpskcActivations);
    baCounters->set_epskc_deactivation_clears(otBorderAgentCounters.mEpskcDeactivationClears);
    baCounters->set_epskc_deactivation_timeouts(otBorderAgentCounters.mEpskcDeactivationTimeouts);
    baCounters->set_epskc_deactivation_max_attempts(otBorderAgentCounters.mEpskcDeactivationMaxAttempts);
    baCounters->set_epskc_deactivation_disconnects(otBorderAgentCounters.mEpskcDeactivationDisconnects);
    baCounters->set_epskc_invalid_ba_state_errors(otBorderAgentCounters.mEpskcInvalidBaStateErrors);
    baCounters->set_epskc_invalid_args_errors(otBorderAgentCounters.mEpskcInvalidArgsErrors);
    baCounters->set_epskc_start_secure_session_errors(otBorderAgentCounters.mEpskcStartSecureSessionErrors);
    baCounters->set_epskc_secure_session_successes(otBorderAgentCounters.mEpskcSecureSessionSuccesses);
    baCounters->set_epskc_secure_session_failures(otBorderAgentCounters.mEpskcSecureSessionFailures);
    baCounters->set_epskc_commissioner_petitions(otBorderAgentCounters.mEpskcCommissionerPetitions);

    baCounters->set_pskc_secure_session_successes(otBorderAgentCounters.mPskcSecureSessionSuccesses);
    baCounters->set_pskc_secure_session_failures(otBorderAgentCounters.mPskcSecureSessionFailures);
    baCounters->set_pskc_commissioner_petitions(otBorderAgentCounters.mPskcCommissionerPetitions);

    baCounters->set_mgmt_active_get_reqs(otBorderAgentCounters.mMgmtActiveGets);
    baCounters->set_mgmt_pending_get_reqs(otBorderAgentCounters.mMgmtPendingGets);
}

void RetrieveTrelInfo(otInstance *aInstance, TelemetryData::TrelInfo *aTrelInfo)
{
    auto otTrelCounters = otTrelGetCounters(aInstance);
    auto trelCounters   = aTrelInfo->mutable_counters();

    aTrelInfo->set_is_trel_enabled(otTrelIsEnabled(aInstance));
    aTrelInfo->set_num_trel_peers(otTrelGetNumberOfPeers(aInstance));

    trelCounters->set_trel_tx_packets(otTrelCounters->mTxPackets);
    trelCounters->set_trel_tx_bytes(otTrelCounters->mTxBytes);
    trelCounters->set_trel_tx_packets_failed(otTrelCounters->mTxFailure);
    trelCounters->set_trel_rx_packets(otTrelCounters->mRxPackets);
    trelCounters->set_trel_rx_bytes(otTrelCounters->mRxBytes);
}

otError RetrieveTelemetryAtom(otInstance                         *otInstance,
                              Mdns::Publisher                    *aPublisher,
                              ThreadnetworkTelemetryDataReported &telemetryDataReported,
                              ThreadnetworkTopoEntryRepeated     &topoEntryRepeated,
                              ThreadnetworkDeviceInfoReported    &deviceInfoReported)
{
    otError                     error = OT_ERROR_NONE;
    std::vector<otNeighborInfo> neighborTable;

    // Begin of ThreadnetworkDeviceInfoReported section.
    deviceInfoReported.set_thread_version(otThreadGetVersion());
    deviceInfoReported.set_ot_rcp_version(otGetRadioVersionString(otInstance));
    // TODO: populate ot_host_version, thread_daemon_version.
    // End of ThreadnetworkDeviceInfoReported section.

    // Begin of WpanStats section.
    auto wpanStats = telemetryDataReported.mutable_wpan_stats();

    {
        otDeviceRole     role  = otThreadGetDeviceRole(otInstance);
        otLinkModeConfig otCfg = otThreadGetLinkMode(otInstance);

        wpanStats->set_node_type(TelemetryNodeTypeFromRoleAndLinkMode(role, otCfg));
    }

    // Disable telemetry retrieval when Thread stack is disabled. DeviceInfo section above is
    // always uploaded to understand the device count.
    if (wpanStats->node_type() == ThreadnetworkTelemetryDataReported::NODE_TYPE_DISABLED)
    {
        otbrLogDebug("Skip telemetry retrieval since Thread stack is disabled.");
        // Return error that only partial telemetries are populated.
        // TODO: refine the error code name to mean: partial data are populated.
        return OT_ERROR_FAILED;
    }

    wpanStats->set_channel(otLinkGetChannel(otInstance));

    {
        uint16_t ccaFailureRate = otLinkGetCcaFailureRate(otInstance);

        wpanStats->set_mac_cca_fail_rate(static_cast<float>(ccaFailureRate) / 0xffff);
    }

    {
        int8_t radioTxPower;

        if (otPlatRadioGetTransmitPower(otInstance, &radioTxPower) == OT_ERROR_NONE)
        {
            wpanStats->set_radio_tx_power(radioTxPower);
        }
        else
        {
            error = OT_ERROR_FAILED;
        }
    }

    {
        const otMacCounters *linkCounters = otLinkGetCounters(otInstance);

        wpanStats->set_phy_rx(linkCounters->mRxTotal);
        wpanStats->set_phy_tx(linkCounters->mTxTotal);
        wpanStats->set_mac_unicast_rx(linkCounters->mRxUnicast);
        wpanStats->set_mac_unicast_tx(linkCounters->mTxUnicast);
        wpanStats->set_mac_broadcast_rx(linkCounters->mRxBroadcast);
        wpanStats->set_mac_broadcast_tx(linkCounters->mTxBroadcast);
        wpanStats->set_mac_tx_ack_req(linkCounters->mTxAckRequested);
        wpanStats->set_mac_tx_no_ack_req(linkCounters->mTxNoAckRequested);
        wpanStats->set_mac_tx_acked(linkCounters->mTxAcked);
        wpanStats->set_mac_tx_data(linkCounters->mTxData);
        wpanStats->set_mac_tx_data_poll(linkCounters->mTxDataPoll);
        wpanStats->set_mac_tx_beacon(linkCounters->mTxBeacon);
        wpanStats->set_mac_tx_beacon_req(linkCounters->mTxBeaconRequest);
        wpanStats->set_mac_tx_other_pkt(linkCounters->mTxOther);
        wpanStats->set_mac_tx_retry(linkCounters->mTxRetry);
        wpanStats->set_mac_rx_data(linkCounters->mRxData);
        wpanStats->set_mac_rx_data_poll(linkCounters->mRxDataPoll);
        wpanStats->set_mac_rx_beacon(linkCounters->mRxBeacon);
        wpanStats->set_mac_rx_beacon_req(linkCounters->mRxBeaconRequest);
        wpanStats->set_mac_rx_other_pkt(linkCounters->mRxOther);
        wpanStats->set_mac_rx_filter_whitelist(linkCounters->mRxAddressFiltered);
        wpanStats->set_mac_rx_filter_dest_addr(linkCounters->mRxDestAddrFiltered);
        wpanStats->set_mac_tx_fail_cca(linkCounters->mTxErrCca);
        wpanStats->set_mac_rx_fail_decrypt(linkCounters->mRxErrSec);
        wpanStats->set_mac_rx_fail_no_frame(linkCounters->mRxErrNoFrame);
        wpanStats->set_mac_rx_fail_unknown_neighbor(linkCounters->mRxErrUnknownNeighbor);
        wpanStats->set_mac_rx_fail_invalid_src_addr(linkCounters->mRxErrInvalidSrcAddr);
        wpanStats->set_mac_rx_fail_fcs(linkCounters->mRxErrFcs);
        wpanStats->set_mac_rx_fail_other(linkCounters->mRxErrOther);
    }

    {
        const otIpCounters *ipCounters = otThreadGetIp6Counters(otInstance);

        wpanStats->set_ip_tx_success(ipCounters->mTxSuccess);
        wpanStats->set_ip_rx_success(ipCounters->mRxSuccess);
        wpanStats->set_ip_tx_failure(ipCounters->mTxFailure);
        wpanStats->set_ip_rx_failure(ipCounters->mRxFailure);
    }
    // End of WpanStats section.

    {
        // Begin of WpanTopoFull section.
        auto     wpanTopoFull = telemetryDataReported.mutable_wpan_topo_full();
        uint16_t rloc16       = otThreadGetRloc16(otInstance);

        wpanTopoFull->set_rloc16(rloc16);

        {
            otRouterInfo info;

            if (otThreadGetRouterInfo(otInstance, rloc16, &info) == OT_ERROR_NONE)
            {
                wpanTopoFull->set_router_id(info.mRouterId);
            }
            else
            {
                error = OT_ERROR_FAILED;
            }
        }

        {
            otNeighborInfoIterator iter = OT_NEIGHBOR_INFO_ITERATOR_INIT;
            otNeighborInfo         neighborInfo;

            while (otThreadGetNextNeighborInfo(otInstance, &iter, &neighborInfo) == OT_ERROR_NONE)
            {
                neighborTable.push_back(neighborInfo);
            }
        }
        wpanTopoFull->set_neighbor_table_size(neighborTable.size());

        uint16_t                 childIndex = 0;
        otChildInfo              childInfo;
        std::vector<otChildInfo> childTable;

        while (otThreadGetChildInfoByIndex(otInstance, childIndex, &childInfo) == OT_ERROR_NONE)
        {
            childTable.push_back(childInfo);
            childIndex++;
        }
        wpanTopoFull->set_child_table_size(childTable.size());

        {
            struct otLeaderData leaderData;

            if (otThreadGetLeaderData(otInstance, &leaderData) == OT_ERROR_NONE)
            {
                wpanTopoFull->set_leader_router_id(leaderData.mLeaderRouterId);
                wpanTopoFull->set_leader_weight(leaderData.mWeighting);
                // Do not log network_data_version.
            }
            else
            {
                error = OT_ERROR_FAILED;
            }
        }

        uint8_t weight = otThreadGetLocalLeaderWeight(otInstance);

        wpanTopoFull->set_leader_local_weight(weight);

        int8_t rssi = otPlatRadioGetRssi(otInstance);

        wpanTopoFull->set_instant_rssi(rssi);

        const otExtendedPanId *extPanId = otThreadGetExtendedPanId(otInstance);
        uint64_t               extPanIdVal;

        extPanIdVal = ConvertOpenThreadUint64(extPanId->m8);
        wpanTopoFull->set_has_extended_pan_id(extPanIdVal != 0);
        // Note: Used leader_router_id instead of leader_rloc16.
        // Note: Network level info (e.g., extended_pan_id, partition_id, is_active_br) is not logged.
        // TODO: populate is_active_srp_server, sum_on_link_prefix_changes, preferred_router_id
        // if needed.
        // End of WpanTopoFull section.

        // Begin of TopoEntry section.
        std::map<uint16_t, const otChildInfo *> childMap;

        for (const otChildInfo &childInfo : childTable)
        {
            auto pair = childMap.insert({childInfo.mRloc16, &childInfo});
            if (!pair.second)
            {
                // This shouldn't happen, so log an error. It doesn't matter which
                // duplicate is kept.
                otbrLogErr("Children with duplicate RLOC16 found: 0x%04x", static_cast<int>(childInfo.mRloc16));
            }
        }

        for (const otNeighborInfo &neighborInfo : neighborTable)
        {
            auto topoEntry = topoEntryRepeated.mutable_topo_entry_repeated()->add_topo_entries();

            // 0~15: uint16_t rloc_16
            // 16~31: uint16_t version Thread version of the neighbor
            uint32_t comboTelemetry1 = 0;
            comboTelemetry1 |= (((uint32_t)neighborInfo.mRloc16) & 0x0000FFFF);
            comboTelemetry1 |= ((((uint32_t)neighborInfo.mVersion) & 0x0000FFFF) << 16);
            topoEntry->set_combo_telemetry1(comboTelemetry1);

            topoEntry->set_age_sec(neighborInfo.mAge);

            // 0~7: uint8_t link_quality_in
            // 8~15: int8_t average_rssi
            // 16~23: int8_t last_rssi
            // 24~31: uint8_t network_data_version
            uint32_t comboTelemetry2 = 0;
            comboTelemetry2 |= (((uint32_t)neighborInfo.mLinkQualityIn) & 0x000000FF);
            comboTelemetry2 |= ((((uint32_t)neighborInfo.mAverageRssi) & 0x000000FF) << 8);
            comboTelemetry2 |= ((((uint32_t)neighborInfo.mLastRssi) & 0x000000FF) << 16);
            // network_data_version is populated in the next section.
            topoEntry->set_combo_telemetry2(comboTelemetry2);

            // Each bit on the flag represents a bool flag
            // 0: rx_on_when_idle
            // 1: full_function
            // 2: secure_data_request
            // 3: full_network_data
            // 4: is_child
            uint32_t topoEntryFlags = 0;
            topoEntryFlags |= (neighborInfo.mRxOnWhenIdle ? 1 : 0);
            topoEntryFlags |= ((neighborInfo.mFullThreadDevice ? 1 : 0) << 1);
            topoEntryFlags |= ((/* secure_data_request */ true ? 1 : 0) << 2);
            topoEntryFlags |= ((neighborInfo.mFullNetworkData ? 1 : 0) << 3);
            topoEntry->set_topo_entry_flags(topoEntryFlags);

            topoEntry->set_link_frame_counter(neighborInfo.mLinkFrameCounter);
            topoEntry->set_mle_frame_counter(neighborInfo.mMleFrameCounter);

            // 0~15: uint16_t mac_frame_error_rate. Frame error rate (0xffff->100%). Requires error tracking feature.
            // 16~31: uint16_t ip_message_error_rate. (IPv6) msg error rate (0xffff->100%). Requires error tracking
            // feature.
            uint32_t comboTelemetry3 = 0;
            comboTelemetry3 |= ((uint32_t)(neighborInfo.mFrameErrorRate) & 0x0000FFFF);
            comboTelemetry3 |= ((((uint32_t)neighborInfo.mMessageErrorRate) & 0x0000FFFF) << 16);
            topoEntry->set_combo_telemetry3(comboTelemetry3);

            if (!neighborInfo.mIsChild)
            {
                continue;
            }

            auto it = childMap.find(neighborInfo.mRloc16);
            if (it == childMap.end())
            {
                otbrLogErr("Neighbor 0x%04x not found in child table", static_cast<int>(neighborInfo.mRloc16));
                continue;
            }
            const otChildInfo *childInfo = it->second;

            comboTelemetry2 |= ((((uint32_t)childInfo->mNetworkDataVersion) & 0x000000FF) << 24);
            topoEntry->set_combo_telemetry2(comboTelemetry2);

            topoEntryFlags |= ((/* is_child */ true ? 1 : 0) << 4);
            topoEntry->set_topo_entry_flags(topoEntryFlags);

            topoEntry->set_timeout_sec(childInfo->mTimeout);
        }
        // End of TopoEntry section.
    }

    {
        // Begin of WpanBorderRouter section.
        auto wpanBorderRouter = telemetryDataReported.mutable_wpan_border_router();

        // Begin of BorderRoutingCounters section.
        {
            auto                           borderRoutingCouters = wpanBorderRouter->mutable_border_routing_counters();
            const otBorderRoutingCounters *otBorderRoutingCounters = otIp6GetBorderRoutingCounters(otInstance);

            borderRoutingCouters->mutable_inbound_unicast()->set_packet_count(
                otBorderRoutingCounters->mInboundUnicast.mPackets);
            borderRoutingCouters->mutable_inbound_unicast()->set_byte_count(
                otBorderRoutingCounters->mInboundUnicast.mBytes);
            borderRoutingCouters->mutable_inbound_multicast()->set_packet_count(
                otBorderRoutingCounters->mInboundMulticast.mPackets);
            borderRoutingCouters->mutable_inbound_multicast()->set_byte_count(
                otBorderRoutingCounters->mInboundMulticast.mBytes);
            borderRoutingCouters->mutable_outbound_unicast()->set_packet_count(
                otBorderRoutingCounters->mOutboundUnicast.mPackets);
            borderRoutingCouters->mutable_outbound_unicast()->set_byte_count(
                otBorderRoutingCounters->mOutboundUnicast.mBytes);
            borderRoutingCouters->mutable_outbound_multicast()->set_packet_count(
                otBorderRoutingCounters->mOutboundMulticast.mPackets);
            borderRoutingCouters->mutable_outbound_multicast()->set_byte_count(
                otBorderRoutingCounters->mOutboundMulticast.mBytes);
            borderRoutingCouters->set_ra_rx(otBorderRoutingCounters->mRaRx);
            borderRoutingCouters->set_ra_tx_success(otBorderRoutingCounters->mRaTxSuccess);
            borderRoutingCouters->set_ra_tx_failure(otBorderRoutingCounters->mRaTxFailure);
            borderRoutingCouters->set_rs_rx(otBorderRoutingCounters->mRsRx);
            borderRoutingCouters->set_rs_tx_success(otBorderRoutingCounters->mRsTxSuccess);
            borderRoutingCouters->set_rs_tx_failure(otBorderRoutingCounters->mRsTxFailure);

            RetrieveNat64Counters(otInstance, borderRoutingCouters);
        }

        // End of BorderRoutingCounters section.

#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
        // Begin of SrpServerInfo section.
        {
            auto                               srpServer = wpanBorderRouter->mutable_srp_server();
            otSrpServerLeaseInfo               leaseInfo;
            const otSrpServerHost             *host             = nullptr;
            const otSrpServerResponseCounters *responseCounters = otSrpServerGetResponseCounters(otInstance);

            srpServer->set_state(SrpServerStateFromOtSrpServerState(otSrpServerGetState(otInstance)));
            srpServer->set_port(otSrpServerGetPort(otInstance));
            srpServer->set_address_mode(
                SrpServerAddressModeFromOtSrpServerAddressMode(otSrpServerGetAddressMode(otInstance)));

            auto srpServerHosts            = srpServer->mutable_hosts();
            auto srpServerServices         = srpServer->mutable_services();
            auto srpServerResponseCounters = srpServer->mutable_response_counters();

            while ((host = otSrpServerGetNextHost(otInstance, host)))
            {
                const otSrpServerService *service = nullptr;

                if (otSrpServerHostIsDeleted(host))
                {
                    srpServerHosts->set_deleted_count(srpServerHosts->deleted_count() + 1);
                }
                else
                {
                    srpServerHosts->set_fresh_count(srpServerHosts->fresh_count() + 1);
                    otSrpServerHostGetLeaseInfo(host, &leaseInfo);
                    srpServerHosts->set_lease_time_total_ms(srpServerHosts->lease_time_total_ms() + leaseInfo.mLease);
                    srpServerHosts->set_key_lease_time_total_ms(srpServerHosts->key_lease_time_total_ms() +
                                                                leaseInfo.mKeyLease);
                    srpServerHosts->set_remaining_lease_time_total_ms(srpServerHosts->remaining_lease_time_total_ms() +
                                                                      leaseInfo.mRemainingLease);
                    srpServerHosts->set_remaining_key_lease_time_total_ms(
                        srpServerHosts->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
                }

                while ((service = otSrpServerHostGetNextService(host, service)))
                {
                    if (otSrpServerServiceIsDeleted(service))
                    {
                        srpServerServices->set_deleted_count(srpServerServices->deleted_count() + 1);
                    }
                    else
                    {
                        srpServerServices->set_fresh_count(srpServerServices->fresh_count() + 1);
                        otSrpServerServiceGetLeaseInfo(service, &leaseInfo);
                        srpServerServices->set_lease_time_total_ms(srpServerServices->lease_time_total_ms() +
                                                                   leaseInfo.mLease);
                        srpServerServices->set_key_lease_time_total_ms(srpServerServices->key_lease_time_total_ms() +
                                                                       leaseInfo.mKeyLease);
                        srpServerServices->set_remaining_lease_time_total_ms(
                            srpServerServices->remaining_lease_time_total_ms() + leaseInfo.mRemainingLease);
                        srpServerServices->set_remaining_key_lease_time_total_ms(
                            srpServerServices->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
                    }
                }
            }

            srpServerResponseCounters->set_success_count(responseCounters->mSuccess);
            srpServerResponseCounters->set_server_failure_count(responseCounters->mServerFailure);
            srpServerResponseCounters->set_format_error_count(responseCounters->mFormatError);
            srpServerResponseCounters->set_name_exists_count(responseCounters->mNameExists);
            srpServerResponseCounters->set_refused_count(responseCounters->mRefused);
            srpServerResponseCounters->set_other_count(responseCounters->mOther);
        }
        // End of SrpServerInfo section.
#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY

#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
        // Begin of DnsServerInfo section.
        {
            auto            dnsServer                 = wpanBorderRouter->mutable_dns_server();
            auto            dnsServerResponseCounters = dnsServer->mutable_response_counters();
            otDnssdCounters otDnssdCounters           = *otDnssdGetCounters(otInstance);

            dnsServerResponseCounters->set_success_count(otDnssdCounters.mSuccessResponse);
            dnsServerResponseCounters->set_server_failure_count(otDnssdCounters.mServerFailureResponse);
            dnsServerResponseCounters->set_format_error_count(otDnssdCounters.mFormatErrorResponse);
            dnsServerResponseCounters->set_name_error_count(otDnssdCounters.mNameErrorResponse);
            dnsServerResponseCounters->set_not_implemented_count(otDnssdCounters.mNotImplementedResponse);
            dnsServerResponseCounters->set_other_count(otDnssdCounters.mOtherResponse);
            // The counters of queries, responses, failures handled by upstream DNS server.
            dnsServerResponseCounters->set_upstream_dns_queries(otDnssdCounters.mUpstreamDnsCounters.mQueries);
            dnsServerResponseCounters->set_upstream_dns_responses(otDnssdCounters.mUpstreamDnsCounters.mResponses);
            dnsServerResponseCounters->set_upstream_dns_failures(otDnssdCounters.mUpstreamDnsCounters.mFailures);

            dnsServer->set_resolved_by_local_srp_count(otDnssdCounters.mResolvedBySrp);

            dnsServer->set_upstream_dns_query_state(
                otDnssdUpstreamQueryIsEnabled(otInstance)
                    ? ThreadnetworkTelemetryDataReported::UPSTREAMDNS_QUERY_STATE_ENABLED
                    : ThreadnetworkTelemetryDataReported::UPSTREAMDNS_QUERY_STATE_DISABLED);
        }
        // End of DnsServerInfo section.
#endif // OTBR_ENABLE_DNSSD_DISCOVERY_PROXY

        // Start of MdnsInfo section.
        if (aPublisher != nullptr)
        {
            auto                     mdns     = wpanBorderRouter->mutable_mdns();
            const MdnsTelemetryInfo &mdnsInfo = aPublisher->GetMdnsTelemetryInfo();

            CopyMdnsResponseCounters(mdnsInfo.mHostRegistrations, mdns->mutable_host_registration_responses());
            CopyMdnsResponseCounters(mdnsInfo.mServiceRegistrations, mdns->mutable_service_registration_responses());
            CopyMdnsResponseCounters(mdnsInfo.mHostResolutions, mdns->mutable_host_resolution_responses());
            CopyMdnsResponseCounters(mdnsInfo.mServiceResolutions, mdns->mutable_service_resolution_responses());

            mdns->set_host_registration_ema_latency_ms(mdnsInfo.mHostRegistrationEmaLatency);
            mdns->set_service_registration_ema_latency_ms(mdnsInfo.mServiceRegistrationEmaLatency);
            mdns->set_host_resolution_ema_latency_ms(mdnsInfo.mHostResolutionEmaLatency);
            mdns->set_service_resolution_ema_latency_ms(mdnsInfo.mServiceResolutionEmaLatency);
        }
        // End of MdnsInfo section.

        // End of WpanBorderRouter section.

        // Start of WpanRcp section.
        {
            auto                        wpanRcp                = telemetryDataReported.mutable_wpan_rcp();
            const otRadioSpinelMetrics *otRadioSpinelMetrics   = otSysGetRadioSpinelMetrics();
            auto                        rcpStabilityStatistics = wpanRcp->mutable_rcp_stability_statistics();

            if (otRadioSpinelMetrics != nullptr)
            {
                rcpStabilityStatistics->set_rcp_timeout_count(otRadioSpinelMetrics->mRcpTimeoutCount);
                rcpStabilityStatistics->set_rcp_reset_count(otRadioSpinelMetrics->mRcpUnexpectedResetCount);
                rcpStabilityStatistics->set_rcp_restoration_count(otRadioSpinelMetrics->mRcpRestorationCount);
                rcpStabilityStatistics->set_spinel_parse_error_count(otRadioSpinelMetrics->mSpinelParseErrorCount);
            }

            // TODO: provide rcp_firmware_update_count info.
            rcpStabilityStatistics->set_thread_stack_uptime(otInstanceGetUptime(otInstance));

            const otRcpInterfaceMetrics *otRcpInterfaceMetrics = otSysGetRcpInterfaceMetrics();

            if (otRcpInterfaceMetrics != nullptr)
            {
                auto rcpInterfaceStatistics = wpanRcp->mutable_rcp_interface_statistics();

                rcpInterfaceStatistics->set_rcp_interface_type(otRcpInterfaceMetrics->mRcpInterfaceType);
                rcpInterfaceStatistics->set_transferred_frames_count(otRcpInterfaceMetrics->mTransferredFrameCount);
                rcpInterfaceStatistics->set_transferred_valid_frames_count(
                    otRcpInterfaceMetrics->mTransferredValidFrameCount);
                rcpInterfaceStatistics->set_transferred_garbage_frames_count(
                    otRcpInterfaceMetrics->mTransferredGarbageFrameCount);
                rcpInterfaceStatistics->set_rx_frames_count(otRcpInterfaceMetrics->mRxFrameCount);
                rcpInterfaceStatistics->set_rx_bytes_count(otRcpInterfaceMetrics->mRxFrameByteCount);
                rcpInterfaceStatistics->set_tx_frames_count(otRcpInterfaceMetrics->mTxFrameCount);
                rcpInterfaceStatistics->set_tx_bytes_count(otRcpInterfaceMetrics->mTxFrameByteCount);
            }
        }
        // End of WpanRcp section.

        // Start of CoexMetrics section.
        {
            auto               coexMetrics = telemetryDataReported.mutable_coex_metrics();
            otRadioCoexMetrics otRadioCoexMetrics;

            if (otPlatRadioGetCoexMetrics(otInstance, &otRadioCoexMetrics) == OT_ERROR_NONE)
            {
                coexMetrics->set_count_tx_request(otRadioCoexMetrics.mNumTxRequest);
                coexMetrics->set_count_tx_grant_immediate(otRadioCoexMetrics.mNumTxGrantImmediate);
                coexMetrics->set_count_tx_grant_wait(otRadioCoexMetrics.mNumTxGrantWait);
                coexMetrics->set_count_tx_grant_wait_activated(otRadioCoexMetrics.mNumTxGrantWaitActivated);
                coexMetrics->set_count_tx_grant_wait_timeout(otRadioCoexMetrics.mNumTxGrantWaitTimeout);
                coexMetrics->set_count_tx_grant_deactivated_during_request(
                    otRadioCoexMetrics.mNumTxGrantDeactivatedDuringRequest);
                coexMetrics->set_tx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgTxRequestToGrantTime);
                coexMetrics->set_count_rx_request(otRadioCoexMetrics.mNumRxRequest);
                coexMetrics->set_count_rx_grant_immediate(otRadioCoexMetrics.mNumRxGrantImmediate);
                coexMetrics->set_count_rx_grant_wait(otRadioCoexMetrics.mNumRxGrantWait);
                coexMetrics->set_count_rx_grant_wait_activated(otRadioCoexMetrics.mNumRxGrantWaitActivated);
                coexMetrics->set_count_rx_grant_wait_timeout(otRadioCoexMetrics.mNumRxGrantWaitTimeout);
                coexMetrics->set_count_rx_grant_deactivated_during_request(
                    otRadioCoexMetrics.mNumRxGrantDeactivatedDuringRequest);
                coexMetrics->set_count_rx_grant_none(otRadioCoexMetrics.mNumRxGrantNone);
                coexMetrics->set_rx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgRxRequestToGrantTime);
            }
            else
            {
                error = OT_ERROR_FAILED;
            }
        }
        // End of CoexMetrics section.

        RetrieveNat64State(otInstance, wpanBorderRouter);
        RetrieveBorderAgentInfo(otInstance, wpanBorderRouter->mutable_border_agent_info());
        RetrieveTrelInfo(otInstance, wpanBorderRouter->mutable_trel_info());
    }

    return error;
}

int PushAtom(const ThreadnetworkTelemetryDataReported &telemetryDataReported)
{
    const std::string        &wpanStats        = telemetryDataReported.wpan_stats().SerializeAsString();
    const std::string        &wpanTopoFull     = telemetryDataReported.wpan_topo_full().SerializeAsString();
    const std::string        &wpanBorderRouter = telemetryDataReported.wpan_border_router().SerializeAsString();
    const std::string        &wpanRcp          = telemetryDataReported.wpan_rcp().SerializeAsString();
    const std::string        &coexMetrics      = telemetryDataReported.coex_metrics().SerializeAsString();
    threadnetwork::BytesField wpanStatsBytesField{wpanStats.c_str(), wpanStats.size()};
    threadnetwork::BytesField wpanTopoFullBytesField{wpanTopoFull.c_str(), wpanTopoFull.size()};
    threadnetwork::BytesField wpanBorderRouterBytesField{wpanBorderRouter.c_str(), wpanBorderRouter.size()};
    threadnetwork::BytesField wpanRcpBytesField{wpanRcp.c_str(), wpanRcp.size()};
    threadnetwork::BytesField coexMetricsBytesField{coexMetrics.c_str(), coexMetrics.size()};
    return threadnetwork::stats_write(threadnetwork::THREADNETWORK_TELEMETRY_DATA_REPORTED, wpanStatsBytesField,
                                      wpanTopoFullBytesField, wpanBorderRouterBytesField, wpanRcpBytesField,
                                      coexMetricsBytesField);
}

int PushAtom(const ThreadnetworkTopoEntryRepeated &topoEntryRepeated)
{
    const std::string        &topoEntryField = topoEntryRepeated.topo_entry_repeated().SerializeAsString();
    threadnetwork::BytesField topoEntryFieldBytesField{topoEntryField.c_str(), topoEntryField.size()};
    return threadnetwork::stats_write(threadnetwork::THREADNETWORK_TOPO_ENTRY_REPEATED, topoEntryFieldBytesField);
}

int PushAtom(const ThreadnetworkDeviceInfoReported &deviceInfoReported)
{
    const std::string &otHostVersion       = deviceInfoReported.ot_host_version();
    const std::string &otRcpVersion        = deviceInfoReported.ot_rcp_version();
    const int32_t     &threadVersion       = deviceInfoReported.thread_version();
    const std::string &threadDaemonVersion = deviceInfoReported.thread_daemon_version();
    return threadnetwork::stats_write(threadnetwork::THREADNETWORK_DEVICE_INFO_REPORTED, otHostVersion.c_str(),
                                      otRcpVersion.c_str(), threadVersion, threadDaemonVersion.c_str());
}

void RetrieveAndPushAtoms(otInstance *otInstance)
{
    ThreadnetworkTelemetryDataReported telemetryDataReported;
    ThreadnetworkTopoEntryRepeated     topoEntryRepeated;
    ThreadnetworkDeviceInfoReported    deviceInfoReported;

    otbrLogInfo("Try to push threadnetwork ATOMs.");
    if (RetrieveTelemetryAtom(otInstance, nullptr, telemetryDataReported, topoEntryRepeated, deviceInfoReported) !=
        OTBR_ERROR_NONE)
    {
        otbrLogWarning("Some telemetries are not populated");
    }
    if (PushAtom(telemetryDataReported) <= 0)
    {
        otbrLogWarning("Failed to push ThreadnetworkTelemetryDataReported");
    }
    if (PushAtom(topoEntryRepeated) <= 0)
    {
        otbrLogWarning("Failed to push ThreadnetworkTopoEntryRepeated");
    }
    if (PushAtom(deviceInfoReported) <= 0)
    {
        otbrLogWarning("Failed to push ThreadnetworkDeviceInfoReported");
    }
    otbrLogInfo("Pushed threadnetwork ATOMs.");
}
} // namespace Android
} // namespace otbr
