/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2003 Apple Computer, 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.

    Contains:	mDNS platform plugin for VxWorks.

    Copyright:  Copyright (C) 2002-2004 Apple Computer, Inc., All Rights Reserved.

    Notes for non-Apple platforms:

        TARGET_NON_APPLE should be defined to 1 to avoid relying on Apple-only header files, macros, or functions.

    To Do:

        - Add support for IPv6 (needs VxWorks IPv6 support).
 */

// Set up the debug library to use the default category (see DebugServicesLite.h for details).

#if ( !TARGET_NON_APPLE )
    #define DEBUG_USE_DEFAULT_CATEGORY      1
#endif

#include    <stdarg.h>
#include    <stddef.h>
#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>

#include    <sys/types.h>
#include    <arpa/inet.h>
#include    <fcntl.h>
#include    <netinet/if_ether.h>
#include    <netinet/in.h>
#include    <netinet/ip.h>
#include    <sys/ioctl.h>
#include    <sys/socket.h>
#include    <unistd.h>

#include    "vxWorks.h"
#include    "ifLib.h"
#include    "inetLib.h"
#include    "pipeDrv.h"
#include    "selectLib.h"
#include    "semLib.h"
#include    "sockLib.h"
#include    "sysLib.h"
#include    "taskLib.h"
#include    "tickLib.h"

#include    "config.h"

#if ( !TARGET_NON_APPLE )
    #include    "ACP/ACPUtilities.h"
    #include    "Support/DebugServicesLite.h"
    #include    "Support/MiscUtilities.h"
#endif

#include    "mDNSEmbeddedAPI.h"

#include    "mDNSVxWorks.h"

#if 0
#pragma mark == Preprocessor ==
#endif

//===========================================================================================================================
//	Preprocessor
//===========================================================================================================================

#if ( !TARGET_NON_APPLE )
debug_log_new_default_category( mdns );
#endif

#if 0
#pragma mark == Constants ==
#endif

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

#define DEBUG_NAME                      "[mDNS] "

#define kMDNSDefaultName                "My-Device"

#define kMDNSTaskName                   "tMDNS"
#define kMDNSTaskPriority               102
#define kMDNSTaskStackSize              49152

#define kMDNSPipeName                   "/pipe/mDNS"
#define kMDNSPipeMessageQueueSize       32
#define kMDNSPipeMessageSize            1

#define kInvalidSocketRef               -1

typedef uint8_t MDNSPipeCommandCode;
enum
{
    kMDNSPipeCommandCodeInvalid         = 0,
    kMDNSPipeCommandCodeReschedule      = 1,
    kMDNSPipeCommandCodeReconfigure     = 2,
    kMDNSPipeCommandCodeQuit            = 3
};

#if 0
#pragma mark == Structures ==
#endif

//===========================================================================================================================
//	Structures
//===========================================================================================================================

typedef int MDNSSocketRef;

struct  MDNSInterfaceItem
{
    MDNSInterfaceItem *         next;
    char name[ 32 ];
    MDNSSocketRef multicastSocketRef;
    MDNSSocketRef sendingSocketRef;
    NetworkInterfaceInfo hostSet;
    mDNSBool hostRegistered;

    int sendMulticastCounter;
    int sendUnicastCounter;
    int sendErrorCounter;

    int recvCounter;
    int recvErrorCounter;
    int recvLoopCounter;
};

#if 0
#pragma mark == Macros ==
#endif

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

#if ( TARGET_NON_APPLE )

// Do-nothing versions of the debugging macros for non-Apple platforms.

    #define check(assertion)
    #define check_string( assertion, cstring )
    #define check_noerr(err)
    #define check_noerr_string( error, cstring )
    #define check_errno( assertion, errno_value )
    #define debug_string( cstring )
    #define require( assertion, label )                                     do { if( !(assertion) ) goto label;} while(0)
    #define require_string( assertion, label, string )                      require(assertion, label)
    #define require_quiet( assertion, label )                               require( assertion, label )
    #define require_noerr( error, label )                                   do { if( (error) != 0 ) goto label;} while(0)
    #define require_noerr_quiet( assertion, label )                         require_noerr( assertion, label )
    #define require_noerr_action( error, label, action )                    do { if( (error) != 0 ) { {action;}; goto label; } } while(0)
    #define require_noerr_action_quiet( assertion, label, action )          require_noerr_action( assertion, label, action )
    #define require_action( assertion, label, action )                      do { if( !(assertion) ) { {action;}; goto label; } } while(0)
    #define require_action_quiet( assertion, label, action )                require_action( assertion, label, action )
    #define require_action_string( assertion, label, action, cstring )      do { if( !(assertion) ) { {action;}; goto label; } } while(0)
    #define require_errno( assertion, errno_value, label )                  do { if( !(assertion) ) goto label;} while(0)
    #define require_errno_action( assertion, errno_value, label, action )   do { if( !(assertion) ) { {action;}; goto label; } } while(0)

    #define dlog( ARGS... )

    #define DEBUG_UNUSED( X )           (void)( X )
#endif

#if 0
#pragma mark == Prototypes ==
#endif

//===========================================================================================================================
//	Prototypes
//===========================================================================================================================

// ifIndexToIfp is in net/if.c, but not exported by net/if.h so provide it here.

extern struct ifnet * ifIndexToIfp(int ifIndex);

// Platform Internals

mDNSlocal void      SetupNames( mDNS * const inMDNS );
mDNSlocal mStatus   SetupInterfaceList( mDNS * const inMDNS );
mDNSlocal mStatus   TearDownInterfaceList( mDNS * const inMDNS );
mDNSlocal mStatus   SetupInterface( mDNS * const inMDNS, const struct ifaddrs *inAddr, MDNSInterfaceItem **outItem );
mDNSlocal mStatus   TearDownInterface( mDNS * const inMDNS, MDNSInterfaceItem *inItem );
mDNSlocal mStatus
SetupSocket(
    mDNS * const inMDNS,
    const struct ifaddrs *  inAddr,
    mDNSIPPort inPort,
    MDNSSocketRef *         outSocketRef );

// Commands

mDNSlocal mStatus   SetupCommandPipe( mDNS * const inMDNS );
mDNSlocal mStatus   TearDownCommandPipe( mDNS * const inMDNS );
mDNSlocal mStatus   SendCommand( const mDNS * const inMDNS, MDNSPipeCommandCode inCommandCode );
mDNSlocal mStatus   ProcessCommand( mDNS * const inMDNS );
mDNSlocal void      ProcessCommandReconfigure( mDNS *inMDNS );

// Threads

mDNSlocal mStatus   SetupTask( mDNS * const inMDNS );
mDNSlocal mStatus   TearDownTask( mDNS * const inMDNS );
mDNSlocal void      Task( mDNS *inMDNS );
mDNSlocal mStatus   TaskInit( mDNS *inMDNS );
mDNSlocal void      TaskSetupReadSet( mDNS *inMDNS, fd_set *outReadSet, int *outMaxSocket );
mDNSlocal void      TaskSetupTimeout( mDNS *inMDNS, mDNSs32 inNextTaskTime, struct timeval *outTimeout );
mDNSlocal void      TaskProcessPacket( mDNS *inMDNS, MDNSInterfaceItem *inItem, MDNSSocketRef inSocketRef );

// Utilities

#if ( TARGET_NON_APPLE )
mDNSlocal void  GenerateUniqueHostName( char *outName, long *ioSeed );
mDNSlocal void  GenerateUniqueDNSName( char *outName, long *ioSeed );
#endif

// Platform Accessors

#ifdef  __cplusplus
extern "C" {
#endif

typedef struct mDNSPlatformInterfaceInfo mDNSPlatformInterfaceInfo;
struct  mDNSPlatformInterfaceInfo
{
    const char *        name;
    mDNSAddr ip;
};

mDNSexport mStatus  mDNSPlatformInterfaceNameToID( mDNS * const inMDNS, const char *inName, mDNSInterfaceID *outID );
mDNSexport mStatus  mDNSPlatformInterfaceIDToInfo( mDNS * const inMDNS, mDNSInterfaceID inID, mDNSPlatformInterfaceInfo *outInfo );

#ifdef  __cplusplus
}
#endif

#if 0
#pragma mark == Globals ==
#endif

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

mDNSlocal mDNS *                    gMDNSPtr                            = NULL;
mDNSlocal mDNS_PlatformSupport gMDNSPlatformSupport;
mDNSlocal mDNSs32 gMDNSTicksToMicrosecondsMultiplier  = 0;

