/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2016 Apple Inc. 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.
 */

#import "Metrics.h"

#if (TARGET_OS_EMBEDDED)
#import <CoreUtils/SoftLinking.h>
#import <WirelessDiagnostics/AWDDNSDomainStats.h>
#import <WirelessDiagnostics/AWDMDNSResponderDNSStatistics.h>
#import <WirelessDiagnostics/AWDMDNSResponderResolveStats.h>
#import <WirelessDiagnostics/AWDMDNSResponderResolveStatsDNSServer.h>
#import <WirelessDiagnostics/AWDMDNSResponderResolveStatsDomain.h>
#import <WirelessDiagnostics/AWDMDNSResponderResolveStatsHostname.h>
#import <WirelessDiagnostics/AWDMDNSResponderResolveStatsResult.h>
#import <WirelessDiagnostics/AWDMetricIds_MDNSResponder.h>
#import <WirelessDiagnostics/WirelessDiagnostics.h>
#import <WirelessDiagnostics/AWDMDNSResponderServicesStats.h>

#import "DNSCommon.h"
#import "mDNSMacOSX.h"
#import "DebugServices.h"

//===========================================================================================================================
//  External Frameworks
//===========================================================================================================================

SOFT_LINK_FRAMEWORK(PrivateFrameworks, WirelessDiagnostics)

SOFT_LINK_CLASS(WirelessDiagnostics, AWDDNSDomainStats)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderDNSStatistics)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderResolveStats)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderResolveStatsDNSServer)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderResolveStatsDomain)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderResolveStatsHostname)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderResolveStatsResult)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDServerConnection)
SOFT_LINK_CLASS(WirelessDiagnostics, AWDMetricManager)

#define AWDDNSDomainStatsSoft                           getAWDDNSDomainStatsClass()
#define AWDMDNSResponderDNSStatisticsSoft               getAWDMDNSResponderDNSStatisticsClass()
#define AWDMDNSResponderResolveStatsSoft                getAWDMDNSResponderResolveStatsClass()
#define AWDMDNSResponderResolveStatsResultSoft          getAWDMDNSResponderResolveStatsResultClass()
#define AWDMDNSResponderResolveStatsDNSServerSoft       getAWDMDNSResponderResolveStatsDNSServerClass()
#define AWDMDNSResponderResolveStatsDomainSoft          getAWDMDNSResponderResolveStatsDomainClass()
#define AWDMDNSResponderResolveStatsHostnameSoft        getAWDMDNSResponderResolveStatsHostnameClass()
#define AWDServerConnectionSoft                         getAWDServerConnectionClass()
#define AWDMetricManagerSoft                            getAWDMetricManagerClass()

//===========================================================================================================================
//  Macros
//===========================================================================================================================

#define countof(X)                      (sizeof(X) / sizeof(X[0]))
#define countof_field(TYPE, FIELD)      countof(((TYPE *)0)->FIELD)
#define increment_saturate(VAR, MAX)    do {if ((VAR) < (MAX)) {++(VAR);}} while (0)
#define ForgetMem(X)                    do {if(*(X)) {free(*(X)); *(X) = NULL;}} while(0)

//===========================================================================================================================
//  Constants
//===========================================================================================================================

#define kQueryStatsMaxQuerySendCount    10
#define kQueryStatsSendCountBinCount    (kQueryStatsMaxQuerySendCount + 1)
#define kQueryStatsLatencyBinCount      55
#define kResolveStatsMaxObjCount        2000

//===========================================================================================================================
//  Data structures
//===========================================================================================================================

typedef struct
{
    const char *            cstr;       // Name of domain as a c-string.
    const domainname *      name;       // Name of domain as length-prefixed labels.
    int                     labelCount; // Number of labels in domain name. Used for domain name comparisons.

}   Domain;

// Important: Do not add to this list without getting privacy approval beforehand. See <rdar://problem/24155761&26397203>.
// If you get approval and do add a domain to this list, make sure it passes ValidateDNSStatsDomains() below.

static const Domain     kQueryStatsDomains[] =
{
    { ".",              (domainname *)"",                            0 },
    { "apple.com.",     (domainname *)"\x5" "apple"     "\x3" "com", 2 },
    { "icloud.com.",    (domainname *)"\x6" "icloud"    "\x3" "com", 2 },
    { "mzstatic.com.",  (domainname *)"\x8" "mzstatic"  "\x3" "com", 2 },
    { "google.com.",    (domainname *)"\x6" "google"    "\x3" "com", 2 },
    { "facebook.com.",  (domainname *)"\x8" "facebook"  "\x3" "com", 2 },
    { "baidu.com.",     (domainname *)"\x5" "baidu"     "\x3" "com", 2 },
    { "yahoo.com.",     (domainname *)"\x5" "yahoo"     "\x3" "com", 2 },
    { "qq.com.",        (domainname *)"\x2" "qq"        "\x3" "com", 2 },
};

check_compile_time(countof(kQueryStatsDomains) == 9);

// DNSHist contains the per domain per network type histogram data that goes in a DNSDomainStats protobuf message. See
// <rdar://problem/23980546> MDNSResponder.proto update.
//
// answeredQuerySendCountBins
//
// An array of 11 histogram bins. The value at index i, for 0 <= i <= 9, is the number of times that an answered DNS query
// was sent i times. The value at index 10 is the number of times that an answered query was sent 10+ times.
//
// unansweredQuerySendCountBins
//
// An array of 11 histogram bins. The value at index i, for 0 <= i <= 9, is the number of times that an unanswered DNS query
// was sent i times. The value at index 10 is the number of times that an unanswered query was sent 10+ times.
//
// responseLatencyBins
//
// An array of 55 histogram bins. Each array value is the number of DNS queries that were answered in a paricular time
// interval. The 55 consecutive non-overlapping time intervals have the following non-inclusive upper bounds (all values are
// in milliseconds): 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190,
// 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1500, 2000, 2500, 3000, 3500, 4000,
// 4500, 5000, 6000, 7000, 8000, 9000, 10000, ∞.

typedef struct
{
    uint16_t    unansweredQuerySendCountBins[kQueryStatsSendCountBinCount];
    uint16_t    unansweredQueryDurationBins[kQueryStatsLatencyBinCount];
    uint16_t    answeredQuerySendCountBins[kQueryStatsSendCountBinCount];
    uint16_t    responseLatencyBins[kQueryStatsLatencyBinCount];
    uint16_t    negAnsweredQuerySendCountBins[kQueryStatsSendCountBinCount];
    uint16_t    negResponseLatencyBins[kQueryStatsLatencyBinCount];

}   DNSHist;

check_compile_time(sizeof(DNSHist) <= 512);
check_compile_time(countof_field(DNSHist, unansweredQuerySendCountBins)  == (kQueryStatsMaxQuerySendCount + 1));
check_compile_time(countof_field(DNSHist, answeredQuerySendCountBins)    == (kQueryStatsMaxQuerySendCount + 1));
check_compile_time(countof_field(DNSHist, negAnsweredQuerySendCountBins) == (kQueryStatsMaxQuerySendCount + 1));

// Important: Do not modify kResponseLatencyMsLimits because the code used to generate AWD reports expects the response
// latency histogram bins to observe these time interval upper bounds.

static const mDNSu32        kResponseLatencyMsLimits[] =
{
        1,     2,     3,     4,     5,
       10,    20,    30,    40,    50,    60,    70,    80,    90,
      100,   110,   120,   130,   140,   150,   160,   170,   180,   190,
      200,   250,   300,   350,   400,   450,   500,   550,   600,   650,   700,   750,   800,   850,   900,   950,
     1000,  1500,  2000,  2500,  3000,  3500,  4000,  4500,
     5000,  6000,  7000,  8000,  9000,
    10000
};

check_compile_time(countof(kResponseLatencyMsLimits) == 54);
check_compile_time(countof_field(DNSHist, unansweredQueryDurationBins) == (countof(kResponseLatencyMsLimits) + 1));
check_compile_time(countof_field(DNSHist, responseLatencyBins)         == (countof(kResponseLatencyMsLimits) + 1));
check_compile_time(countof_field(DNSHist, negResponseLatencyBins)      == (countof(kResponseLatencyMsLimits) + 1));

typedef struct
{
    DNSHist *       histA;      // Histogram data for queries for A resource records.
    DNSHist *       histAAAA;   // Histogram data for queries for AAAA resource records.

}   DNSHistSet;

typedef struct DNSDomainStats       DNSDomainStats;
struct DNSDomainStats
{
    DNSDomainStats *        next;           // Pointer to next domain stats in list.
    const Domain *          domain;         // Domain for which these stats are collected.
    DNSHistSet *            nonCellular;    // Query stats for queries sent over non-cellular interfaces.
    DNSHistSet *            cellular;       // Query stats for queries sent over cellular interfaces.
};

check_compile_time(sizeof(struct DNSDomainStats) <= 32);

