#include "unittest_common.h"
#include "dns_sd.h"
#include "mDNSEmbeddedAPI.h"
#include "mDNSMacOSX.h"

static mDNS_PlatformSupport PlatformStorage;
#define RR_CACHE_SIZE ((32*1024) / sizeof(CacheRecord))
static CacheEntity gRrcachestorage[RR_CACHE_SIZE];

// Primary interface info that is used when simulating the receive of the response packet
mDNSInterfaceID primary_interfaceID;
mDNSAddr primary_v4;
mDNSAddr primary_v6;
mDNSAddr primary_router;

// This function sets up the minimum environement to run a unit test. It
// initializes logging, interfaces, and timenow.
mDNSexport mStatus init_mdns_environment(mDNSBool enableLogging)
{
	mDNS *m = &mDNSStorage;

	init_logging_ut();
	mDNS_LoggingEnabled = enableLogging;
	mDNS_PacketLoggingEnabled = enableLogging;

	mStatus result = mDNS_InitStorage_ut(m, &PlatformStorage, gRrcachestorage, RR_CACHE_SIZE, mDNSfalse, mDNSNULL, mDNSNULL);
	if (result != mStatus_NoError)
		return result;

	primary_v4 = primary_v6 = primary_router = zeroAddr;
	SetInterfaces_ut(&primary_interfaceID, &primary_v4, &primary_v6, &primary_router);

	m->timenow = mDNS_TimeNow_NoLock(m);
	return mStatus_NoError;
}

// This function sets up the minimum environement to run a unit test. It
// initializes logging and timenow.  This is the call to use if your
// unit test does not use interfaces.
mDNSexport mStatus init_mdns_storage(void)
{
	mDNS *m = &mDNSStorage;

	init_logging_ut();
	mDNS_LoggingEnabled = 1;
	mDNS_PacketLoggingEnabled = 1;

	mStatus result = mDNS_InitStorage_ut(m, &PlatformStorage, gRrcachestorage, RR_CACHE_SIZE, mDNSfalse, mDNSNULL, mDNSNULL);
	if (result != mStatus_NoError)
		return result;

	return mStatus_NoError;
}

mDNSlocal void init_client_request(request_state* req, const uint8_t *msgbuf, uint32_t msgSize, uint32_t op)
{
	// Simulate read_msg behavior since unit test does not open a socket
	memset(req, 0, sizeof(request_state));

	req->ts = t_complete;
	req->msgbuf = mDNSNULL;
	req->msgptr = msgbuf;
	req->msgend = msgbuf + msgSize;

	// The rest of the request values are set in order to simulate a request
	req->sd             = client_req_sd;
	req->uid            = client_req_uid;
	req->hdr_bytes      = client_req_hdr_bytes;
	req->hdr.version    = client_req_hdr_version;
	req->hdr.op         = op; // query_request
	req->hdr.datalen    = msgSize;
	req->data_bytes     = msgSize;
	req->process_id     = client_req_process_id;
	memcpy(req->pid_name, client_req_pid_name, strlen(client_req_pid_name));
}

// This function calls the mDNSResponder handle_client_request() API.  It initializes
// the request and query data structures.
mDNSexport mStatus start_client_request(request_state* req, const uint8_t *msgbuf, uint32_t msgsz, uint32_t op, UDPSocket* socket)
{
	// Process the unit test's client request
	init_client_request(req, msgbuf, msgsz, op);

	mStatus result = handle_client_request_ut((void*)req);
	DNSQuestion* q = &req->queryrecord->op.q;
	q->LocalSocket = socket;
	return result;
}

// This function calls the mDNSResponder mDNSCoreReceive() API.
mDNSexport void receive_response(const request_state* req, DNSMessage *msg, size_t msgSize)
{
	mDNS *m = &mDNSStorage;
	mDNSAddr srcaddr;
	mDNSIPPort srcport, dstport;
	const mDNSu8 * end;
	DNSQuestion *q = (DNSQuestion *)&req->queryrecord->op.q;
	UInt8* data = (UInt8*)msg;

	// Used same values for DNS server as specified during init of unit test
	srcaddr.type				= mDNSAddrType_IPv4;
	srcaddr.ip.v4.NotAnInteger	= dns_server_ipv4.NotAnInteger;
	srcport.NotAnInteger		= client_resp_src_port;

	// Used random value for dstport
	dstport.NotAnInteger = swap16((mDNSu16)client_resp_dst_port);

	// Set DNS message (that was copied from a WireShark packet)
	end = (const mDNSu8 *)msg + msgSize;

	// Set socket info that mDNSCoreReceive uses to verify socket context
	q->LocalSocket->ss.port.NotAnInteger = swap16((mDNSu16)client_resp_dst_port);
	q->TargetQID.b[0] = data[0];
	q->TargetQID.b[1] = data[1];

	// Execute mDNSCoreReceive which copies two DNS records into the cache
	mDNSCoreReceive(m, msg, end, &srcaddr, srcport, &primary_v4, dstport, primary_interfaceID);
}