// Platform support

mDNSs32 mDNSPlatformOneSecond;

#if 0
#pragma mark -
#pragma mark == Public APIs ==
#endif

//===========================================================================================================================
//	mDNSReconfigure
//===========================================================================================================================

void    mDNSReconfigure( void )
{
    // Send a "reconfigure" command to the MDNS task.

    if( gMDNSPtr )
    {
        SendCommand( gMDNSPtr, kMDNSPipeCommandCodeReconfigure );
    }
}

#if 0
#pragma mark -
#pragma mark == Platform Support ==
#endif

//===========================================================================================================================
//	mDNSPlatformInit
//===========================================================================================================================

mStatus mDNSPlatformInit( mDNS * const inMDNS )
{
    mStatus err;

    dlog( kDebugLevelInfo, DEBUG_NAME "platform init\n" );

    // Initialize variables.

    mDNSPlatformMemZero( &gMDNSPlatformSupport, sizeof( gMDNSPlatformSupport ) );
    inMDNS->p                           = &gMDNSPlatformSupport;
    inMDNS->p->commandPipe              = ERROR;
    inMDNS->p->task                     = ERROR;
    inMDNS->p->rescheduled              = 1;        // Default to rescheduled until fully initialized.
    mDNSPlatformOneSecond               = sysClkRateGet();
    gMDNSTicksToMicrosecondsMultiplier  = ( 1000000L / mDNSPlatformOneSecond );

    // Allocate semaphores.

    inMDNS->p->lockID = semMCreate( SEM_Q_FIFO );
    require_action( inMDNS->p->lockID, exit, err = mStatus_NoMemoryErr );

    inMDNS->p->readyEvent = semBCreate( SEM_Q_FIFO, SEM_EMPTY );
    require_action( inMDNS->p->readyEvent, exit, err = mStatus_NoMemoryErr );

    inMDNS->p->quitEvent = semBCreate( SEM_Q_FIFO, SEM_EMPTY );
    require_action( inMDNS->p->quitEvent, exit, err = mStatus_NoMemoryErr );

    gMDNSPtr = inMDNS;

    // Set up the task and wait for it to initialize. Initialization is done from the task instead of here to avoid
    // stack space issues. Some of the initialization may require a larger stack than the current task supports.

    err = SetupTask( inMDNS );
    require_noerr( err, exit );

    err = semTake( inMDNS->p->readyEvent, WAIT_FOREVER );
    require_noerr( err, exit );
    err = inMDNS->p->taskInitErr;
    require_noerr( err, exit );

    mDNSCoreInitComplete( inMDNS, err );

exit:
    if( err )
    {
        mDNSPlatformClose( inMDNS );
    }
    dlog( kDebugLevelInfo, DEBUG_NAME "platform init done (err=%ld)\n", err );
    return( err );
}

//===========================================================================================================================
//	mDNSPlatformClose
//===========================================================================================================================

void    mDNSPlatformClose( mDNS * const inMDNS )
{
    mStatus err;

    dlog( kDebugLevelInfo, DEBUG_NAME "platform close\n" );
    check( inMDNS );

    // Tear everything down.

    err = TearDownTask( inMDNS );
    check_noerr( err );

    err = TearDownInterfaceList( inMDNS );
    check_noerr( err );

    err = TearDownCommandPipe( inMDNS );
    check_noerr( err );

    gMDNSPtr = NULL;

    // Release semaphores.

    if( inMDNS->p->quitEvent )
    {
        semDelete( inMDNS->p->quitEvent );
        inMDNS->p->quitEvent = 0;
    }
    if( inMDNS->p->readyEvent )
    {
        semDelete( inMDNS->p->readyEvent );
        inMDNS->p->readyEvent = 0;
    }
    if( inMDNS->p->lockID )
    {
        semDelete( inMDNS->p->lockID );
        inMDNS->p->lockID = 0;
    }

    dlog( kDebugLevelInfo, DEBUG_NAME "platform close done\n" );
}

//===========================================================================================================================
//	mDNSPlatformSendUDP
//===========================================================================================================================

mStatus
mDNSPlatformSendUDP(
    const mDNS * const inMDNS,
    const void * const inMsg,
    const mDNSu8 * const inMsgEnd,
    mDNSInterfaceID inInterfaceID,
    const mDNSAddr *            inDstIP,
    mDNSIPPort inDstPort )
{
    mStatus err;
    MDNSInterfaceItem *     item;
    struct sockaddr_in addr;
    int n;

    dlog( kDebugLevelChatty, DEBUG_NAME "platform send UDP\n" );

    // Check parameters.

    check( inMDNS );
    check( inMsg );
    check( inMsgEnd );
    check( inInterfaceID );
    check( inDstIP );
    if( inDstIP->type != mDNSAddrType_IPv4 )
    {
        err = mStatus_BadParamErr;
        goto exit;
    }

#if ( DEBUG )
    // Make sure the InterfaceID is valid.

    for( item = inMDNS->p->interfaceList; item; item = item->next )
    {
        if( item == (MDNSInterfaceItem *) inInterfaceID )
        {
            break;
        }
    }
    require_action( item, exit, err = mStatus_NoSuchNameErr );
#endif

    // Send the packet.

    item = (MDNSInterfaceItem *) inInterfaceID;
    check( item->sendingSocketRef != kInvalidSocketRef );

    mDNSPlatformMemZero( &addr, sizeof( addr ) );
    addr.sin_family         = AF_INET;
    addr.sin_port           = inDstPort.NotAnInteger;
    addr.sin_addr.s_addr    = inDstIP->ip.v4.NotAnInteger;

    n = inMsgEnd - ( (const mDNSu8 * const) inMsg );
    n = sendto( item->sendingSocketRef, (char *) inMsg, n, 0, (struct sockaddr *) &addr, sizeof( addr ) );
    check_errno( n, errno );

    item->sendErrorCounter      += ( n < 0 );
    item->sendMulticastCounter  += ( inDstPort.NotAnInteger == MulticastDNSPort.NotAnInteger );
    item->sendUnicastCounter    += ( inDstPort.NotAnInteger != MulticastDNSPort.NotAnInteger );

    dlog( kDebugLevelChatty, DEBUG_NAME "sent (to=%u.%u.%u.%u:%hu)\n",
          inDstIP->ip.v4.b[ 0 ], inDstIP->ip.v4.b[ 1 ], inDstIP->ip.v4.b[ 2 ], inDstIP->ip.v4.b[ 3 ],
          htons( inDstPort.NotAnInteger ) );
    err = mStatus_NoError;

exit:
    dlog( kDebugLevelChatty, DEBUG_NAME "platform send UDP done\n" );
    return( err );
}

//===========================================================================================================================
//	Connection-oriented (TCP) functions
//===========================================================================================================================

mDNSexport mStatus mDNSPlatformTCPConnect(const mDNSAddr *dst, mDNSOpaque16 dstport, mDNSInterfaceID InterfaceID,
                                          TCPConnectionCallback callback, void *context, int *descriptor)
{
    (void)dst;          // Unused
    (void)dstport;      // Unused
    (void)InterfaceID;  // Unused
    (void)callback;     // Unused
    (void)context;      // Unused
    (void)descriptor;   // Unused
    return(mStatus_UnsupportedErr);
}

mDNSexport void mDNSPlatformTCPCloseConnection(int sd)
{
    (void)sd;           // Unused
}

mDNSexport long mDNSPlatformReadTCP(int sd, void *buf, unsigned long buflen)
{
    (void)sd;           // Unused
    (void)buf;          // Unused
    (void)buflen;           // Unused
    return(0);
}

mDNSexport long mDNSPlatformWriteTCP(int sd, const char *msg, unsigned long len)
{
    (void)sd;           // Unused
    (void)msg;          // Unused
    (void)len;          // Unused
    return(0);
}

//===========================================================================================================================
//	mDNSPlatformLock
//===========================================================================================================================

void    mDNSPlatformLock( const mDNS * const inMDNS )
{
    check( inMDNS->p->lockID );

    if( inMDNS->p->lockID )
    {
        #if ( TARGET_NON_APPLE )
        semTake( inMDNS->p->lockID, WAIT_FOREVER );
        #else
        semTakeDeadlockDetect( inMDNS->p->lockID, WAIT_FOREVER );
        #endif
    }
}