static const Domain     kResolveStatsDomains[] =
{
    { "apple.com.",     (domainname *)"\x5" "apple"    "\x3" "com", 2 },
    { "icloud.com.",    (domainname *)"\x6" "icloud"   "\x3" "com", 2 },
    { "mzstatic.com.",  (domainname *)"\x8" "mzstatic" "\x3" "com", 2 },
    { "me.com.",        (domainname *)"\x2" "me"       "\x3" "com", 2 },
};

check_compile_time(countof(kResolveStatsDomains) == 4);

typedef struct ResolveStatsDomain           ResolveStatsDomain;
typedef struct ResolveStatsHostname         ResolveStatsHostname;
typedef struct ResolveStatsDNSServer        ResolveStatsDNSServer;
typedef struct ResolveStatsIPv4AddrSet      ResolveStatsIPv4AddrSet;
typedef struct ResolveStatsIPv6Addr         ResolveStatsIPv6Addr;
typedef struct ResolveStatsNegAAAASet       ResolveStatsNegAAAASet;

struct ResolveStatsDomain
{
    ResolveStatsDomain *        next;           // Next domain object in list.
    ResolveStatsHostname *      hostnameList;   // List of hostname objects in this domain.
    const Domain *              domainInfo;     // Pointer to domain info.
};

struct ResolveStatsHostname
{
    ResolveStatsHostname *          next;       // Next hostname object in list.
    ResolveStatsIPv4AddrSet *       addrV4List; // List of IPv4 addresses to which this hostname resolved.
    ResolveStatsIPv6Addr *          addrV6List; // List of IPv6 addresses to which this hostname resolved.
    ResolveStatsNegAAAASet *        negV6List;  // List of negative AAAA response objects.
    uint8_t                         name[1];    // Variable length storage for hostname as length-prefixed labels.
};

check_compile_time(sizeof(ResolveStatsHostname) <= 64);

struct ResolveStatsDNSServer
{
    ResolveStatsDNSServer *     next;           // Next DNS server object in list.
    uint8_t                     id;             // 8-bit ID assigned to this DNS server used by IP address objects.
    mDNSBool                    isForCell;      // True if this DNS server belongs to a cellular interface.
    mDNSBool                    isAddrV6;       // True if this DNS server has an IPv6 address instead of IPv4.
    uint8_t                     addrBytes[1];   // Variable length storage for DNS server's IP address.
};

check_compile_time(sizeof(ResolveStatsDNSServer) <= 32);

typedef struct
{
    uint16_t        count;          // Number of times this IPv4 address was provided as a resolution result.
    uint8_t         serverID;       // 8-bit ID of the DNS server from which this IPv4 address came.
    uint8_t         isNegative;
    uint8_t         addrBytes[4];   // IPv4 address bytes.

}   IPv4AddrCounter;

check_compile_time(sizeof(IPv4AddrCounter) <= 8);

struct ResolveStatsIPv4AddrSet
{
    ResolveStatsIPv4AddrSet *       next;           // Next set of IPv4 address counters in list.
    IPv4AddrCounter                 counters[3];    // Array of IPv4 address counters.
};

check_compile_time(sizeof(ResolveStatsIPv4AddrSet) <= 32);

struct ResolveStatsIPv6Addr
{
    ResolveStatsIPv6Addr *      next;           // Next IPv6 address object in list.
    uint16_t                    count;          // Number of times this IPv6 address was provided as a resolution result.
    uint8_t                     serverID;       // 8-bit ID of the DNS server from which this IPv6 address came.
    uint8_t                     addrBytes[16];  // IPv6 address bytes.
};

check_compile_time(sizeof(ResolveStatsIPv6Addr) <= 32);

typedef struct
{
    uint16_t        count;      // Number of times that a negative response was returned by a DNS server.
    uint8_t         serverID;   // 8-bit ID of the DNS server that sent the negative responses.

}   NegAAAACounter;

check_compile_time(sizeof(NegAAAACounter) <= 4);

struct ResolveStatsNegAAAASet
{
    ResolveStatsNegAAAASet *        next;           // Next set of negative AAAA response counters in list.
    NegAAAACounter                  counters[6];    // Array of negative AAAA response counters.
};

check_compile_time(sizeof(ResolveStatsNegAAAASet) <= 32);

typedef enum
{
    kResponseType_IPv4Addr  = 1,
    kResponseType_IPv6Addr  = 2,
    kResponseType_NegA      = 3,
    kResponseType_NegAAAA   = 4

}   ResponseType;

typedef struct
{
    ResponseType        type;
    const uint8_t *     data;

}   Response;

//===========================================================================================================================
//  Globals
//===========================================================================================================================

extern mDNS     mDNSStorage;

static DNSDomainStats *             gDomainStatsList            = NULL;
static ResolveStatsDomain *         gResolveStatsList           = NULL;
static ResolveStatsDNSServer *      gResolveStatsServerList     = NULL;
static unsigned int                 gResolveStatsNextServerID   = 0;
static int                          gResolveStatsObjCount       = 0;
static AWDServerConnection *        gAWDServerConnection        = nil;

//===========================================================================================================================
//  Local Prototypes
//===========================================================================================================================

mDNSlocal mStatus   DNSDomainStatsCreate(const Domain *inDomain, DNSDomainStats **outStats);
mDNSlocal void      DNSDomainStatsFree(DNSDomainStats *inStats);
mDNSlocal void      DNSDomainStatsFreeList(DNSDomainStats *inList);
mDNSlocal mStatus   DNSDomainStatsUpdate(DNSDomainStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, mDNSu32 inLatencyMs, mDNSBool inForCell);

mDNSlocal mStatus   ResolveStatsDomainCreate(const Domain *inDomain, ResolveStatsDomain **outDomain);
mDNSlocal void      ResolveStatsDomainFree(ResolveStatsDomain *inDomain);
mDNSlocal mStatus   ResolveStatsDomainUpdate(ResolveStatsDomain *inDomain, const domainname *inHostname, const Response *inResp, const mDNSAddr *inDNSAddr, mDNSBool inForCell);
mDNSlocal mStatus   ResolveStatsDomainCreateAWDVersion(const ResolveStatsDomain *inDomain, AWDMDNSResponderResolveStatsDomain **outDomain);

mDNSlocal mStatus   ResolveStatsHostnameCreate(const domainname *inName, ResolveStatsHostname **outHostname);
mDNSlocal void      ResolveStatsHostnameFree(ResolveStatsHostname *inHostname);
mDNSlocal mStatus   ResolveStatsHostnameUpdate(ResolveStatsHostname *inHostname, const Response *inResp, uint8_t inServerID);
mDNSlocal mStatus   ResolveStatsHostnameCreateAWDVersion(const ResolveStatsHostname *inHostname, AWDMDNSResponderResolveStatsHostname **outHostname);

mDNSlocal mStatus   ResolveStatsDNSServerCreate(const mDNSAddr *inAddr, mDNSBool inForCell, ResolveStatsDNSServer **outServer);
mDNSlocal void      ResolveStatsDNSServerFree(ResolveStatsDNSServer *inServer);
mDNSlocal mStatus   ResolveStatsDNSServerCreateAWDVersion(const ResolveStatsDNSServer *inServer, AWDMDNSResponderResolveStatsDNSServer **outServer);

mDNSlocal mStatus   ResolveStatsIPv4AddrSetCreate(ResolveStatsIPv4AddrSet **outSet);
mDNSlocal void      ResolveStatsIPv4AddrSetFree(ResolveStatsIPv4AddrSet *inSet);

mDNSlocal mStatus   ResolveStatsIPv6AddressCreate(uint8_t inServerID, const uint8_t inAddrBytes[16], ResolveStatsIPv6Addr **outAddr);
mDNSlocal void      ResolveStatsIPv6AddressFree(ResolveStatsIPv6Addr *inAddr);

mDNSlocal mStatus   ResolveStatsNegAAAASetCreate(ResolveStatsNegAAAASet **outSet);
mDNSlocal void      ResolveStatsNegAAAASetFree(ResolveStatsNegAAAASet *inSet);
mDNSlocal mStatus   ResolveStatsGetServerID(const mDNSAddr *inServerAddr, mDNSBool inForCell, uint8_t *outServerID);