mDNSexport  size_t get_reply_len(char* name, uint16_t rdlen)
{
	size_t len = sizeof(DNSServiceFlags);
	len += sizeof(mDNSu32);     // interface index
	len += sizeof(DNSServiceErrorType);
	len += strlen(name) + 1;
	len += 3 * sizeof(mDNSu16); // type, class, rdlen
	len += rdlen;
	len += sizeof(mDNSu32);     // TTL
	return len;
}


void free_req(request_state* req)
{
	// Cleanup request's memory usage
	while (req->replies)
	{
		reply_state *reply = req->replies;
		req->replies = req->replies->next;
		mDNSPlatformMemFree(reply);
	}
	req->replies = NULL;
	mDNSPlatformMemFree(req);
}

// Unit test support functions follow
#define SA_LEN(addr) (((addr)->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))

mDNSexport void get_ip(const char *const name, struct sockaddr_storage *result)
{
	struct addrinfo* aiList;
	int err = getaddrinfo(name, NULL, NULL, &aiList);
	if (err) fprintf(stderr, "getaddrinfo error %d for %s", err, name);
	else memcpy(result, aiList->ai_addr, SA_LEN(aiList->ai_addr));
	if (aiList) freeaddrinfo(aiList);
}

// The AddDNSServer_ut function adds a dns server to mDNSResponder's list.
mDNSexport mStatus AddDNSServerScoped_ut(mDNSInterfaceID interfaceID, ScopeType scoped)
{
    mDNS *m = &mDNSStorage;
    m->timenow = 0;
    mDNS_Lock(m);
    domainname  d;
    mDNSAddr    addr;
    mDNSIPPort  port;
    mDNSs32     serviceID      = 0;
    mDNSu32     timeout        = dns_server_timeout;
    mDNSBool    cellIntf       = 0;
    mDNSBool    isExpensive    = 0;
    mDNSBool    isConstrained  = 0;
    mDNSBool    isCLAT46       = mDNSfalse;
    mDNSu32     resGroupID     = dns_server_resGroupID;
    mDNSBool    reqA           = mDNStrue;
    mDNSBool    reqAAAA        = mDNStrue;
    mDNSBool    reqDO          = mDNSfalse;
    d.c[0]                     = 0;
    addr.type                  = mDNSAddrType_IPv4;
    addr.ip.v4.NotAnInteger    = dns_server_ipv4.NotAnInteger;
    port.NotAnInteger          = client_resp_src_port;
    mDNS_AddDNSServer(m, &d, interfaceID, serviceID, &addr, port, scoped, timeout,
                      cellIntf, isExpensive, isConstrained, isCLAT46, resGroupID,
                      reqA, reqAAAA, reqDO);
    mDNS_Unlock(m);
    return mStatus_NoError;
}

mDNSexport mStatus AddDNSServer_ut(void)
{
    return AddDNSServerScoped_ut(primary_interfaceID, kScopeNone);
}

mDNSexport mStatus  force_uDNS_SetupDNSConfig_ut(mDNS *const m)
{
    m->p->LastConfigGeneration = 0;
    return uDNS_SetupDNSConfig(m);
}

mDNSexport mStatus verify_cache_addr_order_for_domain_ut(mDNS *const m, mDNSu8* octet, mDNSu32 count, const domainname *const name)
{
    mStatus result = mStatus_NoError;
    const CacheGroup *cg = CacheGroupForName(m, DomainNameHashValue(name), name);
    if (cg)
    {
        mDNSu32 i;
        CacheRecord **rp = (CacheRecord **)&cg->members;
        for (i = 0 ; *rp && i < count ; i++ )
        {
            if ((*rp)->resrec.rdata->u.ipv4.b[3] != octet[i])
            {
                LogInfo ("Octet %d compare failed %d != %d", i, (*rp)->resrec.rdata->u.ipv4.b[3], octet[i]);
                break;
            }
            rp = &(*rp)->next;
        }
        if (i != count) result = mStatus_Invalid;
    }
    else
    {
        LogInfo ("Cache group not found");
        result = mStatus_Invalid;
    }

    return result;
}