//===========================================================================================================================
//	mDNSPlatformUnlock
//===========================================================================================================================

void    mDNSPlatformUnlock( const mDNS * const inMDNS )
{
    check( inMDNS );
    check( inMDNS->p );
    check( inMDNS->p->lockID );
    check_string( inMDNS->p->task != ERROR, "mDNS task not started" );

    // When an API routine is called, "m->NextScheduledEvent" is reset to "timenow" before calling mDNSPlatformUnlock()
    // Since our main mDNS_Execute() loop is on a different thread, we need to wake up that thread to:
    // (a) handle immediate work (if any) resulting from this API call
    // (b) calculate the next sleep time between now and the next interesting event

    if( ( mDNS_TimeNow(inMDNS) - inMDNS->NextScheduledEvent ) >= 0 )
    {
        // We only need to send the reschedule event when called from a task other than the mDNS task since if we are
        // called from mDNS task, we'll loop back and call mDNS_Execute. This avoids filling up the command queue.

        if( ( inMDNS->p->rescheduled++ == 0 ) && ( taskIdSelf() != inMDNS->p->task ) )
        {
            SendCommand( inMDNS, kMDNSPipeCommandCodeReschedule );
        }
    }

    if( inMDNS->p->lockID )
    {
        semGive( inMDNS->p->lockID );
    }
}

//===========================================================================================================================
//	mDNSPlatformStrLen
//===========================================================================================================================

mDNSu32  mDNSPlatformStrLen( const void *inSrc )
{
    check( inSrc );

    return( (mDNSu32) strlen( (const char *) inSrc ) );
}

//===========================================================================================================================
//	mDNSPlatformStrCopy
//===========================================================================================================================

void    mDNSPlatformStrCopy( void *inDst, const void *inSrc )
{
    check( inSrc );
    check( inDst );

    strcpy( (char *) inDst, (const char*) inSrc );
}

//===========================================================================================================================
//	mDNSPlatformMemCopy
//===========================================================================================================================

void    mDNSPlatformMemCopy( void *inDst, const void *inSrc, mDNSu32 inSize )
{
    check( inSrc );
    check( inDst );

    memcpy( inDst, inSrc, inSize );
}

//===========================================================================================================================
//	mDNSPlatformMemSame
//===========================================================================================================================

mDNSBool    mDNSPlatformMemSame( const void *inDst, const void *inSrc, mDNSu32 inSize )
{
    check( inSrc );
    check( inDst );

    return( memcmp( inSrc, inDst, inSize ) == 0 );
}

//===========================================================================================================================
//	mDNSPlatformMemZero
//===========================================================================================================================

void    mDNSPlatformMemZero( void *inDst, mDNSu32 inSize )
{
    check( inDst );

    memset( inDst, 0, inSize );
}

//===========================================================================================================================
//	mDNSPlatformMemAllocate
//===========================================================================================================================

mDNSexport void *   mDNSPlatformMemAllocate( mDNSu32 inSize )
{
    void *      mem;

    check( inSize > 0 );

    mem = malloc( inSize );
    check( mem );

    return( mem );
}

//===========================================================================================================================
//	mDNSPlatformMemFree
//===========================================================================================================================

mDNSexport void mDNSPlatformMemFree( void *inMem )
{
    check( inMem );

    free( inMem );
}

//===========================================================================================================================
//	mDNSPlatformRandomSeed
//===========================================================================================================================

mDNSexport mDNSu32 mDNSPlatformRandomSeed(void)
{
    return( tickGet() );
}

//===========================================================================================================================
//	mDNSPlatformTimeInit
//===========================================================================================================================

mDNSexport mStatus mDNSPlatformTimeInit( void )
{
    // No special setup is required on VxWorks -- we just use tickGet().
    return( mStatus_NoError );
}

//===========================================================================================================================
//	mDNSPlatformRawTime
//===========================================================================================================================

mDNSs32 mDNSPlatformRawTime( void )
{
    return( (mDNSs32) tickGet() );
}

//===========================================================================================================================
//	mDNSPlatformUTC
//===========================================================================================================================

mDNSexport mDNSs32  mDNSPlatformUTC( void )
{
    return( -1 );
}

//===========================================================================================================================
//	mDNSPlatformInterfaceNameToID
//===========================================================================================================================

mStatus mDNSPlatformInterfaceNameToID( mDNS * const inMDNS, const char *inName, mDNSInterfaceID *outID )
{
    mStatus err;
    MDNSInterfaceItem *     ifd;

    check( inMDNS );
    check( inMDNS->p );
    check( inName );

    // Search for an interface with the specified name,

    for( ifd = inMDNS->p->interfaceList; ifd; ifd = ifd->next )
    {
        if( strcmp( ifd->name, inName ) == 0 )
        {
            break;
        }
    }
    if( !ifd )
    {
        err = mStatus_NoSuchNameErr;
        goto exit;
    }

    // Success!

    if( outID )
    {
        *outID = (mDNSInterfaceID) ifd;
    }
    err = mStatus_NoError;

exit:
    return( err );
}

//===========================================================================================================================
//	mDNSPlatformInterfaceIDToInfo
//===========================================================================================================================

mStatus mDNSPlatformInterfaceIDToInfo( mDNS * const inMDNS, mDNSInterfaceID inID, mDNSPlatformInterfaceInfo *outInfo )
{
    mStatus err;
    MDNSInterfaceItem *     ifd;

    check( inMDNS );
    check( inID );
    check( outInfo );

    // Search for an interface with the specified ID,

    for( ifd = inMDNS->p->interfaceList; ifd; ifd = ifd->next )
    {
        if( ifd == (MDNSInterfaceItem *) inID )
        {
            break;
        }
    }
    if( !ifd )
    {
        err = mStatus_NoSuchNameErr;
        goto exit;
    }

    // Success!

    outInfo->name   = ifd->name;
    outInfo->ip     = ifd->hostSet.ip;
    err             = mStatus_NoError;

exit:
    return( err );
}

//===========================================================================================================================
//	debugf_
//===========================================================================================================================

#if ( MDNS_DEBUGMSGS )
mDNSexport void debugf_( const char *format, ... )
{
    char buffer[ 512 ];
    va_list args;
    mDNSu32 length;

    va_start( args, format );
    length = mDNS_vsnprintf( buffer, sizeof( buffer ), format, args );
    va_end( args );

    dlog( kDebugLevelInfo, "%s\n", buffer );
}
#endif

//===========================================================================================================================
//	verbosedebugf_
//===========================================================================================================================

#if ( MDNS_DEBUGMSGS > 1 )
mDNSexport void verbosedebugf_( const char *format, ... )
{
    char buffer[ 512 ];
    va_list args;
    mDNSu32 length;

    va_start( args, format );
    length = mDNS_vsnprintf( buffer, sizeof( buffer ), format, args );
    va_end( args );

    dlog( kDebugLevelVerbose, "%s\n", buffer );
}
#endif

//===========================================================================================================================
//	LogMsg
//===========================================================================================================================

void LogMsg( const char *inFormat, ... )
{
    char buffer[ 512 ];
    va_list args;
    mDNSu32 length;

    va_start( args, inFormat );
    length = mDNS_vsnprintf( buffer, sizeof( buffer ), inFormat, args );
    va_end( args );

    dlog( kDebugLevelWarning, "%s\n", buffer );
}

#if 0
#pragma mark -
#pragma mark == Platform Internals ==
#endif

//===========================================================================================================================
//	SetupNames
//===========================================================================================================================