mDNSlocal mStatus   CreateDomainStatsList(DNSDomainStats **outList);
mDNSlocal mStatus   CreateResolveStatsList(ResolveStatsDomain **outList);
mDNSlocal void      FreeResolveStatsList(ResolveStatsDomain *inList);
mDNSlocal void      FreeResolveStatsServerList(ResolveStatsDNSServer *inList);
mDNSlocal mStatus   SubmitAWDMetric(UInt32 inMetricID);
mDNSlocal mStatus   SubmitAWDMetricQueryStats(void);
mDNSlocal mStatus   SubmitAWDMetricResolveStats(void);
mDNSlocal mStatus   CreateAWDDNSDomainStats(DNSHist *inHist, const char *inDomain, mDNSBool inForCell, AWDDNSDomainStats_RecordType inType, AWDDNSDomainStats **outStats);
mDNSlocal mStatus   AddAWDDNSDomainStats(AWDMDNSResponderDNSStatistics *inMetric, DNSHistSet *inSet, const char *inDomain, mDNSBool inForCell);
mDNSlocal void      LogDNSHistSet(const DNSHistSet *inSet, const char *inDomain, mDNSBool inForCell);
mDNSlocal void      LogDNSHist(const DNSHist *inHist, const char *inDomain, mDNSBool inForCell, const char *inType);
mDNSlocal void      LogDNSHistSendCounts(const uint16_t inSendCountBins[kQueryStatsSendCountBinCount]);
mDNSlocal void      LogDNSHistLatencies(const uint16_t inLatencyBins[kQueryStatsLatencyBinCount]);
#if (METRICS_VALIDATE_DNS_STATS_DOMAINS)
mDNSlocal void      ValidateDNSStatsDomains(void);
#endif

//===========================================================================================================================
//  MetricsInit
//===========================================================================================================================

mStatus MetricsInit(void)
{
#if (METRICS_VALIDATE_DNS_STATS_DOMAINS)
    ValidateDNSStatsDomains();
#endif

    @autoreleasepool
    {
        gAWDServerConnection = [[AWDServerConnectionSoft alloc]
            initWithComponentId:     AWDComponentId_MDNSResponder
            andBlockOnConfiguration: NO];

        if (gAWDServerConnection)
        {
            [gAWDServerConnection
                registerQueriableMetricCallback: ^(UInt32 inMetricID)
                {
                    SubmitAWDMetric(inMetricID);
                }
                forIdentifier: (UInt32)AWDMetricId_MDNSResponder_DNSStatistics];

            [gAWDServerConnection
                registerQueriableMetricCallback: ^(UInt32 inMetricID)
                {
                    SubmitAWDMetric(inMetricID);
                }
                forIdentifier: (UInt32)AWDMetricId_MDNSResponder_ResolveStats];

            [gAWDServerConnection
                registerQueriableMetricCallback: ^(UInt32 inMetricID)
                {
                    SubmitAWDMetric(inMetricID);
                }
                forIdentifier: (UInt32)AWDMetricId_MDNSResponder_ServicesStats];
        }
        else
        {
            LogMsg("MetricsInit: failed to create AWD server connection.");
        }
    }

    if( gAWDServerConnection )
    {
        CreateDomainStatsList(&gDomainStatsList);
        CreateResolveStatsList(&gResolveStatsList);
    }

    return (mStatus_NoError);
}

//===========================================================================================================================
//  MetricsUpdateUDNSQueryStats
//===========================================================================================================================

mDNSexport void MetricsUpdateUDNSQueryStats(const domainname *inQueryName, mDNSu16 inType, const ResourceRecord *inRR, mDNSu32 inSendCount, mDNSu32 inLatencyMs, mDNSBool inForCell)
{
    DNSDomainStats *        stats;
    int                     queryLabelCount;
    const domainname *      queryParentDomain;
    mDNSBool                isQueryInDomain;
    int                     skipCount;
    int                     skipCountLast = -1;

    require_quiet(gAWDServerConnection, exit);
    require_quiet((inType == kDNSType_A) || (inType == kDNSType_AAAA), exit);

    queryLabelCount = CountLabels(inQueryName);

    for (stats = gDomainStatsList; stats; stats = stats->next)
    {
        isQueryInDomain = mDNSfalse;
        if (strcmp(stats->domain->cstr, ".") == 0)
        {
            // All queries are in the root domain.
            isQueryInDomain = mDNStrue;
        }
        else
        {
            skipCount = queryLabelCount - stats->domain->labelCount;
            if (skipCount >= 0)
            {
                if (skipCount != skipCountLast)
                {
                    queryParentDomain = SkipLeadingLabels(inQueryName, skipCount);
                    skipCountLast = skipCount;
                }
                isQueryInDomain = SameDomainName(queryParentDomain, stats->domain->name);
            }
        }

        if (isQueryInDomain)
        {
            DNSDomainStatsUpdate(stats, inType, inRR, inSendCount, inLatencyMs, inForCell);
        }
    }

exit:
    return;
}

//===========================================================================================================================
//  MetricsUpdateUDNSResolveStats
//===========================================================================================================================

mDNSexport void MetricsUpdateUDNSResolveStats(const domainname *inQueryName, const ResourceRecord *inRR, mDNSBool inForCell)
{
    ResolveStatsDomain *        domain;
    domainname                  hostname;
    size_t                      hostnameLen;
    mDNSBool                    isQueryInDomain;
    int                         skipCount;
    int                         skipCountLast = -1;
    int                         queryLabelCount;
    const domainname *          queryParentDomain;
    Response                    response;

    require_quiet(gAWDServerConnection, exit);
    require_quiet((inRR->rrtype == kDNSType_A) || (inRR->rrtype == kDNSType_AAAA), exit);
    require_quiet(inRR->rDNSServer, exit);

    queryLabelCount = CountLabels(inQueryName);

    for (domain = gResolveStatsList; domain; domain = domain->next)
    {
        isQueryInDomain = mDNSfalse;
        skipCount = queryLabelCount - domain->domainInfo->labelCount;
        if (skipCount >= 0)
        {
            if (skipCount != skipCountLast)
            {
                queryParentDomain = SkipLeadingLabels(inQueryName, skipCount);
                skipCountLast = skipCount;
            }
            isQueryInDomain = SameDomainName(queryParentDomain, domain->domainInfo->name);
        }
        if (!isQueryInDomain) continue;

        hostnameLen = (size_t)(queryParentDomain->c - inQueryName->c);
        if (hostnameLen >= sizeof(hostname.c)) continue;

        memcpy(hostname.c, inQueryName->c, hostnameLen);
        hostname.c[hostnameLen] = 0;

        if (inRR->RecordType == kDNSRecordTypePacketNegative)
        {
            response.type = (inRR->rrtype == kDNSType_A) ? kResponseType_NegA : kResponseType_NegAAAA;
            response.data = NULL;
        }
        else
        {
            response.type = (inRR->rrtype == kDNSType_A) ? kResponseType_IPv4Addr : kResponseType_IPv6Addr;
            response.data = (inRR->rrtype == kDNSType_A) ? inRR->rdata->u.ipv4.b : inRR->rdata->u.ipv6.b;
        }
        ResolveStatsDomainUpdate(domain, &hostname, &response, &inRR->rDNSServer->addr, inForCell);
    }

exit:
    return;
}

//===========================================================================================================================
//  LogMetrics
//===========================================================================================================================

mDNSexport void LogMetrics(void)
{
    DNSDomainStats *                    stats;
    const ResolveStatsDomain *          domain;
    const ResolveStatsHostname *        hostname;
    const ResolveStatsDNSServer *       server;
    const ResolveStatsIPv4AddrSet *     addrV4;
    const ResolveStatsIPv6Addr *        addrV6;
    const ResolveStatsNegAAAASet *      negV6;
    int                                 hostnameCount;
    int                                 i;
    unsigned int                        serverID;
    int                                 serverObjCount   = 0;
    int                                 hostnameObjCount = 0;
    int                                 addrObjCount     = 0;

    LogMsgNoIdent("gAWDServerConnection %p", gAWDServerConnection);
    LogMsgNoIdent("---- DNS query stats by domain -----");

    for (stats = gDomainStatsList; stats; stats = stats->next)
    {
        if (!stats->nonCellular && !stats->cellular)
        {
            LogMsgNoIdent("No data for %s", stats->domain->cstr);
            continue;
        }
        if (stats->nonCellular) LogDNSHistSet(stats->nonCellular, stats->domain->cstr, mDNSfalse);
        if (stats->cellular)    LogDNSHistSet(stats->cellular,    stats->domain->cstr, mDNStrue);
    }

    LogMsgNoIdent("---- DNS resolve stats by domain -----");

    LogMsgNoIdent("Servers:");
    for (server = gResolveStatsServerList; server; server = server->next)
    {
        serverObjCount++;
        LogMsgNoIdent(server->isAddrV6 ? "%2u: %s %.16a" : "%2u: %s %.4a",
            server->id, server->isForCell ? " C" : "NC", server->addrBytes);
    }

    for (domain = gResolveStatsList; domain; domain = domain->next)
    {
        hostnameCount = 0;
        for (hostname = domain->hostnameList; hostname; hostname = hostname->next) { hostnameCount++; }
        hostnameObjCount += hostnameCount;

        LogMsgNoIdent("%##s (%d hostname%s)", domain->domainInfo->name, hostnameCount, (hostnameCount == 1) ? "" : "s");

        for (hostname = domain->hostnameList; hostname; hostname = hostname->next)
        {
            LogMsgNoIdent("    %##s", hostname->name);
            for (serverID = 0; serverID < gResolveStatsNextServerID; ++serverID)
            {
                for (addrV4 = hostname->addrV4List; addrV4; addrV4 = addrV4->next)
                {
                    if (serverID == 0) addrObjCount++;
                    for (i = 0; i < (int)countof(addrV4->counters); ++i)
                    {
                        const IPv4AddrCounter *      counter;

                        counter = &addrV4->counters[i];
                        if (counter->count == 0) break;
                        if (counter->serverID == serverID)
                        {
                            if (counter->isNegative)
                            {
                                LogMsgNoIdent("%10u: %3u negative A", counter->serverID, counter->count);
                            }
                            else
                            {
                                LogMsgNoIdent("%10u: %3u %.4a", counter->serverID, counter->count, counter->addrBytes);
                            }
                        }
                    }
                }
                for (addrV6 = hostname->addrV6List; addrV6; addrV6 = addrV6->next)
                {
                    if (serverID == 0) addrObjCount++;
                    if (addrV6->serverID == serverID)
                    {
                        LogMsgNoIdent("%10u: %3u %.16a", addrV6->serverID, addrV6->count, addrV6->addrBytes);
                    }
                }
                for (negV6 = hostname->negV6List; negV6; negV6 = negV6->next)
                {
                    if (serverID == 0) addrObjCount++;
                    for (i = 0; i < (int)countof(negV6->counters); ++i)
                    {
                        const NegAAAACounter *      counter;

                        counter = &negV6->counters[i];
                        if (counter->count == 0) break;
                        if (counter->serverID == serverID)
                        {
                            LogMsgNoIdent("%10u: %3u negative AAAA", counter->serverID, counter->count);
                        }
                    }
                }
            }
        }
    }
    LogMsgNoIdent("Total object count: %3d (server %d hostname %d address %d)",
        serverObjCount + hostnameObjCount + addrObjCount, serverObjCount, hostnameObjCount, addrObjCount);
    
    LogMsgNoIdent("---- Num of Services Registered -----");
    LogMsgNoIdent("Current_number_of_services_registered :[%d], Max_number_of_services_registered :[%d]",
                  curr_num_regservices, max_num_regservices);
}