mDNSlocal void  SetupNames( mDNS * const inMDNS )
{
    char tempCString[ 128 ];
    mDNSu8 tempPString[ 128 ];
    mDNSu8 *        namePtr;

    // Set up the host name.

    tempCString[ 0 ] = '\0';
    GenerateUniqueHostName( tempCString, NULL );
    check( tempCString[ 0 ] != '\0' );
    if( tempCString[ 0 ] == '\0' )
    {
        // No name so use the default.

        strcpy( tempCString, kMDNSDefaultName );
    }
    inMDNS->nicelabel.c[ 0 ] = strlen( tempCString );
    memcpy( &inMDNS->nicelabel.c[ 1 ], tempCString, inMDNS->nicelabel.c[ 0 ] );
    check( inMDNS->nicelabel.c[ 0 ] > 0 );

    // Set up the DNS name.

    tempCString[ 0 ] = '\0';
    GenerateUniqueDNSName( tempCString, NULL );
    if( tempCString[ 0 ] != '\0' )
    {
        tempPString[ 0 ] = strlen( tempCString );
        memcpy( &tempPString[ 1 ], tempCString, tempPString[ 0 ] );
        namePtr = tempPString;
    }
    else
    {
        // No DNS name so use the host name.

        namePtr = inMDNS->nicelabel.c;
    }
    ConvertUTF8PstringToRFC1034HostLabel( namePtr, &inMDNS->hostlabel );
    if( inMDNS->hostlabel.c[ 0 ] == 0 )
    {
        // Nice name has no characters that are representable as an RFC 1034 name (e.g. Japanese) so use the default.

        MakeDomainLabelFromLiteralString( &inMDNS->hostlabel, kMDNSDefaultName );
    }
    check( inMDNS->hostlabel.c[ 0 ] > 0 );

    mDNS_SetFQDN( inMDNS );

    dlog( kDebugLevelInfo, DEBUG_NAME "nice name \"%.*s\"\n", inMDNS->nicelabel.c[ 0 ], &inMDNS->nicelabel.c[ 1 ] );
    dlog( kDebugLevelInfo, DEBUG_NAME "host name \"%.*s\"\n", inMDNS->hostlabel.c[ 0 ], &inMDNS->hostlabel.c[ 1 ] );
}

//===========================================================================================================================
//	SetupInterfaceList
//===========================================================================================================================

mDNSlocal mStatus   SetupInterfaceList( mDNS * const inMDNS )
{
    mStatus err;
    struct ifaddrs *            addrs;
    struct ifaddrs *            p;
    uint32_t flagMask;
    uint32_t flagTest;
    MDNSInterfaceItem **        next;
    MDNSInterfaceItem *         item;

    addrs = NULL;

    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up interface list\n" );
    check( inMDNS );

    // Tear down any existing interfaces that may be set up.

    TearDownInterfaceList( inMDNS );
    inMDNS->p->interfaceList = NULL;
    next = &inMDNS->p->interfaceList;

    // Set up each interface that is active, multicast-capable, and not the loopback interface or point-to-point.

    flagMask = IFF_UP | IFF_MULTICAST | IFF_LOOPBACK | IFF_POINTOPOINT;
    flagTest = IFF_UP | IFF_MULTICAST;

    err = getifaddrs( &addrs );
    require_noerr( err, exit );

    for( p = addrs; p; p = p->ifa_next )
    {
        if( ( p->ifa_flags & flagMask ) == flagTest )
        {
            err = SetupInterface( inMDNS, p, &item );
            require_noerr( err, exit );

            *next = item;
            next = &item->next;
        }
    }
    err = mStatus_NoError;

exit:
    if( addrs )
    {
        freeifaddrs( addrs );
    }
    if( err )
    {
        TearDownInterfaceList( inMDNS );
    }
    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up interface list done (err=%ld)\n", err );
    return( err );
}

//===========================================================================================================================
//	TearDownInterfaceList
//===========================================================================================================================

mDNSlocal mStatus   TearDownInterfaceList( mDNS * const inMDNS )
{
    dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down interface list\n" );
    check( inMDNS );

    // Tear down all the interfaces.

    while( inMDNS->p->interfaceList )
    {
        MDNSInterfaceItem *     item;

        item = inMDNS->p->interfaceList;
        inMDNS->p->interfaceList = item->next;

        TearDownInterface( inMDNS, item );
    }

    dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down interface list done\n" );
    return( mStatus_NoError );
}

//===========================================================================================================================
//	SetupInterface
//===========================================================================================================================

mDNSlocal mStatus   SetupInterface( mDNS * const inMDNS, const struct ifaddrs *inAddr, MDNSInterfaceItem **outItem )
{
    mStatus err;
    MDNSInterfaceItem *             item;
    MDNSSocketRef socketRef;
    const struct sockaddr_in *      ipv4, *mask;

    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up interface (name=%s)\n", inAddr->ifa_name );
    check( inMDNS );
    check( inAddr );
    check( inAddr->ifa_addr );
    ipv4 = (const struct sockaddr_in *) inAddr->ifa_addr;
    mask = (const struct sockaddr_in *) inAddr->ifa_netmask;
    check( outItem );

    // Allocate memory for the info item.

    item = (MDNSInterfaceItem *) calloc( 1, sizeof( *item ) );
    require_action( item, exit, err = mStatus_NoMemoryErr );
    strcpy( item->name, inAddr->ifa_name );
    item->multicastSocketRef    = kInvalidSocketRef;
    item->sendingSocketRef      = kInvalidSocketRef;

    // Set up the multicast DNS (port 5353) socket for this interface.

    err = SetupSocket( inMDNS, inAddr, MulticastDNSPort, &socketRef );
    require_noerr( err, exit );
    item->multicastSocketRef = socketRef;

    // Set up the sending socket for this interface.

    err = SetupSocket( inMDNS, inAddr, zeroIPPort, &socketRef );
    require_noerr( err, exit );
    item->sendingSocketRef = socketRef;

    // Register this interface with mDNS.

    item->hostSet.InterfaceID             = (mDNSInterfaceID) item;
    item->hostSet.ip.type               = mDNSAddrType_IPv4;
    item->hostSet.ip.ip.v4.NotAnInteger = ipv4->sin_addr.s_addr;
    item->hostSet.mask.type               = mDNSAddrType_IPv4;
    item->hostSet.mask.ip.v4.NotAnInteger = mask->sin_addr.s_addr;
    item->hostSet.ifname[0]               = 0;
    item->hostSet.Advertise               = inMDNS->AdvertiseLocalAddresses;
    item->hostSet.McastTxRx               = mDNStrue;

    err = mDNS_RegisterInterface( inMDNS, &item->hostSet, NormalActivation );
    require_noerr( err, exit );
    item->hostRegistered = mDNStrue;

    dlog( kDebugLevelInfo, DEBUG_NAME "Registered IP address: %u.%u.%u.%u\n",
          item->hostSet.ip.ip.v4.b[ 0 ], item->hostSet.ip.ip.v4.b[ 1 ],
          item->hostSet.ip.ip.v4.b[ 2 ], item->hostSet.ip.ip.v4.b[ 3 ] );

    // Success!

    *outItem = item;
    item = NULL;

exit:
    if( item )
    {
        TearDownInterface( inMDNS, item );
    }
    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up interface done (name=%s, err=%ld)\n", inAddr->ifa_name, err );
    return( err );
}

//===========================================================================================================================
//	TearDownInterface
//===========================================================================================================================

mDNSlocal mStatus   TearDownInterface( mDNS * const inMDNS, MDNSInterfaceItem *inItem )
{
    MDNSSocketRef socketRef;

    check( inMDNS );
    check( inItem );

    // Deregister this interface with mDNS.

    dlog( kDebugLevelInfo, DEBUG_NAME "Deregistering IP address: %u.%u.%u.%u\n",
          inItem->hostSet.ip.ip.v4.b[ 0 ], inItem->hostSet.ip.ip.v4.b[ 1 ],
          inItem->hostSet.ip.ip.v4.b[ 2 ], inItem->hostSet.ip.ip.v4.b[ 3 ] );

    if( inItem->hostRegistered )
    {
        inItem->hostRegistered = mDNSfalse;
        mDNS_DeregisterInterface( inMDNS, &inItem->hostSet, NormalActivation );
    }

    // Close the multicast socket.

    socketRef = inItem->multicastSocketRef;
    inItem->multicastSocketRef = kInvalidSocketRef;
    if( socketRef != kInvalidSocketRef )
    {
        dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down multicast socket %d\n", socketRef );
        close( socketRef );
    }

    // Close the sending socket.

    socketRef = inItem->sendingSocketRef;
    inItem->sendingSocketRef = kInvalidSocketRef;
    if( socketRef != kInvalidSocketRef )
    {
        dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down sending socket %d\n", socketRef );
        close( socketRef );
    }

    // Free the memory used by the interface info.

    free( inItem );
    return( mStatus_NoError );
}

//===========================================================================================================================
//	SetupSocket
//===========================================================================================================================

mDNSlocal mStatus
SetupSocket(
    mDNS * const inMDNS,
    const struct ifaddrs *  inAddr,
    mDNSIPPort inPort,
    MDNSSocketRef *         outSocketRef  )
{
    mStatus err;
    MDNSSocketRef socketRef;
    int option;
    unsigned char optionByte;
    struct ip_mreq mreq;
    const struct sockaddr_in *      ipv4;
    struct sockaddr_in addr;
    mDNSv4Addr ip;

    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up socket done\n" );
    check( inMDNS );
    check( inAddr );
    check( inAddr->ifa_addr );
    ipv4 = (const struct sockaddr_in *) inAddr->ifa_addr;
    check( outSocketRef );

    // Set up a UDP socket for multicast DNS.

    socketRef = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
    require_errno_action( socketRef, errno, exit, err = mStatus_UnknownErr );

    // A port of zero means this socket is for sending and should be set up for sending. Otherwise, it is for receiving
    // and should be set up for receiving. The reason for separate sending vs receiving sockets is to workaround problems
    // with VxWorks IP stack when using dynamic IP configuration such as DHCP (problems binding to wildcard IP when the
    // IP address later changes). Since we have to bind the Multicast DNS address to workaround these issues we have to
    // use a separate sending socket since it is illegal to send a packet with a multicast source address (RFC 1122).

    if( inPort.NotAnInteger != zeroIPPort.NotAnInteger )
    {
        // Turn on reuse port option so multiple servers can listen for Multicast DNS packets.

        option = 1;
        err = setsockopt( socketRef, SOL_SOCKET, SO_REUSEADDR, (char *) &option, sizeof( option ) );
        check_errno( err, errno );

        // Join the all-DNS multicast group so we receive Multicast DNS packets.

        ip.NotAnInteger             = ipv4->sin_addr.s_addr;
        mreq.imr_multiaddr.s_addr   = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
        mreq.imr_interface.s_addr   = ip.NotAnInteger;
        err = setsockopt( socketRef, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof( mreq ) );
        check_errno( err, errno );

        // Bind to the multicast DNS address and port 5353.

        mDNSPlatformMemZero( &addr, sizeof( addr ) );
        addr.sin_family         = AF_INET;
        addr.sin_port           = inPort.NotAnInteger;
        addr.sin_addr.s_addr    = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
        err = bind( socketRef, (struct sockaddr *) &addr, sizeof( addr ) );
        check_errno( err, errno );

        dlog( kDebugLevelVerbose, DEBUG_NAME "setting up socket done (%s, %u.%u.%u.%u:%u, %d)\n",
              inAddr->ifa_name, ip.b[ 0 ], ip.b[ 1 ], ip.b[ 2 ], ip.b[ 3 ], ntohs( inPort.NotAnInteger ), socketRef );
    }
    else
    {
        // Bind to the interface address and multicast DNS port.

        ip.NotAnInteger         = ipv4->sin_addr.s_addr;
        mDNSPlatformMemZero( &addr, sizeof( addr ) );
        addr.sin_family         = AF_INET;
        addr.sin_port           = MulticastDNSPort.NotAnInteger;
        addr.sin_addr.s_addr    = ip.NotAnInteger;
        err = bind( socketRef, (struct sockaddr *) &addr, sizeof( addr ) );
        check_errno( err, errno );

        // Direct multicast packets to the specified interface.

        addr.sin_addr.s_addr = ip.NotAnInteger;
        err = setsockopt( socketRef, IPPROTO_IP, IP_MULTICAST_IF, (char *) &addr.sin_addr, sizeof( addr.sin_addr ) );
        check_errno( err, errno );

        // Set the TTL of outgoing unicast packets to 255 (helps against spoofing).

        option = 255;
        err = setsockopt( socketRef, IPPROTO_IP, IP_TTL, (char *) &option, sizeof( option ) );
        check_errno( err, errno );

        // Set the TTL of outgoing multicast packets to 255 (helps against spoofing).

        optionByte = 255;
        err = setsockopt( socketRef, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &optionByte, sizeof( optionByte ) );
        check_errno( err, errno );

        // WARNING: Setting this option causes unicast responses to be routed to the wrong interface so they are
        // WARNING: disabled. These options were only hints to improve 802.11 performance (and not implemented) anyway.

#if 0
        // Mark packets as high-throughput/low-delay (i.e. lowest reliability) to maximize 802.11 multicast rate.

        option = IPTOS_LOWDELAY | IPTOS_THROUGHPUT;
        err = setsockopt( socketRef, IPPROTO_IP, IP_TOS, (char *) &option, sizeof( option ) );
        check_errno( err, errno );
#endif

        dlog( kDebugLevelVerbose, DEBUG_NAME "setting up sending socket done (%s, %u.%u.%u.%u, %d)\n",
              inAddr->ifa_name, ip.b[ 0 ], ip.b[ 1 ], ip.b[ 2 ], ip.b[ 3 ], socketRef );
    }

    // Success!

    *outSocketRef = socketRef;
    socketRef = kInvalidSocketRef;
    err = mStatus_NoError;

exit:
    if( socketRef != kInvalidSocketRef )
    {
        close( socketRef );
    }
    return( err );
}

#if 0
#pragma mark -
#pragma mark == Commands ==
#endif

//===========================================================================================================================
//	SetupCommandPipe
//===========================================================================================================================

mDNSlocal mStatus   SetupCommandPipe( mDNS * const inMDNS )
{
    mStatus err;

    // Clean up any leftover command pipe.

    TearDownCommandPipe( inMDNS );

    // Create the pipe device and open it.

    pipeDevCreate( kMDNSPipeName, kMDNSPipeMessageQueueSize, kMDNSPipeMessageSize );

    inMDNS->p->commandPipe = open( kMDNSPipeName, O_RDWR, 0 );
    require_errno_action( inMDNS->p->commandPipe, errno, exit, err = mStatus_UnsupportedErr );

    err = mStatus_NoError;

exit:
    return( err );
}

//===========================================================================================================================
//	TearDownCommandPipe
//===========================================================================================================================

mDNSlocal mStatus   TearDownCommandPipe( mDNS * const inMDNS )
{
    if( inMDNS->p->commandPipe != ERROR )
    {
        close( inMDNS->p->commandPipe );
#ifdef _WRS_VXWORKS_5_X
        // pipeDevDelete is not defined in older versions of VxWorks
        pipeDevDelete( kMDNSPipeName, FALSE );
#endif
        inMDNS->p->commandPipe = ERROR;
    }
    return( mStatus_NoError );
}

//===========================================================================================================================
//	SendCommand
//===========================================================================================================================

mDNSlocal mStatus   SendCommand( const mDNS * const inMDNS, MDNSPipeCommandCode inCommandCode )
{
    mStatus err;

    require_action( inMDNS->p->commandPipe != ERROR, exit, err = mStatus_NotInitializedErr );

    err = write( inMDNS->p->commandPipe, &inCommandCode, sizeof( inCommandCode ) );
    require_errno( err, errno, exit );

    err = mStatus_NoError;

exit:
    return( err );
}

//===========================================================================================================================
//	ProcessCommand
//===========================================================================================================================

mDNSlocal mStatus   ProcessCommand( mDNS * const inMDNS )
{
    mStatus err;
    MDNSPipeCommandCode commandCode;

    require_action( inMDNS->p->commandPipe != ERROR, exit, err = mStatus_NotInitializedErr );

    // Read the command code from the pipe and dispatch it.

    err = read( inMDNS->p->commandPipe, &commandCode, sizeof( commandCode ) );
    require_errno( err, errno, exit );

    switch( commandCode )
    {
    case kMDNSPipeCommandCodeReschedule:

        // Reschedule event. Do nothing here, but this will cause mDNS_Execute to run before waiting again.

        dlog( kDebugLevelChatty, DEBUG_NAME "reschedule\n" );
        break;

    case kMDNSPipeCommandCodeReconfigure:
        ProcessCommandReconfigure( inMDNS );
        break;

    case kMDNSPipeCommandCodeQuit:

        // Quit requested. Set quit flag and bump the config ID to let the thread exit normally.

        dlog( kDebugLevelVerbose, DEBUG_NAME "processing pipe quit command\n" );
        inMDNS->p->quit = mDNStrue;
        ++inMDNS->p->configID;
        break;

    default:
        dlog( kDebugLevelError, DEBUG_NAME "unknown pipe command code (code=0x%08X)\n", commandCode );
        err = mStatus_BadParamErr;
        goto exit;
        break;
    }
    err = mStatus_NoError;

exit:
    return( err );
}

//===========================================================================================================================
//	ProcessCommandReconfigure
//===========================================================================================================================