//===========================================================================================================================
//  DNSDomainStatsCreate
//===========================================================================================================================

mDNSlocal mStatus DNSDomainStatsCreate(const Domain *inDomain, DNSDomainStats **outStats)
{
    mStatus                 err;
    DNSDomainStats *        obj;

    obj = (DNSDomainStats *)calloc(1, sizeof(*obj));
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    obj->domain = inDomain;

    *outStats = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  DNSDomainStatsFree
//===========================================================================================================================

mDNSlocal void DNSDomainStatsFree(DNSDomainStats *inStats)
{
    if (inStats->nonCellular)
    {
        ForgetMem(&inStats->nonCellular->histA);
        ForgetMem(&inStats->nonCellular->histAAAA);
        free(inStats->nonCellular);
        inStats->nonCellular = NULL;
    }
    if (inStats->cellular)
    {
        ForgetMem(&inStats->cellular->histA);
        ForgetMem(&inStats->cellular->histAAAA);
        free(inStats->cellular);
        inStats->cellular = NULL;
    }
    free(inStats);
}

//===========================================================================================================================
//  DNSDomainStatsFreeList
//===========================================================================================================================

mDNSlocal void DNSDomainStatsFreeList(DNSDomainStats *inList)
{
    DNSDomainStats *        stats;

    while ((stats = inList) != NULL)
    {
        inList = stats->next;
        DNSDomainStatsFree(stats);
    }
}

//===========================================================================================================================
//  DNSDomainStatsUpdate
//===========================================================================================================================

mDNSlocal mStatus DNSDomainStatsUpdate(DNSDomainStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, mDNSu32 inLatencyMs, mDNSBool inForCell)
{
    mStatus             err;
    DNSHistSet *        set;
    DNSHistSet **       pSet;
    DNSHist *           hist;
    DNSHist **          pHist;
    int                 i;

    require_action_quiet(inRR || (inQuerySendCount > 0), exit, err = mStatus_NoError);
    require_action_quiet((inType == kDNSType_A) || (inType == kDNSType_AAAA), exit, err = mStatus_NoError);

    pSet = inForCell ? &inStats->cellular : &inStats->nonCellular;
    if ((set = *pSet) == NULL)
    {
        set = (DNSHistSet *)calloc(1, sizeof(*set));
        require_action_quiet(set, exit, err = mStatus_NoMemoryErr);
        *pSet = set;
    }
    pHist = (inType == kDNSType_A) ? &set->histA : &set->histAAAA;
    if ((hist = *pHist) == NULL)
    {
        hist = (DNSHist *)calloc(1, sizeof(*hist));
        require_action_quiet(hist, exit, err = mStatus_NoMemoryErr);
        *pHist = hist;
    }

    if (inRR)
    {
        uint16_t *          sendCountBins;
        uint16_t *          latencyBins;
        const mDNSBool      isNegative = (inRR->RecordType == kDNSRecordTypePacketNegative);

        i = Min(inQuerySendCount, kQueryStatsMaxQuerySendCount);

        sendCountBins = isNegative ? hist->negAnsweredQuerySendCountBins : hist->answeredQuerySendCountBins;
        increment_saturate(sendCountBins[i], UINT16_MAX);

        if (inQuerySendCount > 0)
        {
            for (i = 0; (i < (int)countof(kResponseLatencyMsLimits)) && (inLatencyMs >= kResponseLatencyMsLimits[i]); ++i) {}
            latencyBins = isNegative ? hist->negResponseLatencyBins : hist->responseLatencyBins;
            increment_saturate(latencyBins[i], UINT16_MAX);
        }
    }
    else
    {
        i = Min(inQuerySendCount, kQueryStatsMaxQuerySendCount);
        increment_saturate(hist->unansweredQuerySendCountBins[i], UINT16_MAX);

        for (i = 0; (i < (int)countof(kResponseLatencyMsLimits)) && (inLatencyMs >= kResponseLatencyMsLimits[i]); ++i) {}
        increment_saturate(hist->unansweredQueryDurationBins[i], UINT16_MAX);
    }
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsDomainCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsDomainCreate(const Domain *inDomain, ResolveStatsDomain **outDomain)
{
    mStatus                     err;
    ResolveStatsDomain *        obj;

    obj = (ResolveStatsDomain *)calloc(1, sizeof(*obj));
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    obj->domainInfo = inDomain;

    *outDomain = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsDomainFree
//===========================================================================================================================

mDNSlocal void ResolveStatsDomainFree(ResolveStatsDomain *inDomain)
{
    ResolveStatsHostname *      hostname;

    while ((hostname = inDomain->hostnameList) != NULL)
    {
        inDomain->hostnameList = hostname->next;
        ResolveStatsHostnameFree(hostname);
    }
    free(inDomain);
}

//===========================================================================================================================
//  ResolveStatsDomainUpdate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsDomainUpdate(ResolveStatsDomain *inDomain, const domainname *inHostname, const Response *inResp, const mDNSAddr *inDNSAddr, mDNSBool inForCell)
{
    mStatus                     err;
    ResolveStatsHostname **     p;
    ResolveStatsHostname *      hostname;
    uint8_t                     serverID;

    for (p = &inDomain->hostnameList; (hostname = *p) != NULL; p = &hostname->next)
    {
        if (SameDomainName((domainname *)hostname->name, inHostname)) break;
    }

    if (!hostname)
    {
        require_action_quiet(gResolveStatsObjCount < kResolveStatsMaxObjCount, exit, err = mStatus_Refused);
        err = ResolveStatsHostnameCreate(inHostname, &hostname);
        require_noerr_quiet(err, exit);
        gResolveStatsObjCount++;
        *p = hostname;
    }

    err = ResolveStatsGetServerID(inDNSAddr, inForCell, &serverID);
    require_noerr_quiet(err, exit);

    err = ResolveStatsHostnameUpdate(hostname, inResp, serverID);
    require_noerr_quiet(err, exit);

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsHostnameCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsHostnameCreate(const domainname *inName, ResolveStatsHostname **outHostname)
{
    mStatus                     err;
    ResolveStatsHostname *      obj;
    size_t                      nameLen;

    nameLen = DomainNameLength(inName);
    require_action_quiet(nameLen > 0, exit, err = mStatus_Invalid);

    obj = (ResolveStatsHostname *)calloc(1, sizeof(*obj) - 1 + nameLen);
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    memcpy(obj->name, inName, nameLen);

    *outHostname = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsDomainCreateAWDVersion
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsDomainCreateAWDVersion(const ResolveStatsDomain *inDomain, AWDMDNSResponderResolveStatsDomain **outDomain)
{
    mStatus                                     err;
    AWDMDNSResponderResolveStatsDomain *        domain;
    ResolveStatsHostname *                      hostname;
    AWDMDNSResponderResolveStatsHostname *      awdHostname;
    NSString *                                  name;

    domain = [[AWDMDNSResponderResolveStatsDomainSoft alloc] init];
    require_action_quiet(domain, exit, err = mStatus_UnknownErr);

    name = [[NSString alloc] initWithUTF8String:inDomain->domainInfo->cstr];
    require_action_quiet(name, exit, err = mStatus_UnknownErr);

    domain.name = name;
    [name release];
    name = nil;

    for (hostname = inDomain->hostnameList; hostname; hostname = hostname->next)
    {
        err = ResolveStatsHostnameCreateAWDVersion(hostname, &awdHostname);
        require_noerr_quiet(err, exit);

        [domain addHostname:awdHostname];
        [awdHostname release];
        awdHostname = nil;
    }

    *outDomain = domain;
    domain = nil;
    err = mStatus_NoError;

exit:
    [domain release];
    return (err);
}

//===========================================================================================================================
//  ResolveStatsHostnameFree
//===========================================================================================================================

mDNSlocal void ResolveStatsHostnameFree(ResolveStatsHostname *inHostname)
{
    ResolveStatsIPv4AddrSet *       addrV4;
    ResolveStatsIPv6Addr *          addrV6;
    ResolveStatsNegAAAASet *        negV6;

    while ((addrV4 = inHostname->addrV4List) != NULL)
    {
        inHostname->addrV4List = addrV4->next;
        ResolveStatsIPv4AddrSetFree(addrV4);
    }
    while ((addrV6 = inHostname->addrV6List) != NULL)
    {
        inHostname->addrV6List = addrV6->next;
        ResolveStatsIPv6AddressFree(addrV6);
    }
    while ((negV6 = inHostname->negV6List) != NULL)
    {
        inHostname->negV6List = negV6->next;
        ResolveStatsNegAAAASetFree(negV6);
    }
    free(inHostname);
}

//===========================================================================================================================
//  ResolveStatsHostnameUpdate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsHostnameUpdate(ResolveStatsHostname *inHostname, const Response *inResp, uint8_t inServerID)
{
    mStatus     err;

    if ((inResp->type == kResponseType_IPv4Addr) || (inResp->type == kResponseType_NegA))
    {
        ResolveStatsIPv4AddrSet **      p;
        ResolveStatsIPv4AddrSet *       addrV4;
        int                             i;
        IPv4AddrCounter *               counter;

        for (p = &inHostname->addrV4List; (addrV4 = *p) != NULL; p = &addrV4->next)
        {
            for (i = 0; i < (int)countof(addrV4->counters); ++i)
            {
                counter = &addrV4->counters[i];
                if (counter->count == 0) break;
                if (counter->serverID != inServerID) continue;
                if (inResp->type == kResponseType_NegA)
                {
                    if (counter->isNegative) break;
                }
                else
                {
                    if (memcmp(counter->addrBytes, inResp->data, 4) == 0) break;
                }
            }
            if (i < (int)countof(addrV4->counters)) break;
        }
        if (!addrV4)
        {
            require_action_quiet(gResolveStatsObjCount < kResolveStatsMaxObjCount, exit, err = mStatus_Refused);
            err = ResolveStatsIPv4AddrSetCreate(&addrV4);
            require_noerr_quiet(err, exit);
            gResolveStatsObjCount++;

            *p = addrV4;
            counter = &addrV4->counters[0];
        }
        if (counter->count == 0)
        {
            counter->serverID = inServerID;
            if (inResp->type == kResponseType_NegA)
            {
                counter->isNegative = 1;
            }
            else
            {
                counter->isNegative = 0;
                memcpy(counter->addrBytes, inResp->data, 4);
            }
        }
        increment_saturate(counter->count, UINT16_MAX);
        err = mStatus_NoError;
    }
    else if (inResp->type == kResponseType_IPv6Addr)
    {
        ResolveStatsIPv6Addr **     p;
        ResolveStatsIPv6Addr *      addrV6;

        for (p = &inHostname->addrV6List; (addrV6 = *p) != NULL; p = &addrV6->next)
        {
            if ((addrV6->serverID == inServerID) && (memcmp(addrV6->addrBytes, inResp->data, 16) == 0)) break;
        }
        if (!addrV6)
        {
            require_action_quiet(gResolveStatsObjCount < kResolveStatsMaxObjCount, exit, err = mStatus_Refused);
            err = ResolveStatsIPv6AddressCreate(inServerID, inResp->data, &addrV6);
            require_noerr_quiet(err, exit);
            gResolveStatsObjCount++;

            *p = addrV6;
        }
        increment_saturate(addrV6->count, UINT16_MAX);
        err = mStatus_NoError;
    }
    else if (inResp->type == kResponseType_NegAAAA)
    {
        ResolveStatsNegAAAASet **       p;
        ResolveStatsNegAAAASet *        negV6;
        int                             i;
        NegAAAACounter *                counter;

        for (p = &inHostname->negV6List; (negV6 = *p) != NULL; p = &negV6->next)
        {
            for (i = 0; i < (int)countof(negV6->counters); ++i)
            {
                counter = &negV6->counters[i];
                if ((counter->count == 0) || (counter->serverID == inServerID)) break;
            }
            if (i < (int)countof(negV6->counters)) break;
        }
        if (!negV6)
        {
            require_action_quiet(gResolveStatsObjCount < kResolveStatsMaxObjCount, exit, err = mStatus_Refused);
            err = ResolveStatsNegAAAASetCreate(&negV6);
            require_noerr_quiet(err, exit);
            gResolveStatsObjCount++;

            *p = negV6;
            counter = &negV6->counters[0];
        }
        if (counter->count == 0) counter->serverID = inServerID;
        increment_saturate(counter->count, UINT16_MAX);
        err = mStatus_NoError;
    }
    else
    {
        err = mStatus_Invalid;
    }

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsHostnameCreateAWDVersion
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsHostnameCreateAWDVersion(const ResolveStatsHostname *inHostname, AWDMDNSResponderResolveStatsHostname **outHostname)
{
    mStatus                                     err;
    AWDMDNSResponderResolveStatsHostname *      hostname;
    NSString *                                  name;
    char                                        nameBuf[MAX_ESCAPED_DOMAIN_NAME];
    const char *                                ptr;
    ResolveStatsIPv4AddrSet *                   addrV4;
    ResolveStatsIPv6Addr *                      addrV6;
    ResolveStatsNegAAAASet *                    negV6;
    AWDMDNSResponderResolveStatsResult *        result = nil;
    int                                         i;

    hostname = [[AWDMDNSResponderResolveStatsHostnameSoft alloc] init];
    require_action_quiet(hostname, exit, err = mStatus_UnknownErr);

    ptr = ConvertDomainNameToCString((domainname *)inHostname->name, nameBuf);
    require_action_quiet(ptr, exit, err = mStatus_UnknownErr);

    name = [[NSString alloc] initWithUTF8String:nameBuf];
    require_action_quiet(name, exit, err = mStatus_UnknownErr);

    hostname.name = name;
    [name release];
    name = nil;

    for (addrV4 = inHostname->addrV4List; addrV4; addrV4 = addrV4->next)
    {
        for (i = 0; i < (int)countof(addrV4->counters); ++i)
        {
            const IPv4AddrCounter *     counter;
            NSData *                    addrBytes;

            counter = &addrV4->counters[i];
            if (counter->count == 0) break;

            result = [[AWDMDNSResponderResolveStatsResultSoft alloc] init];
            require_action_quiet(result, exit, err = mStatus_UnknownErr);

            if (counter->isNegative)
            {
                result.type = AWDMDNSResponderResolveStatsResult_ResultType_NegA;
            }
            else
            {
                addrBytes = [[NSData alloc] initWithBytes:counter->addrBytes length:4];
                require_action_quiet(addrBytes, exit, err = mStatus_UnknownErr);

                result.type = AWDMDNSResponderResolveStatsResult_ResultType_IPv4Addr;
                result.data = addrBytes;
                [addrBytes release];
            }
            result.count    = counter->count;
            result.serverID = counter->serverID;

            [hostname addResult:result];
            [result release];
            result = nil;
        }
    }

    for (addrV6 = inHostname->addrV6List; addrV6; addrV6 = addrV6->next)
    {
        NSData *        addrBytes;

        result = [[AWDMDNSResponderResolveStatsResultSoft alloc] init];
        require_action_quiet(result, exit, err = mStatus_UnknownErr);

        addrBytes = [[NSData alloc] initWithBytes:addrV6->addrBytes length:16];
        require_action_quiet(addrBytes, exit, err = mStatus_UnknownErr);

        result.type     = AWDMDNSResponderResolveStatsResult_ResultType_IPv6Addr;
        result.count    = addrV6->count;
        result.serverID = addrV6->serverID;
        result.data     = addrBytes;

        [addrBytes release];

        [hostname addResult:result];
        [result release];
        result = nil;
    }

    for (negV6 = inHostname->negV6List; negV6; negV6 = negV6->next)
    {
        for (i = 0; i < (int)countof(negV6->counters); ++i)
        {
            const NegAAAACounter *      counter;

            counter = &negV6->counters[i];
            if (counter->count == 0) break;

            result = [[AWDMDNSResponderResolveStatsResultSoft alloc] init];
            require_action_quiet(result, exit, err = mStatus_UnknownErr);

            result.type     = AWDMDNSResponderResolveStatsResult_ResultType_NegAAAA;
            result.count    = counter->count;
            result.serverID = counter->serverID;

            [hostname addResult:result];
            [result release];
            result = nil;
        }
    }

    *outHostname = hostname;
    hostname = nil;
    err = mStatus_NoError;

exit:
    [result release];
    [hostname release];
    return (err);
}

//===========================================================================================================================
//  ResolveStatsDNSServerCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsDNSServerCreate(const mDNSAddr *inAddr, mDNSBool inForCell, ResolveStatsDNSServer **outServer)
{
    mStatus                     err;
    ResolveStatsDNSServer *     obj;
    size_t                      addrLen;

    require_action_quiet((inAddr->type == mDNSAddrType_IPv4) || (inAddr->type == mDNSAddrType_IPv6), exit, err = mStatus_Invalid);

    addrLen = (inAddr->type == mDNSAddrType_IPv4) ? 4 : 16;
    obj = (ResolveStatsDNSServer *)calloc(1, sizeof(*obj) - 1 + addrLen);
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    obj->isForCell = inForCell;
    if (inAddr->type == mDNSAddrType_IPv4)
    {
        obj->isAddrV6 = mDNSfalse;
        memcpy(obj->addrBytes, inAddr->ip.v4.b, addrLen);
    }
    else
    {
        obj->isAddrV6 = mDNStrue;
        memcpy(obj->addrBytes, inAddr->ip.v6.b, addrLen);
    }

    *outServer = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsDNSServerFree
//===========================================================================================================================

mDNSlocal void ResolveStatsDNSServerFree(ResolveStatsDNSServer *inServer)
{
    free(inServer);
}

//===========================================================================================================================
//  ResolveStatsDNSServerCreateAWDVersion
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsDNSServerCreateAWDVersion(const ResolveStatsDNSServer *inServer, AWDMDNSResponderResolveStatsDNSServer **outServer)
{
    mStatus                                     err;
    AWDMDNSResponderResolveStatsDNSServer *     server;
    NSData *                                    addrBytes = nil;

    server = [[AWDMDNSResponderResolveStatsDNSServerSoft alloc] init];
    require_action_quiet(server, exit, err = mStatus_UnknownErr);

    addrBytes = [[NSData alloc] initWithBytes:inServer->addrBytes length:(inServer->isAddrV6 ? 16 : 4)];
    require_action_quiet(addrBytes, exit, err = mStatus_UnknownErr);

    server.serverID = inServer->id;
    server.address  = addrBytes;
    if (inServer->isForCell)
    {
        server.networkType = AWDMDNSResponderResolveStatsDNSServer_NetworkType_Cellular;
    }
    else
    {
        server.networkType = AWDMDNSResponderResolveStatsDNSServer_NetworkType_NonCellular;
    }

    *outServer = server;
    server = nil;
    err = mStatus_NoError;

exit:
    [addrBytes release];
    [server release];
    return (err);
}

//===========================================================================================================================
//  ResolveStatsIPv4AddrSetCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsIPv4AddrSetCreate(ResolveStatsIPv4AddrSet **outSet)
{
    mStatus                         err;
    ResolveStatsIPv4AddrSet *       obj;

    obj = (ResolveStatsIPv4AddrSet *)calloc(1, sizeof(*obj));
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    *outSet = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsIPv4AddrSetFree
//===========================================================================================================================

mDNSlocal void ResolveStatsIPv4AddrSetFree(ResolveStatsIPv4AddrSet *inSet)
{
    free(inSet);
}

//===========================================================================================================================
//  ResolveStatsIPv6AddressCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsIPv6AddressCreate(uint8_t inServerID, const uint8_t inAddrBytes[16], ResolveStatsIPv6Addr **outAddr)
{
    mStatus                     err;
    ResolveStatsIPv6Addr *      obj;

    obj = (ResolveStatsIPv6Addr *)calloc(1, sizeof(*obj));
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    obj->serverID = inServerID;
    memcpy(obj->addrBytes, inAddrBytes, 16);

    *outAddr = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsIPv6AddressFree
//===========================================================================================================================

mDNSlocal void ResolveStatsIPv6AddressFree(ResolveStatsIPv6Addr *inAddr)
{
    free(inAddr);
}

//===========================================================================================================================
//  ResolveStatsNegAAAASetCreate
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsNegAAAASetCreate(ResolveStatsNegAAAASet **outSet)
{
    mStatus                         err;
    ResolveStatsNegAAAASet *        obj;

    obj = (ResolveStatsNegAAAASet *)calloc(1, sizeof(*obj));
    require_action_quiet(obj, exit, err = mStatus_NoMemoryErr);

    *outSet = obj;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  ResolveStatsNegAAAASetFree
//===========================================================================================================================

mDNSlocal void ResolveStatsNegAAAASetFree(ResolveStatsNegAAAASet *inSet)
{
    free(inSet);
}

//===========================================================================================================================
//  ResolveStatsGetServerID
//===========================================================================================================================

mDNSlocal mStatus ResolveStatsGetServerID(const mDNSAddr *inServerAddr, mDNSBool inForCell, uint8_t *outServerID)
{
    mStatus                         err;
    ResolveStatsDNSServer **        p;
    ResolveStatsDNSServer *         server;

    require_action_quiet((inServerAddr->type == mDNSAddrType_IPv4) || (inServerAddr->type == mDNSAddrType_IPv6), exit, err = mStatus_Invalid);

    for (p = &gResolveStatsServerList; (server = *p) != NULL; p = &server->next)
    {
        if ((inForCell && server->isForCell) || (!inForCell && !server->isForCell))
        {
            if (inServerAddr->type == mDNSAddrType_IPv4)
            {
                if (!server->isAddrV6 && (memcmp(server->addrBytes, inServerAddr->ip.v4.b, 4) == 0)) break;
            }
            else
            {
                if (server->isAddrV6 && (memcmp(server->addrBytes, inServerAddr->ip.v6.b, 16) == 0)) break;
            }
        }
    }

    if (!server)
    {
        require_action_quiet(gResolveStatsNextServerID <= UINT8_MAX, exit, err = mStatus_Refused);
        require_action_quiet(gResolveStatsObjCount < kResolveStatsMaxObjCount, exit, err = mStatus_Refused);
        err = ResolveStatsDNSServerCreate(inServerAddr, inForCell, &server);
        require_noerr_quiet(err, exit);
        gResolveStatsObjCount++;

        server->id   = gResolveStatsNextServerID++;
        server->next = gResolveStatsServerList;
        gResolveStatsServerList = server;
    }
    else if (gResolveStatsServerList != server)
    {
        *p = server->next;
        server->next = gResolveStatsServerList;
        gResolveStatsServerList = server;
    }

    *outServerID = server->id;
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  CreateDomainStatsList
//===========================================================================================================================

mDNSlocal mStatus CreateDomainStatsList(DNSDomainStats **outList)
{
    mStatus                 err;
    int                     i;
    DNSDomainStats *        stats;
    DNSDomainStats **       p;
    DNSDomainStats *        list = NULL;

    p = &list;
    for (i = 0; i < (int)countof(kQueryStatsDomains); ++i)
    {
        err = DNSDomainStatsCreate(&kQueryStatsDomains[i], &stats);
        require_noerr_quiet(err, exit);

        *p = stats;
        p = &stats->next;
    }

    *outList = list;
    list = NULL;

exit:
    DNSDomainStatsFreeList(list);
    return (err);
}

//===========================================================================================================================
//  CreateResolveStatsList
//===========================================================================================================================

mDNSlocal mStatus CreateResolveStatsList(ResolveStatsDomain **outList)
{
    mStatus                     err;
    int                         i;
    ResolveStatsDomain *        domain;
    ResolveStatsDomain **       p;
    ResolveStatsDomain *        list = NULL;

    p = &list;
    for (i = 0; i < (int)countof(kResolveStatsDomains); ++i)
    {
        err = ResolveStatsDomainCreate(&kResolveStatsDomains[i], &domain);
        require_noerr_quiet(err, exit);

        *p = domain;
        p = &domain->next;
    }

    *outList = list;
    list = NULL;

exit:
    FreeResolveStatsList(list);
    return (err);
}

//===========================================================================================================================
//  FreeResolveStatsList
//===========================================================================================================================

mDNSlocal void FreeResolveStatsList(ResolveStatsDomain *inList)
{
    ResolveStatsDomain *        domain;

    while ((domain = inList) != NULL)
    {
        inList = domain->next;
        ResolveStatsDomainFree(domain);
    }
}

//===========================================================================================================================
//  FreeResolveStatsServerList
//===========================================================================================================================

mDNSlocal void FreeResolveStatsServerList(ResolveStatsDNSServer *inList)
{
    ResolveStatsDNSServer *     server;

    while ((server = inList) != NULL)
    {
        inList = server->next;
        ResolveStatsDNSServerFree(server);
    }
}

//===========================================================================================================================
//  SubmitAWDMetric
//===========================================================================================================================

mDNSlocal mStatus SubmitAWDMetric(UInt32 inMetricID)
{
    mStatus     err;

    switch (inMetricID)
    {
        case AWDMetricId_MDNSResponder_DNSStatistics:
            err = SubmitAWDMetricQueryStats();
            break;

        case AWDMetricId_MDNSResponder_ResolveStats:
            err = SubmitAWDMetricResolveStats();
            break;

        case AWDMetricId_MDNSResponder_ServicesStats:
            [AWDMetricManagerSoft postMetricWithId:AWDMetricId_MDNSResponder_ServicesStats unsignedIntegerValue:max_num_regservices];
            KQueueLock(&mDNSStorage);
            // reset the no of max services since we want to collect the max no of services registered per AWD submission period
            max_num_regservices = curr_num_regservices;
            KQueueUnlock(&mDNSStorage, "SubmitAWDSimpleMetricServiceStats");
            err = mStatus_NoError;
            break;
            
        default:
            err = mStatus_UnsupportedErr;
            break;
    }

    if (err) LogMsg("SubmitAWDMetric for metric ID 0x%08X failed with error %d", inMetricID, err);
    return (err);
}

//===========================================================================================================================
//  SubmitAWDMetricQueryStats
//===========================================================================================================================

mDNSlocal mStatus SubmitAWDMetricQueryStats(void)
{
    mStatus                             err;
    BOOL                                success;
    DNSDomainStats *                    stats;
    DNSDomainStats *                    newDomainStatsList;
    DNSDomainStats *                    domainStatsList = NULL;
    AWDMetricContainer *                container       = nil;
    AWDMDNSResponderDNSStatistics *     metric          = nil;

    err = CreateDomainStatsList(&newDomainStatsList);
    require_noerr_quiet(err, exit);

    KQueueLock(&mDNSStorage);
    domainStatsList = gDomainStatsList;
    gDomainStatsList = newDomainStatsList;
    KQueueUnlock(&mDNSStorage, "SubmitAWDMetricQueryStats");

    container = [gAWDServerConnection newMetricContainerWithIdentifier:AWDMetricId_MDNSResponder_DNSStatistics];
    require_action_quiet(container, exit, err = mStatus_UnknownErr);

    metric = [[AWDMDNSResponderDNSStatisticsSoft alloc] init];
    require_action_quiet(metric, exit, err = mStatus_UnknownErr);

    while ((stats = domainStatsList) != NULL)
    {
        if (stats->nonCellular)
        {
            err = AddAWDDNSDomainStats(metric, stats->nonCellular, stats->domain->cstr, mDNSfalse);
            require_noerr_quiet(err, exit);
        }
        if (stats->cellular)
        {
            err = AddAWDDNSDomainStats(metric, stats->cellular, stats->domain->cstr, mDNStrue);
            require_noerr_quiet(err, exit);
        }
        domainStatsList = stats->next;
        DNSDomainStatsFree(stats);
    }

    container.metric = metric;
    success = [gAWDServerConnection submitMetric:container];
    LogMsg("SubmitAWDMetricQueryStats: metric submission %s.", success ? "succeeded" : "failed" );
    err = success ? mStatus_NoError : mStatus_UnknownErr;

exit:
    [metric release];
    [container release];
    DNSDomainStatsFreeList(domainStatsList);
    return (err);
}

//===========================================================================================================================
//  SubmitAWDMetricResolveStats
//===========================================================================================================================

mDNSlocal mStatus SubmitAWDMetricResolveStats(void)
{
    mStatus                             err;
    ResolveStatsDomain *                newResolveStatsList;
    ResolveStatsDomain *                domainList  = NULL;
    ResolveStatsDNSServer *             serverList  = NULL;
    AWDMetricContainer *                container   = nil;
    AWDMDNSResponderResolveStats *      metric      = nil;
    ResolveStatsDNSServer *             server;
    ResolveStatsDomain *                domain;
    BOOL                                success;

    err = CreateResolveStatsList(&newResolveStatsList);
    require_noerr_quiet(err, exit);

    KQueueLock(&mDNSStorage);
    domainList = gResolveStatsList;
    serverList = gResolveStatsServerList;
    gResolveStatsList           = newResolveStatsList;
    gResolveStatsServerList     = NULL;
    gResolveStatsNextServerID   = 0;
    gResolveStatsObjCount       = 0;
    KQueueUnlock(&mDNSStorage, "SubmitAWDMetricResolveStats");

    container = [gAWDServerConnection newMetricContainerWithIdentifier:AWDMetricId_MDNSResponder_ResolveStats];
    require_action_quiet(container, exit, err = mStatus_UnknownErr);

    metric = [[AWDMDNSResponderResolveStatsSoft alloc] init];
    require_action_quiet(metric, exit, err = mStatus_UnknownErr);

    while ((server = serverList) != NULL)
    {
        AWDMDNSResponderResolveStatsDNSServer *     awdServer;

        serverList = server->next;
        err = ResolveStatsDNSServerCreateAWDVersion(server, &awdServer);
        ResolveStatsDNSServerFree(server);
        require_noerr_quiet(err, exit);

        [metric addServer:awdServer];
        [awdServer release];
    }

    while ((domain = domainList) != NULL)
    {
        AWDMDNSResponderResolveStatsDomain *        awdDomain;

        domainList = domain->next;
        err = ResolveStatsDomainCreateAWDVersion(domain, &awdDomain);
        ResolveStatsDomainFree(domain);
        require_noerr_quiet(err, exit);

        [metric addDomain:awdDomain];
        [awdDomain release];
    }

    container.metric = metric;
    success = [gAWDServerConnection submitMetric:container];
    LogMsg("SubmitAWDMetricResolveStats: metric submission %s.", success ? "succeeded" : "failed" );
    err = success ? mStatus_NoError : mStatus_UnknownErr;

exit:
    [metric release];
    [container release];
    FreeResolveStatsList(domainList);
    FreeResolveStatsServerList(serverList);
    return (err);
}

//===========================================================================================================================
//  CreateAWDDNSDomainStats
//===========================================================================================================================

mDNSlocal mStatus CreateAWDDNSDomainStats(DNSHist *inHist, const char *inDomain, mDNSBool inForCell, AWDDNSDomainStats_RecordType inType, AWDDNSDomainStats **outStats)
{
    mStatus                 err;
    AWDDNSDomainStats *     awdStats    = nil;
    NSString *              domain      = nil;
    uint32_t                sendCountBins[kQueryStatsSendCountBinCount];
    uint32_t                latencyBins[kQueryStatsLatencyBinCount];
    int                     i;
    unsigned int            totalAnswered;
    unsigned int            totalNegAnswered;
    unsigned int            totalUnanswered;

    awdStats = [[AWDDNSDomainStatsSoft alloc] init];
    require_action_quiet(awdStats, exit, err = mStatus_UnknownErr);

    domain = [[NSString alloc] initWithUTF8String:inDomain];
    require_action_quiet(domain, exit, err = mStatus_UnknownErr);

    awdStats.domain      = domain;
    awdStats.networkType = inForCell ? AWDDNSDomainStats_NetworkType_Cellular : AWDDNSDomainStats_NetworkType_NonCellular;
    awdStats.recordType  = inType;

    totalAnswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        sendCountBins[i] = inHist->answeredQuerySendCountBins[i];
        totalAnswered   += inHist->answeredQuerySendCountBins[i];
    }
    [awdStats setAnsweredQuerySendCounts:sendCountBins count:kQueryStatsSendCountBinCount];

    totalNegAnswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        sendCountBins[i]  = inHist->negAnsweredQuerySendCountBins[i];
        totalNegAnswered += inHist->negAnsweredQuerySendCountBins[i];
    }
    [awdStats setNegAnsweredQuerySendCounts:sendCountBins count:kQueryStatsSendCountBinCount];

    totalUnanswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        sendCountBins[i] = inHist->unansweredQuerySendCountBins[i];
        totalUnanswered += inHist->unansweredQuerySendCountBins[i];
    }
    [awdStats setUnansweredQuerySendCounts:sendCountBins count:kQueryStatsSendCountBinCount];

    if (totalAnswered > inHist->answeredQuerySendCountBins[0])
    {
        for (i = 0; i < kQueryStatsLatencyBinCount; ++i)
        {
            latencyBins[i] = inHist->responseLatencyBins[i];
        }
        [awdStats setResponseLatencyMs:latencyBins count:kQueryStatsLatencyBinCount];
    }

    if (totalNegAnswered > inHist->negAnsweredQuerySendCountBins[0])
    {
        for (i = 0; i < kQueryStatsLatencyBinCount; ++i)
        {
            latencyBins[i] = inHist->negResponseLatencyBins[i];
        }
        [awdStats setNegResponseLatencyMs:latencyBins count:kQueryStatsLatencyBinCount];
    }

    if (totalUnanswered > 0)
    {
        for (i = 0; i < kQueryStatsLatencyBinCount; ++i)
        {
            latencyBins[i] = inHist->unansweredQueryDurationBins[i];
        }
        [awdStats setUnansweredQueryDurationMs:latencyBins count:kQueryStatsLatencyBinCount];
    }

    *outStats = awdStats;
    awdStats = nil;
    err = mStatus_NoError;

exit:
    [domain release];
    [awdStats release];
    return (err);
}

//===========================================================================================================================
//  AddAWDDNSDomainStats
//===========================================================================================================================

mDNSlocal mStatus AddAWDDNSDomainStats(AWDMDNSResponderDNSStatistics *inMetric, DNSHistSet *inSet, const char *inDomain, mDNSBool inForCell)
{
    mStatus                 err;
    AWDDNSDomainStats *     awdStats;

    if (inSet->histA)
    {
        err = CreateAWDDNSDomainStats(inSet->histA, inDomain, inForCell, AWDDNSDomainStats_RecordType_A, &awdStats);
        require_noerr_quiet(err, exit);

        [inMetric addStats:awdStats];
        [awdStats release];
    }
    if (inSet->histAAAA)
    {
        err = CreateAWDDNSDomainStats(inSet->histAAAA, inDomain, inForCell, AWDDNSDomainStats_RecordType_AAAA, &awdStats);
        require_noerr_quiet(err, exit);

        [inMetric addStats:awdStats];
        [awdStats release];
    }
    err = mStatus_NoError;

exit:
    return (err);
}

//===========================================================================================================================
//  LogDNSHistSet
//===========================================================================================================================

mDNSlocal void LogDNSHistSet(const DNSHistSet *inSet, const char *inDomain, mDNSBool inForCell)
{
    if (inSet->histA)       LogDNSHist(inSet->histA,    inDomain, inForCell, "A");
    if (inSet->histAAAA)    LogDNSHist(inSet->histAAAA, inDomain, inForCell, "AAAA");
}

//===========================================================================================================================
//  LogDNSHist
//===========================================================================================================================

#define Percent(N, D)       ((N) * 100) / (D), (((N) * 10000) / (D)) % 100
#define PercentFmt          "%3u.%02u"
#define LogStat(LABEL, COUNT, ACCUMULATOR, TOTAL) \
    LogMsgNoIdent("%s %5u " PercentFmt " " PercentFmt, (LABEL), (COUNT), Percent(COUNT, TOTAL), Percent(ACCUMULATOR, TOTAL))

mDNSlocal void LogDNSHist(const DNSHist *inHist, const char *inDomain, mDNSBool inForCell, const char *inType)
{
    unsigned int        totalAnswered;
    unsigned int        totalNegAnswered;
    unsigned int        totalUnanswered;
    int                 i;

    totalAnswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        totalAnswered += inHist->answeredQuerySendCountBins[i];
    }

    totalNegAnswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        totalNegAnswered += inHist->negAnsweredQuerySendCountBins[i];
    }

    totalUnanswered = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        totalUnanswered += inHist->unansweredQuerySendCountBins[i];
    }

    LogMsgNoIdent("Domain: %s (%s, %s)", inDomain, inForCell ? "C" : "NC", inType);
    LogMsgNoIdent("Answered questions            %4u", totalAnswered);
    LogMsgNoIdent("Negatively answered questions %4u", totalNegAnswered);
    LogMsgNoIdent("Unanswered questions          %4u", totalUnanswered);
    LogMsgNoIdent("-- Query send counts ---------");
    LogDNSHistSendCounts(inHist->answeredQuerySendCountBins);
    LogMsgNoIdent("-- Query send counts (NAQs) --");
    LogDNSHistSendCounts(inHist->negAnsweredQuerySendCountBins);

    if (totalAnswered > inHist->answeredQuerySendCountBins[0])
    {
        LogMsgNoIdent("--- Response times -----------");
        LogDNSHistLatencies(inHist->responseLatencyBins);
    }

    if (totalNegAnswered > inHist->negAnsweredQuerySendCountBins[0])
    {
        LogMsgNoIdent("--- Response times (NAQs) ----");
        LogDNSHistLatencies(inHist->negResponseLatencyBins);
    }

    if (totalUnanswered > 0)
    {
        LogMsgNoIdent("--- Unanswered query times ---");
        LogDNSHistLatencies(inHist->unansweredQueryDurationBins);
    }
}

//===========================================================================================================================
//  LogDNSHistSendCounts
//===========================================================================================================================

mDNSlocal void LogDNSHistSendCounts(const uint16_t inSendCountBins[kQueryStatsSendCountBinCount])
{
    uint32_t        total;
    char            label[16];
    int             i;

    total = 0;
    for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
    {
        total += inSendCountBins[i];
    }

    if (total > 0)
    {
        uint32_t        accumulator = 0;

        for (i = 0; i < kQueryStatsSendCountBinCount; ++i)
        {
            accumulator += inSendCountBins[i];
            if (i < (kQueryStatsSendCountBinCount - 1))
            {
                snprintf(label, sizeof(label), "%2d ", i);
            }
            else
            {
                snprintf(label, sizeof(label), "%2d+", i);
            }
            LogStat(label, inSendCountBins[i], accumulator, total);
            if (accumulator == total) break;
        }
    }
    else
    {
        LogMsgNoIdent("No data.");
    }
}

//===========================================================================================================================
//  LogDNSHistLatencies
//===========================================================================================================================

mDNSlocal void LogDNSHistLatencies(const uint16_t inLatencyBins[kQueryStatsLatencyBinCount])
{
    uint32_t        total;
    int             i;
    char            label[16];

    total = 0;
    for (i = 0; i < kQueryStatsLatencyBinCount; ++i)
    {
        total += inLatencyBins[i];
    }

    if (total > 0)
    {
        uint32_t        accumulator = 0;

        for (i = 0; i < kQueryStatsLatencyBinCount; ++i)
        {
            accumulator += inLatencyBins[i];
            if (i < (int)countof(kResponseLatencyMsLimits))
            {
                snprintf(label, sizeof(label), "< %5u ms", kResponseLatencyMsLimits[i]);
            }
            else
            {
                snprintf(label, sizeof(label), "<     ∞ ms");
            }
            LogStat(label, inLatencyBins[i], accumulator, total);
            if (accumulator == total) break;
        }
    }
    else
    {
        LogMsgNoIdent("No data.");
    }
}

//===========================================================================================================================
//  ValidateDNSStatsDomains
//===========================================================================================================================

#if (METRICS_VALIDATE_DNS_STATS_DOMAINS)
#warning "Do not include ValidateDNSStatsDomains() in customer release!"
mDNSlocal void ValidateDNSStatsDomains(void)
{
    int                 i;
    const Domain *      domain;
    mDNSu8 *            ptr;
    domainname          domainNameExpected;
    int                 labelCountExpected;
    mDNSBool            domainNamesEqual;
    mDNSBool            failed = mDNSfalse;

    for (i = 0; i < countof(kQueryStatsDomains); ++i)
    {
        domain = &kQueryStatsDomains[i];

        if (strcmp(domain->cstr, ".") == 0)
        {
            domainNameExpected.c[0] = 0;
        }
        else
        {
            ptr = MakeDomainNameFromDNSNameString(&domainNameExpected, domain->cstr);
            if (!ptr)
            {
                LogMsg("ValidateDNSStatsDomains: Failed to make domain name for \"%s\".", domain->cstr);
                failed = mDNStrue;
                goto exit;
            }
        }

        domainNamesEqual = SameDomainName(domain->name, &domainNameExpected);
        labelCountExpected = CountLabels(&domainNameExpected);
        if (domainNamesEqual && (domain->labelCount == labelCountExpected))
        {
            LogMsg("ValidateDNSStatsDomains: \"%s\" passed.", domain->cstr);
        }
        else
        {
            if (!domainNamesEqual)
            {
                LogMsg("ValidateDNSStatsDomains: \"%s\" failed: incorrect domain name.", domain->cstr);
            }
            if (domain->labelCount != labelCountExpected)
            {
                LogMsg("ValidateDNSStatsDomains: \"%s\" failed: incorrect label count. Actual %d, expected %d.",
                    domain->cstr, domain->labelCount, labelCountExpected);
            }
            failed = mDNStrue;
        }
    }

exit:
    if (failed) abort();
}
#endif
#endif // TARGET_OS_EMBEDDED