mDNSlocal void  ProcessCommandReconfigure( mDNS *inMDNS )
{
    mStatus err;

    dlog( kDebugLevelVerbose, DEBUG_NAME "processing pipe reconfigure command\n" );

    // Tear down the existing interfaces and set up new ones using the new IP info.

    mDNSPlatformLock( inMDNS );

    err = TearDownInterfaceList( inMDNS );
    check_noerr( err );

    err = SetupInterfaceList( inMDNS );
    check_noerr( err );

    mDNSPlatformUnlock( inMDNS );

    // Inform clients of the change.

    mDNS_ConfigChanged(m);

    // Force mDNS to update.

    mDNSCoreMachineSleep( inMDNS, mDNSfalse ); // What is this for? Mac OS X does not do this

    // Bump the config ID so the main processing loop detects the configuration change.

    ++inMDNS->p->configID;
}

#if 0
#pragma mark -
#pragma mark == Threads ==
#endif

//===========================================================================================================================
//	SetupTask
//===========================================================================================================================

mDNSlocal mStatus   SetupTask( mDNS * const inMDNS )
{
    mStatus err;
    int task;

    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up thread\n" );
    check( inMDNS );

    // Create our main thread. Note: The task will save off its ID in the globals. We cannot do it here because the
    // task invokes code that needs it and the task may begin execution before taskSpawn returns the task ID.
    // This also means code in this thread context cannot rely on the task ID until the task has fully initialized.

    task = taskSpawn( kMDNSTaskName, kMDNSTaskPriority, 0, kMDNSTaskStackSize, (FUNCPTR) Task,
                      (int) inMDNS, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
    require_action( task != ERROR, exit, err = mStatus_NoMemoryErr );

    err = mStatus_NoError;

exit:
    dlog( kDebugLevelVerbose, DEBUG_NAME "setting up thread done (err=%ld, id=%d)\n", err, task );
    return( err );
}

//===========================================================================================================================
//	TearDownTask
//===========================================================================================================================

mDNSlocal mStatus   TearDownTask( mDNS * const inMDNS )
{
    mStatus err;

    dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down thread\n" );
    check( inMDNS );

    // Send a quit command to cause the thread to exit.

    SendCommand( inMDNS, kMDNSPipeCommandCodeQuit );

    // Wait for the thread to signal it has exited. Timeout in 10 seconds to handle a hung thread.

    if( inMDNS->p->quitEvent )
    {
        err = semTake( inMDNS->p->quitEvent, sysClkRateGet() * 10 );
        check_noerr( err );
    }
    err = mStatus_NoError;

    dlog( kDebugLevelVerbose, DEBUG_NAME "tearing down thread done (err=%ld)\n", err );
    return( err );
}

//===========================================================================================================================
//	Task
//===========================================================================================================================

mDNSlocal void  Task( mDNS *inMDNS )
{
    mStatus err;
    fd_set allReadSet;
    MDNSInterfaceItem *     item;
    int maxSocket;
    long configID;
    struct timeval timeout;

    dlog( kDebugLevelVerbose, DEBUG_NAME "task starting\n" );
    check( inMDNS );

    // Set up everything up.

    err = TaskInit( inMDNS );
    require_noerr( err, exit );

    // Main Processing Loop.

    while( !inMDNS->p->quit )
    {
        // Set up the read set here to avoid the overhead of setting it up each iteration of the main processing loop.
        // If the configuration changes, the server ID will be bumped, causing this code to set up the read set again.

        TaskSetupReadSet( inMDNS, &allReadSet, &maxSocket );
        configID = inMDNS->p->configID;
        dlog( kDebugLevelVerbose, DEBUG_NAME "task starting processing loop (configID=%ld)\n", configID );

        while( configID == inMDNS->p->configID )
        {
            mDNSs32 nextTaskTime;
            fd_set readSet;
            int n;

            // Give the mDNS core a chance to do its work. Reset the rescheduled flag before calling mDNS_Execute
            // so anything that needs processing during or after causes a re-schedule to wake up the thread. The
            // reschedule flag is set to 1 after processing a waking up to prevent redundant reschedules while
            // processing packets. This introduces a window for a race condition because the thread wake-up and
            // reschedule set are not atomic, but this would be benign. Even if the reschedule flag is "corrupted"
            // like this, it would only result in a redundant reschedule since it will loop back to mDNS_Execute.

            inMDNS->p->rescheduled = 0;
            nextTaskTime = mDNS_Execute( inMDNS );
            TaskSetupTimeout( inMDNS, nextTaskTime, &timeout );

            // Wait until something occurs (e.g. command, incoming packet, or timeout).

            readSet = allReadSet;
            n = select( maxSocket + 1, &readSet, NULL, NULL, &timeout );
            inMDNS->p->rescheduled = 1;
            check_errno( n, errno );
            dlog( kDebugLevelChatty - 1, DEBUG_NAME "task select result = %d\n", n );
            if( n == 0 )
            {
                // Next task timeout occurred. Loop back up to give mDNS core a chance to work.

                dlog( kDebugLevelChatty, DEBUG_NAME "next task timeout occurred (%ld)\n", mDNS_TimeNow(inMDNS) );
                continue;
            }

            // Scan the read set to determine if any sockets have something pending and process them.

            n = 0;
            for( item = inMDNS->p->interfaceList; item; item = item->next )
            {
                if( FD_ISSET( item->multicastSocketRef, &readSet ) )
                {
                    TaskProcessPacket( inMDNS, item, item->multicastSocketRef );
                    ++n;
                }
            }

            // Check for a pending command and process it.

            if( FD_ISSET( inMDNS->p->commandPipe, &readSet ) )
            {
                ProcessCommand( inMDNS );
                ++n;
            }
            check( n > 0 );
        }
    }

exit:
    // Signal we've quit.

    check( inMDNS->p->quitEvent );
    semGive( inMDNS->p->quitEvent );

    dlog( kDebugLevelInfo, DEBUG_NAME "task ended\n" );
}

//===========================================================================================================================
//	TaskInit
//===========================================================================================================================

mDNSlocal mStatus   TaskInit( mDNS *inMDNS )
{
    mStatus err;

    dlog( kDebugLevelVerbose, DEBUG_NAME "task init\n" );
    check( inMDNS->p->readyEvent );

    inMDNS->p->task = taskIdSelf();

    err = SetupCommandPipe( inMDNS );
    require_noerr( err, exit );

    SetupNames( inMDNS );

    err = SetupInterfaceList( inMDNS );
    require_noerr( err, exit );

exit:
    // Signal the "ready" semaphore to indicate the task initialization code has completed (success or not).

    inMDNS->p->taskInitErr = err;
    semGive( inMDNS->p->readyEvent );

    dlog( kDebugLevelVerbose, DEBUG_NAME "task init done (err=%ld)\n", err );
    return( err );
}

//===========================================================================================================================
//	TaskSetupReadSet
//===========================================================================================================================

mDNSlocal void  TaskSetupReadSet( mDNS *inMDNS, fd_set *outReadSet, int *outMaxSocket )
{
    MDNSInterfaceItem *     item;
    int maxSocket;

    dlog( kDebugLevelVerbose, DEBUG_NAME "task setting up read set\n" );
    check( inMDNS );
    check( outReadSet );
    check( outMaxSocket );

    // Initialize the read set. Default the max socket to -1 so "maxSocket + 1" (as needed by select) is zero. This
    // should never happen since we should always have at least one interface, but it's just to be safe.

    FD_ZERO( outReadSet );
    maxSocket = -1;

    // Add all the receiving sockets to the read set.

    for( item = inMDNS->p->interfaceList; item; item = item->next )
    {
        FD_SET( item->multicastSocketRef, outReadSet );
        if( item->multicastSocketRef > maxSocket )
        {
            maxSocket = item->multicastSocketRef;
        }
    }

    // Add the command pipe to the read set.

    FD_SET( inMDNS->p->commandPipe, outReadSet );
    if( inMDNS->p->commandPipe > maxSocket )
    {
        maxSocket = inMDNS->p->commandPipe;
    }
    check( maxSocket > 0 );
    *outMaxSocket = maxSocket;

    dlog( kDebugLevelVerbose, DEBUG_NAME "task setting up read set done (maxSocket=%d)\n", maxSocket );
}

//===========================================================================================================================
//	TaskSetupTimeout
//===========================================================================================================================

mDNSlocal void  TaskSetupTimeout( mDNS *inMDNS, mDNSs32 inNextTaskTime, struct timeval *outTimeout )
{
    mDNSs32 delta;

    // Calculate how long to wait before performing idle processing.

    delta = inNextTaskTime - mDNS_TimeNow(inMDNS);
    if( delta <= 0 )
    {
        // The next task time is now or in the past. Set the timeout to fire immediately.

        outTimeout->tv_sec  = 0;
        outTimeout->tv_usec = 0;
    }
    else
    {
        // Calculate the seconds and microseconds until the timeout should occur. Add one to the ticks remainder
        // before multiplying to account for integer rounding error and avoid firing the timeout too early.

        outTimeout->tv_sec  = delta / mDNSPlatformOneSecond;
        outTimeout->tv_usec = ( ( delta % mDNSPlatformOneSecond ) + 1 ) * gMDNSTicksToMicrosecondsMultiplier;

        // Check if the microseconds is more than 1 second. If so, bump the seconds instead.

        if( outTimeout->tv_usec >= 1000000L )
        {
            outTimeout->tv_sec += 1;
            outTimeout->tv_usec = 0;
        }
    }

    dlog( kDebugLevelChatty, DEBUG_NAME "next task in %ld:%ld seconds (%ld)\n",
          outTimeout->tv_sec, outTimeout->tv_usec, inNextTaskTime );
}
//===========================================================================================================================
//	TaskProcessPacket
//===========================================================================================================================

mDNSlocal void  TaskProcessPacket( mDNS *inMDNS, MDNSInterfaceItem *inItem, MDNSSocketRef inSocketRef )
{
    int n;
    DNSMessage packet;
    struct sockaddr_in addr;
    int addrSize;
    mDNSu8 *                packetEndPtr;
    mDNSAddr srcAddr;
    mDNSIPPort srcPort;
    mDNSAddr dstAddr;
    mDNSIPPort dstPort;

    // Receive the packet.

    addrSize = sizeof( addr );
    n = recvfrom( inSocketRef, (char *) &packet, sizeof( packet ), 0, (struct sockaddr *) &addr, &addrSize );
    check( n >= 0 );
    if( n >= 0 )
    {
        // Set up the src/dst/interface info.

        srcAddr.type                = mDNSAddrType_IPv4;
        srcAddr.ip.v4.NotAnInteger  = addr.sin_addr.s_addr;
        srcPort.NotAnInteger        = addr.sin_port;
        dstAddr.type                = mDNSAddrType_IPv4;
        dstAddr.ip.v4               = AllDNSLinkGroup_v4.ip.v4;
        dstPort                     = MulticastDNSPort;

        dlog( kDebugLevelChatty, DEBUG_NAME "packet received\n" );
        dlog( kDebugLevelChatty, DEBUG_NAME "    size      = %d\n", n );
        dlog( kDebugLevelChatty, DEBUG_NAME "    src       = %u.%u.%u.%u:%hu\n",
              srcAddr.ip.v4.b[ 0 ], srcAddr.ip.v4.b[ 1 ], srcAddr.ip.v4.b[ 2 ], srcAddr.ip.v4.b[ 3 ],
              ntohs( srcPort.NotAnInteger ) );
        dlog( kDebugLevelChatty, DEBUG_NAME "    dst       = %u.%u.%u.%u:%hu\n",
              dstAddr.ip.v4.b[ 0 ], dstAddr.ip.v4.b[ 1 ], dstAddr.ip.v4.b[ 2 ], dstAddr.ip.v4.b[ 3 ],
              ntohs( dstPort.NotAnInteger ) );
        dlog( kDebugLevelChatty, DEBUG_NAME "    interface = 0x%08X\n", (int) inItem->hostSet.InterfaceID );
        dlog( kDebugLevelChatty, DEBUG_NAME "--\n" );

        // Dispatch the packet to mDNS.

        packetEndPtr = ( (mDNSu8 *) &packet ) + n;
        mDNSCoreReceive( inMDNS, &packet, packetEndPtr, &srcAddr, srcPort, &dstAddr, dstPort, inItem->hostSet.InterfaceID );
    }

    // Update counters.

    inItem->recvCounter         += 1;
    inItem->recvErrorCounter    += ( n < 0 );
}

#if 0
#pragma mark -
#pragma mark == Utilities ==
#endif

#if ( TARGET_NON_APPLE )
//===========================================================================================================================
//	GenerateUniqueHostName
//
//	Non-Apple platform stub routine to generate a unique name for the device. Should be implemented to return a unique name.
//===========================================================================================================================

mDNSlocal void  GenerateUniqueHostName( char *outName, long *ioSeed )
{
    DEBUG_UNUSED( ioSeed );

    // $$$ Non-Apple Platforms: Fill in appropriate name for device.

    mDNSPlatformStrCopy( outName, kMDNSDefaultName );
}

//===========================================================================================================================
//	GenerateUniqueDNSName
//
//	Non-Apple platform stub routine to generate a unique RFC 1034-compatible DNS name for the device. Should be
//	implemented to return a unique name.
//===========================================================================================================================

mDNSlocal void  GenerateUniqueDNSName( char *outName, long *ioSeed )
{
    DEBUG_UNUSED( ioSeed );

    // $$$ Non-Apple Platforms: Fill in appropriate DNS name for device.

    mDNSPlatformStrCopy( outName, kMDNSDefaultName );
}
#endif

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	getifaddrs
//===========================================================================================================================

int getifaddrs( struct ifaddrs **outAddrs )
{
    int err;
    struct ifaddrs *        head;
    struct ifaddrs **       next;
    struct ifaddrs *        ifa;
    int i;
    struct ifnet *          ifp;
    char ipString[ INET_ADDR_LEN ];
    int n;

    head = NULL;
    next = &head;

    i = 1;
    for( ;; )
    {
        ifp = ifIndexToIfp( i );
        if( !ifp )
        {
            break;
        }
        ++i;

        // Allocate and initialize the ifaddrs structure and attach it to the linked list.

        ifa = (struct ifaddrs *) calloc( 1, sizeof( struct ifaddrs ) );
        require_action( ifa, exit, err = ENOMEM );

        *next = ifa;
        next  = &ifa->ifa_next;

        // Fetch the name.

        ifa->ifa_name = (char *) malloc( 16 );
        require_action( ifa->ifa_name, exit, err = ENOMEM );

        n = sprintf( ifa->ifa_name, "%s%d", ifp->if_name, ifp->if_unit );
        require_action( n < 16, exit, err = ENOBUFS );

        // Fetch the address.

        ifa->ifa_addr = (struct sockaddr *) calloc( 1, sizeof( struct sockaddr_in ) );
        require_action( ifa->ifa_addr, exit, err = ENOMEM );

        ipString[ 0 ] = '\0';
        #if ( TARGET_NON_APPLE )
        err = ifAddrGet( ifa->ifa_name, ipString );
        require_noerr( err, exit );
        #else
        err = ifAddrGetNonAlias( ifa->ifa_name, ipString );
        require_noerr( err, exit );
        #endif

        err = sock_pton( ipString, AF_INET, ifa->ifa_addr, 0, NULL );
        require_noerr( err, exit );

        // Fetch flags.

        ifa->ifa_flags = ifp->if_flags;
    }

    // Success!

    if( outAddrs )
    {
        *outAddrs = head;
        head = NULL;
    }
    err = 0;

exit:
    if( head )
    {
        freeifaddrs( head );
    }
    return( err );
}

//===========================================================================================================================
//	freeifaddrs
//===========================================================================================================================

void    freeifaddrs( struct ifaddrs *inAddrs )
{
    struct ifaddrs *        p;
    struct ifaddrs *        q;

    // Free each piece of the structure. Set to null after freeing to handle macro-aliased fields.

    for( p = inAddrs; p; p = q )
    {
        q = p->ifa_next;

        if( p->ifa_name )
        {
            free( p->ifa_name );
            p->ifa_name = NULL;
        }
        if( p->ifa_addr )
        {
            free( p->ifa_addr );
            p->ifa_addr = NULL;
        }
        if( p->ifa_netmask )
        {
            free( p->ifa_netmask );
            p->ifa_netmask = NULL;
        }
        if( p->ifa_dstaddr )
        {
            free( p->ifa_dstaddr );
            p->ifa_dstaddr = NULL;
        }
        if( p->ifa_data )
        {
            free( p->ifa_data );
            p->ifa_data = NULL;
        }
        free( p );
    }
}

//===========================================================================================================================
//	sock_pton
//===========================================================================================================================

int sock_pton( const char *inString, int inFamily, void *outAddr, size_t inAddrSize, size_t *outAddrSize )
{
    int err;

    if( inFamily == AF_INET )
    {
        struct sockaddr_in *        ipv4;

        if( inAddrSize == 0 )
        {
            inAddrSize = sizeof( struct sockaddr_in );
        }
        if( inAddrSize < sizeof( struct sockaddr_in ) )
        {
            err = EINVAL;
            goto exit;
        }

        ipv4 = (struct sockaddr_in *) outAddr;
        err = inet_aton( (char *) inString, &ipv4->sin_addr );
        if( err == 0 )
        {
            ipv4->sin_family = AF_INET;
            if( outAddrSize )
            {
                *outAddrSize = sizeof( struct sockaddr_in );
            }
        }
    }
#if ( defined( AF_INET6 ) )
    else if( inFamily == AF_INET6 )     // $$$ TO DO: Add IPv6 support.
    {
        err = EAFNOSUPPORT;
        goto exit;
    }
#endif
    else
    {
        err = EAFNOSUPPORT;
        goto exit;
    }

exit:
    return( err );
}

//===========================================================================================================================
//	sock_ntop
//===========================================================================================================================

char *  sock_ntop( const void *inAddr, size_t inAddrSize, char *inBuffer, size_t inBufferSize )
{
    const struct sockaddr *     addr;

    addr = (const struct sockaddr *) inAddr;
    if( addr->sa_family == AF_INET )
    {
        struct sockaddr_in *        ipv4;

        if( inAddrSize == 0 )
        {
            inAddrSize = sizeof( struct sockaddr_in );
        }
        if( inAddrSize < sizeof( struct sockaddr_in ) )
        {
            errno = EINVAL;
            inBuffer = NULL;
            goto exit;
        }
        if( inBufferSize < 16 )
        {
            errno = ENOBUFS;
            inBuffer = NULL;
            goto exit;
        }

        ipv4 = (struct sockaddr_in *) addr;
        inet_ntoa_b( ipv4->sin_addr, inBuffer );
    }
#if ( defined( AF_INET6 ) )
    else if( addr->sa_family == AF_INET6 )  // $$$ TO DO: Add IPv6 support.
    {
        errno = EAFNOSUPPORT;
        inBuffer = NULL;
        goto exit;
    }
#endif
    else
    {
        errno = EAFNOSUPPORT;
        inBuffer = NULL;
        goto exit;
    }

exit:
    return( inBuffer );
}

#if 0
#pragma mark -
#pragma mark == Debugging ==
#endif

#if ( DEBUG )

void    mDNSShow( BOOL inShowRecords );
void    mDNSShowRecords( void );
void    mDNSShowTXT( const void *inTXT, size_t inTXTSize );

//===========================================================================================================================
//	mDNSShow
//===========================================================================================================================

void    mDNSShow( BOOL inShowRecords )
{
    MDNSInterfaceItem *     item;
    mDNSAddr ip;
    int n;

    if( !gMDNSPtr )
    {
        printf( "### mDNS not initialized\n" );
        return;
    }

    // Globals

    printf( "\n-- mDNS globals --\n" );
    printf( "    sizeof( mDNS )                     = %d\n", (int) sizeof( mDNS ) );
    printf( "    sizeof( ResourceRecord )           = %d\n", (int) sizeof( ResourceRecord ) );
    printf( "    sizeof( AuthRecord )               = %d\n", (int) sizeof( AuthRecord ) );
    printf( "    sizeof( CacheRecord )              = %d\n", (int) sizeof( CacheRecord ) );
    printf( "    gMDNSPtr                           = 0x%08lX\n", (unsigned long) gMDNSPtr );
    printf( "    gMDNSTicksToMicrosecondsMultiplier = %ld\n", gMDNSTicksToMicrosecondsMultiplier );
    printf( "    lockID                             = 0x%08lX\n", (unsigned long) gMDNSPtr->p->lockID );
    printf( "    readyEvent                         = 0x%08lX\n", (unsigned long) gMDNSPtr->p->readyEvent );
    printf( "    taskInitErr                        = %ld\n", gMDNSPtr->p->taskInitErr );
    printf( "    quitEvent                          = 0x%08lX\n", (unsigned long) gMDNSPtr->p->quitEvent );
    printf( "    commandPipe                        = %d\n", gMDNSPtr->p->commandPipe );
    printf( "    task                               = 0x%08lX\n", (unsigned long) gMDNSPtr->p->task );
    printf( "    quit                               = %d\n", gMDNSPtr->p->quit );
    printf( "    configID                           = %ld\n", gMDNSPtr->p->configID );
    printf( "    rescheduled                        = %d\n", gMDNSPtr->p->rescheduled );
    printf( "    nicelabel                          = \"%.*s\"\n", gMDNSPtr->nicelabel.c[ 0 ], (char *) &gMDNSPtr->nicelabel.c[ 1 ] );
    printf( "    hostLabel                          = \"%.*s\"\n", gMDNSPtr->hostlabel.c[ 0 ], (char *) &gMDNSPtr->hostlabel.c[ 1 ] );
    printf( "\n");

    // Interfaces

    printf( "\n-- mDNS interfaces --\n" );
    n = 1;
    for( item = gMDNSPtr->p->interfaceList; item; item = item->next )
    {
        printf( "    -- interface %u --\n", n );
        printf( "        name                           = \"%s\"\n", item->name );
        printf( "        multicastSocketRef             = %d\n", item->multicastSocketRef );
        printf( "        sendingSocketRef               = %d\n", item->sendingSocketRef );
        ip = item->hostSet.ip;
        printf( "        hostSet.ip                     = %u.%u.%u.%u\n", ip.ip.v4.b[ 0 ], ip.ip.v4.b[ 1 ],
                ip.ip.v4.b[ 2 ], ip.ip.v4.b[ 3 ] );
        printf( "        hostSet.advertise              = %s\n", item->hostSet.Advertise ? "YES" : "NO" );
        printf( "        hostRegistered                 = %s\n", item->hostRegistered ? "YES" : "NO" );
        printf( "        --\n" );
        printf( "        sendMulticastCounter           = %d\n", item->sendMulticastCounter );
        printf( "        sendUnicastCounter             = %d\n", item->sendUnicastCounter );
        printf( "        sendErrorCounter               = %d\n", item->sendErrorCounter );
        printf( "        recvCounter                    = %d\n", item->recvCounter );
        printf( "        recvErrorCounter               = %d\n", item->recvErrorCounter );
        printf( "        recvLoopCounter                = %d\n", item->recvLoopCounter );
        printf( "\n" );
        ++n;
    }

    // Resource Records

    if( inShowRecords )
    {
        mDNSShowRecords();
    }
}

//===========================================================================================================================
//	mDNSShowRecords
//===========================================================================================================================

void    mDNSShowRecords( void )
{
    MDNSInterfaceItem *     item;
    int n;
    AuthRecord *            record;
    char name[ MAX_ESCAPED_DOMAIN_NAME ];

    printf( "\n-- mDNS resource records --\n" );
    n = 1;
    for( record = gMDNSPtr->ResourceRecords; record; record = record->next )
    {
        item = (MDNSInterfaceItem *) record->resrec.InterfaceID;
        ConvertDomainNameToCString( &record->resrec.name, name );
        printf( "    -- record %d --\n", n );
        printf( "        interface = 0x%08X (%s)\n", (int) item, item ? item->name : "<any>" );
        printf( "        name      = \"%s\"\n", name );
        printf( "\n" );
        ++n;
    }
    printf( "\n");
}

//===========================================================================================================================
//	mDNSShowTXT
//===========================================================================================================================

void    mDNSShowTXT( const void *inTXT, size_t inTXTSize )
{
    const mDNSu8 *      p;
    const mDNSu8 *      end;
    int i;
    mDNSu8 size;

    printf( "\nTXT record (%u bytes):\n\n", inTXTSize );

    p   = (const mDNSu8 *) inTXT;
    end = p + inTXTSize;
    i   = 0;

    while( p < end )
    {
        size = *p++;
        if( ( p + size ) > end )
        {
            printf( "\n### MALFORMED TXT RECORD (length byte too big for record)\n\n" );
            break;
        }
        printf( "%2d (%3d bytes): \"%.*s\"\n", i, size, size, p );
        p += size;
        ++i;
    }
    printf( "\n" );
}
#endif  // DEBUG
