/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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.
 */

/****************************************************************************

  ICPConfig.cc


****************************************************************************/

#include "ts/ink_platform.h"
#include "ts/ink_file.h"
#include "P_EventSystem.h"
#include "P_Cache.h"
#include "P_Net.h"
#include "P_RecProcess.h"
#include "Main.h"
#include "ICP.h"
#include "ICPProcessor.h"
#include "ICPlog.h"
#include "BaseManager.h"
#include "ts/I_Layout.h"

//--------------------------------------------------------------------------
//  Each ICP peer is described in "icp.config" with the
//  following info:
//    hostname (string)         -- hostname, used only if (host_ip_str == 0)
//    host_ip_str (string)      -- decimal dot notation; if null get IP
//                                   addresss via lookup on hostname
//    ctype (int)               -- 1=Parent, 2=Sibling, 3=local
//    proxy_port (int)          -- TCP Port #
//    icp_port (int)            -- UDP Port #
//    multicast_member          -- 0=No 1=Yes
//    multicast_ip_str (string) -- decimal dot notation
//    multicast_ttl (int)       -- (1 - 2; default 1)
//
//#############################################################################
//
//  ICP global configuration options are described in "records.config" using
//  the following entries.
//
//    proxy.config.icp.icp_configuration STRING (default "icp.config")
//    proxy.config.icp.enabled INT (0=No 1=Yes)
//    proxy.config.icp.icp_port INT 3130
//    proxy.config.icp.icp_interface STRING hme0
//    proxy.config.icp.multicast_enabled INT (0=No 1=Yes)
//    proxy.config.icp.query_timeout INT (seconds default is 2 secs)
//    proxy.config.icp.lookup_local INT (default is cluster lookup)
//
//  Example (1 parent and 1 sibling):
//  ============================================
//    proxy.config.icp.enabled INT 1
//    proxy.config.icp.multicast_enabled INT 0
//    proxy.config.icp.query_timeout INT 2
//
//    === "icp.config" entries ===
//
//    hostname:         "host1"
//    host_ip_str:      "209.1.33.10"
//    ctype:            1
//    proxy_port:       8080
//    icp_port:         3130
//    multicast_member  0
//    multicast_ip_str: "0.0.0.0"
//    multicast_ttl:    0
//
//    hostname:         "host2"
//    host_ip_str:      "209.1.33.11"
//    ctype:            2
//    proxy_port:       8080
//    icp_port:         3130
//    multicast_member  0
//    multicast_ip_str: "0.0.0.0"
//    multicast_ttl:    0
//
//  Example (1 parent and 1 sibling using MultiCast):
//  ============================================================
//    proxy.config.icp.enabled INT 1
//    proxy.config.icp.multicast_enabled INT 1
//    proxy.config.icp.query_timeout INT 2
//
//    === "icp.config" entries ===
//
//    hostname:         "host1"
//    host_ip_str:      "209.1.33.10"
//    ctype:            1
//    proxy_port:       8080
//    icp_port:         3130
//    multicast_member  1
//    multicast_ip_str: "239.128.16.128"
//    multicast_ttl:    1
//
//    hostname:         "host2"
//    host_ip_str:      "209.1.33.11"
//    ctype:            2
//    proxy_port:       8080
//    icp_port:         3130
//    multicast_member  1
//    multicast_ip_str: "239.128.16.128"
//    multicast_ttl:    1
//

//------------------------------------
// Class AtomicLock member functions
//------------------------------------
#if !defined(USE_CAS_FOR_ATOMICLOCK)
AtomicLock::AtomicLock()
{
  _mutex = new_ProxyMutex();
}

AtomicLock::~AtomicLock()
{
}

int
AtomicLock::Lock()
{
  EThread *et = this_ethread();
  ink_assert(et != nullptr);
  return MUTEX_TAKE_TRY_LOCK(_mutex, et);
}

int
AtomicLock::HaveLock()
{
  EThread *et = this_ethread();
  ink_assert(et != nullptr);
  return (_mutex->thread_holding == et);
}

void
AtomicLock::Unlock()
{
  EThread *et = this_ethread();
  ink_assert(et != nullptr);
  MUTEX_UNTAKE_LOCK(_mutex, et);
}

#else  // USE_CAS_FOR_ATOMICLOCK
AtomicLock::AtomicLock() : lock_word(0)
{
}

AtomicLock::~AtomicLock()
{
}

int
AtomicLock::Lock()
{
  bool status = ink_atomic_cas(&_lock_word, AtomicLock::UNLOCKED, AtomicLock::LOCKED);
  return status;
}

int
AtomicLock::HaveLock()
{
  return (_lock_word == LOCKED);
}

void
AtomicLock::Unlock()
{
  ink_assert(_lock_word == AtomicLock::LOCKED);
  _lock_word = AtomicLock::UNLOCKED;
}
#endif // USE_CAS_FOR_ATOMICLOCK

//---------------------------------------------------------------------
// Class BitMap -- Member functions.
//                 Generic bitmap management class
//      Note: Bit positions are zero based (0 .. (bitmap_maxsize-1) )
//---------------------------------------------------------------------
BitMap::BitMap(int bitmap_maxsize)
{
  if (bitmap_maxsize <= (int)(STATIC_BITMAP_BYTE_SIZE * BITS_PER_BYTE)) {
    _bitmap           = _static_bitmap;
    _bitmap_size      = bitmap_maxsize;
    _bitmap_byte_size = STATIC_BITMAP_BYTE_SIZE;
  } else {
    _bitmap_byte_size = (bitmap_maxsize + (BITS_PER_BYTE - 1)) / BITS_PER_BYTE;
    _bitmap           = new char[_bitmap_byte_size];
    _bitmap_size      = bitmap_maxsize;
  }
  memset((void *)_bitmap, 0, _bitmap_byte_size);
}

BitMap::~BitMap()
{
  if (_bitmap_size > (int)(STATIC_BITMAP_BYTE_SIZE * BITS_PER_BYTE)) {
    delete[] _bitmap;
  }
}

void
BitMap::SetBit(int bit)
{
  if (bit >= _bitmap_size) {
    return;
  }

  char *pbyte = &_bitmap[bit / BITS_PER_BYTE];
  *pbyte |= (1 << (bit % BITS_PER_BYTE));
}

void
BitMap::ClearBit(int bit)
{
  if (bit >= _bitmap_size) {
    return;
  }

  char *pbyte = &_bitmap[bit / BITS_PER_BYTE];
  *pbyte &= ~(1 << (bit % BITS_PER_BYTE));
}

int
BitMap::IsBitSet(int bit)
{
  if (bit >= _bitmap_size) {
    return 0;
  }

  char *pbyte = &_bitmap[bit / BITS_PER_BYTE];
  if (*pbyte & (1 << (bit % BITS_PER_BYTE))) {
    return 1;
  } else {
    return 0;
  }
}

//-----------------------------------------------------------------------
// Class ICPConfigData member functions
//      Manage global ICP configuration data from the TS configuration.
//      Support class for ICPConfiguration.
//-----------------------------------------------------------------------
int
ICPConfigData::operator==(ICPConfigData &ICPData)
{
  if (ICPData._icp_enabled != _icp_enabled) {
    return 0;
  }
  if (ICPData._icp_port != _icp_port) {
    return 0;
  }
  if (ICPData._icp_interface != _icp_interface) {
    return 0;
  }
  if (ICPData._multicast_enabled != _multicast_enabled) {
    return 0;
  }
  if (ICPData._icp_query_timeout != _icp_query_timeout) {
    return 0;
  }
  if (ICPData._cache_lookup_local != _cache_lookup_local) {
    return 0;
  }
  if (ICPData._stale_lookup != _stale_lookup) {
    return 0;
  }
  if (ICPData._reply_to_unknown_peer != _reply_to_unknown_peer) {
    return 0;
  }
  if (ICPData._default_reply_port != _default_reply_port) {
    return 0;
  }
  return 1;
}

//------------------------------------------------------------------------
// Class PeerConfigData member functions
//      Manage ICP Peer configuration data from the TS configuration.
//      Support class for ICPConfiguration.
//------------------------------------------------------------------------
PeerConfigData::PeerConfigData() : _ctype(CTYPE_NONE), _proxy_port(0), _icp_port(0), _mc_member(0), _mc_ttl(0)
{
  memset(_hostname, 0, HOSTNAME_SIZE);
}

PeerType_t
PeerConfigData::CTypeToPeerType_t(int ctype)
{
  switch (ctype) {
  case (int)CTYPE_PARENT:
    return PEER_PARENT;

  case (int)CTYPE_SIBLING:
    return PEER_SIBLING;

  case (int)CTYPE_LOCAL:
    return PEER_LOCAL;

  default:
    return PEER_NONE;
  }
}

int
PeerConfigData::GetHostIPByName(char *hostname, IpAddr &rip)
{
  // Short circuit NULL hostname case
  if (nullptr == hostname || 0 == *hostname) {
    return 1; // Unable to map to IP address
  }

  addrinfo hints;
  addrinfo *ai;
  sockaddr const *best = nullptr;

  ink_zero(hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_flags  = AI_ADDRCONFIG;
  if (0 == getaddrinfo(hostname, nullptr, &hints, &ai)) {
    for (addrinfo *spot = ai; spot; spot = spot->ai_next) {
      // If current address is valid, and either we don't have one yet
      // or this address is less than our current, set it as current.
      if (ats_is_ip(spot->ai_addr) && (!best || -1 == ats_ip_addr_cmp(spot->ai_addr, best))) {
        best = spot->ai_addr;
      }
    }
    if (best) {
      rip.assign(best);
    }
    freeaddrinfo(ai);
  }
  return best ? 0 : 1;
}

bool
PeerConfigData::operator==(PeerConfigData &PeerData)
{
  if (strncmp(PeerData._hostname, _hostname, PeerConfigData::HOSTNAME_SIZE) != 0) {
    return false;
  }
  if (PeerData._ctype != _ctype) {
    return false;
  }
  if (PeerData._ip_addr != _ip_addr) {
    return false;
  }
  if (PeerData._proxy_port != _proxy_port) {
    return false;
  }
  if (PeerData._icp_port != _icp_port) {
    return false;
  }
  if (PeerData._mc_member != _mc_member) {
    return false;
  }
  if (PeerData._mc_ip_addr != _mc_ip_addr) {
    return false;
  }
  if (PeerData._mc_ttl != _mc_ttl) {
    return false;
  }
  return true;
}

//-----------------------------------------------------------------------
// Class ICPConfigUpdateCont member functions
//      Retry callout to ICPConfiguration::icp_config_change_callback()
//-----------------------------------------------------------------------
ICPConfigUpdateCont::ICPConfigUpdateCont(void *d, void *v) : Continuation(new_ProxyMutex()), _data(d), _value(v)
{
}

int
ICPConfigUpdateCont::RetryICPconfigUpdate(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
  ICPConfiguration::icp_config_change_callback(_data, _value);
  delete this;
  return EVENT_DONE;
}

//--------------------------------------------------------------------------
// Class ICPConfiguration member functions
//      Overall manager of ICP configuration data from TS configuration.
//--------------------------------------------------------------------------
using ICPCfgContHandler = int (ICPConfigUpdateCont::*)(int, void *);
ICPConfiguration::ICPConfiguration() : _icp_config_callouts(0)
{
  //*********************************************************
  // Allocate working and current ICPConfigData structures
  //*********************************************************
  _icp_cdata         = new ICPConfigData();
  _icp_cdata_current = new ICPConfigData();

  //********************************************************************
  // Read ICP config and setup update callbacks for "icp_cdata_current"
  //********************************************************************
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_icp_enabled, "proxy.config.icp.enabled");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_icp_port, "proxy.config.icp.icp_port");
  ICP_EstablishStaticConfigStringAlloc(_icp_cdata_current->_icp_interface, "proxy.config.icp.icp_interface");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_multicast_enabled, "proxy.config.icp.multicast_enabled");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_icp_query_timeout, "proxy.config.icp.query_timeout");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_cache_lookup_local, "proxy.config.icp.lookup_local");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_stale_lookup, "proxy.config.icp.stale_icp_enabled");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_reply_to_unknown_peer, "proxy.config.icp.reply_to_unknown_peer");
  ICP_EstablishStaticConfigInteger(_icp_cdata_current->_default_reply_port, "proxy.config.icp.default_reply_port");
  REC_EstablishStaticConfigInteger(_icp_cdata_current->_cache_generation, "proxy.config.http.cache.generation");

  UpdateGlobalConfig(); // sync working copy with current

  //**********************************************************
  // Allocate working and current PeerConfigData structures
  //**********************************************************
  for (int n = 0; n <= MAX_DEFINED_PEERS; ++n) {
    _peer_cdata[n]         = new PeerConfigData;
    _peer_cdata_current[n] = new PeerConfigData;
  }

  //*********************************************************
  // Initialize Peer data by simulating an update callout.
  //*********************************************************
  char icp_config_filename[PATH_NAME_MAX] = "";
  ICP_ReadConfigString(icp_config_filename, "proxy.config.icp.icp_configuration", sizeof(icp_config_filename) - 1);
  (void)icp_config_change_callback((void *)this, (void *)icp_config_filename, 1);
  UpdatePeerConfig(); // sync working copy with current

  //***************************************
  // Setup update callout on "icp.config"
  //***************************************
  ICP_RegisterConfigUpdateFunc("proxy.config.icp.icp_configuration", mgr_icp_config_change_callback, (void *)this);
}

ICPConfiguration::~ICPConfiguration()
{
// TBD: Need to disable update callbacks before deallocating data.
//      How do we do this?  For now, this never happens.
#ifdef OMIT
  if (_icp_cdata) {
    // TBD: Make sure _icp_cdata->_icp_interface has been freed
    delete ((void *)_icp_cdata);
  }
  if (_icp_cdata_current)
    delete ((void *)_icp_cdata_current);
  if (_peer_cdata)
    delete ((void *)_peer_cdata);
  if (_peer_cdata_current)
    delete ((void *)_peer_cdata_current);
#endif // OMIT
}

int
ICPConfiguration::GlobalConfigChange()
{
  return (!(*_icp_cdata == *_icp_cdata_current));
}

void
ICPConfiguration::UpdateGlobalConfig()
{
  *_icp_cdata = *_icp_cdata_current;
}

int
ICPConfiguration::PeerConfigChange()
{
  // Note: Entry zero reserved for "localhost"
  for (int i = 1; i <= MAX_DEFINED_PEERS; ++i) {
    if (!(*_peer_cdata[i] == *_peer_cdata_current[i])) {
      return 1;
    }
  }
  return 0;
}

void
ICPConfiguration::UpdatePeerConfig()
{
  // Note: Entry zero reserved for "localhost"
  for (int i = 1; i <= MAX_DEFINED_PEERS; ++i) {
    //
    // Broken on DEC and Solaris x86
    // *_peer_cdata[i] = *_peer_cdata_current[i];
    //
    memcpy(_peer_cdata[i], _peer_cdata_current[i], sizeof(*_peer_cdata[i]));
    // Setup IP address
    if ((_peer_cdata[i]->_ip_addr.isValid()) && _peer_cdata[i]->_hostname[0]) {
      // IP address not specified, lookup using hostname.
      (void)PeerConfigData::GetHostIPByName(_peer_cdata[i]->_hostname, _peer_cdata[i]->_my_ip_addr);
    } else {
      // IP address specified by user, lookup on hostname not required.
      _peer_cdata[i]->_my_ip_addr = _peer_cdata[i]->_ip_addr;
    }
  }
}

int
ICPConfiguration::mgr_icp_config_change_callback(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */,
                                                 RecData data, void *cookie)
{
  //*****************************************************************
  // Callout invoked by Configuration management when changes occur
  // to icp.config
  //*****************************************************************

  // Map this manager configuration callout onto ET_ICP

  ICPConfigUpdateCont *rh = new ICPConfigUpdateCont(cookie, data.rec_string);
  SET_CONTINUATION_HANDLER(rh, (ICPCfgContHandler)&ICPConfigUpdateCont::RetryICPconfigUpdate);
  eventProcessor.schedule_imm(rh, ET_ICP);
  return EVENT_DONE;
}

namespace
{
inline char *
next_field(char *text, char fs)
{
  text = strchr(text, fs);
  // Compress contiguous whitespace by leaving zret pointing at the last space.
  if (text && *text == fs) {
    while (text[1] == fs) {
      ++text;
    }
  }
  return text;
}
}

void *
ICPConfiguration::icp_config_change_callback(void *data, void *value, int startup)
{
  EThread *thread   = this_ethread();
  ProxyMutex *mutex = thread->mutex.get();

  //
  // Cast passed parameters to correct types
  //
  char *filename              = (char *)value;
  ICPConfiguration *ICPconfig = (ICPConfiguration *)data;

  //
  // Determine if data is locked, if so defer update action
  //
  if (!startup && !ICPconfig->Lock()) {
    // Build retry continuation
    ICPConfigUpdateCont *rh = new ICPConfigUpdateCont(data, value);
    SET_CONTINUATION_HANDLER(rh, (ICPCfgContHandler)&ICPConfigUpdateCont::RetryICPconfigUpdate);
    eventProcessor.schedule_in(rh, HRTIME_MSECONDS(ICPConfigUpdateCont::RETRY_INTERVAL), ET_ICP);
    return EVENT_DONE;
  }
  ICP_INCREMENT_DYN_STAT(config_mgmt_callouts_stat);
  ICPconfig->_icp_config_callouts++;

  //
  // Allocate working buffer for PeerConfigData[]
  //
  PeerConfigData *P = new PeerConfigData[MAX_DEFINED_PEERS + 1];

  //
  // Build pathname to "icp.config" and open file
  //
  ink_release_assert(filename != nullptr);

  ats_scoped_str config_path(Layout::get()->relative_to(Layout::get()->sysconfdir, filename));
  int fd = open(config_path, O_RDONLY);
  if (fd < 0) {
    RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, open failed");
    delete[] P;
    return EVENT_DONE;
  }
  //***********************************************************************
  // Parse records in "icp.config"
  //  Each line is formatted as follows with ":" separator for each field
  //    - hostname (string)           -- Identifier for entry
  //    - host_ip_str (string)        -- decimal dot notation
  //    - ctype (int)                 -- 1=Parent, 2=Sibling, 3=Local
  //    - proxy_port (int)            -- TCP Port #
  //    - icp_port (int)              -- UDP Port #
  //    - multicast_member            -- 0=No 1=Yes
  //    - multicast_ip_str (string)   -- decimal dot notation
  //    - multicast_ttl (int)         -- (1 - 2; default 1)
  //***********************************************************************
  const int colons_per_entry = 8; // expected ':' separators per entry

  int error = 0;
  int ln    = 0;
  int n_colons;
  char line[512];
  char *cur;
  char *next;
  char *p;
  char fs = ':'; // field separator.
  int len;       // length of current input line (original).

  int n = 1; // Note: Entry zero reserved for "localhost" data

  //////////////////////////////////////////////////////////////////////
  // Read and parse "icp.config" entries.
  //
  // Note: ink_file_fd_readline() null terminates returned buffer
  //////////////////////////////////////////////////////////////////////
  while ((len = ink_file_fd_readline(fd, sizeof(line) - 1, line)) > 0) {
    ln++;
    cur = line;
    while (isspace(*cur)) {
      ++cur, --len; // skip leading space.
    }
    if (!*cur || *cur == '#') {
      continue;
    }

    if (n >= MAX_DEFINED_PEERS) {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, maximum peer entries exceeded");
      error = 1;
      break;
    }
    //***********************************
    // Verify general syntax of entry
    //***********************************
    /* Ugly. The original field separator was colon, but we can't have that
       if we want to support IPv6. So - since each line is required to have a
       separator at the end of the line, we look there and require it to be
       consistent. It still must be an acceptable character.
    */
    char *last = cur + len - 1; // last character.
    if ('\n' == *last) {
      --last; // back over trailing LF.
    }
    if (nullptr == strchr(" ;:|,", *last)) {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, invalid separator [value %d]", *last);
      error = 1;
      break;
    }
    fs = *last;

    n_colons = 0;
    p        = cur;
    while (nullptr != (p = next_field(p, fs))) {
      ++p;
      ++n_colons;
    }
    if (n_colons != colons_per_entry) {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, invalid syntax, line %d: expected %d fields, found %d", ln,
                       colons_per_entry, n_colons);
      error = 1;
      break;
    }
    //*******************
    // Extract hostname
    //*******************
    next    = next_field(cur, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      ink_strlcpy(P[n]._hostname, cur, PeerConfigData::HOSTNAME_SIZE);
    } else {
      P[n]._hostname[0] = 0;
    }
    //*********************
    // Extract host_ip_str
    //*********************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      if (0 != P[n]._ip_addr.load(cur)) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad host ip_addr, line %d", ln);
        error = 1;
        break;
      }
    } else {
      P[n]._ip_addr.invalidate();
    }

    if (!P[n]._hostname[0] && !P[n]._ip_addr.isValid()) {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad hostname, line %d", ln);
      error = 1;
      break;
    }
    //******************
    // Extract ctype
    //******************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      P[n]._ctype = atoi(cur);
      if ((P[n]._ctype != PeerConfigData::CTYPE_PARENT) && (P[n]._ctype != PeerConfigData::CTYPE_SIBLING) &&
          (P[n]._ctype != PeerConfigData::CTYPE_LOCAL)) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad ctype, line %d", ln);
        error = 1;
        break;
      }
    } else {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, 2bad ctype, line %d", ln);
      error = 1;
      break;
    }
    //*********************
    // Extract proxy_port
    //*********************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      if ((P[n]._proxy_port = atoi(cur)) <= 0) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad proxy_port, line %d", ln);
        error = 1;
        break;
      }
    } else {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, 2bad proxy_port, line %d", ln);
      error = 1;
      break;
    }
    //*********************
    // Extract icp_port
    //*********************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      if ((P[n]._icp_port = atoi(cur)) <= 0) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad icp_port, line %d", ln);
        error = 1;
        break;
      }
    } else {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, 2bad icp_port, line %d", ln);
      error = 1;
      break;
    }
    //****************************
    // Extract multicast_member
    //****************************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      if ((P[n]._mc_member = atoi(cur)) < 0) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad mc_member, line %d", ln);
        error = 1;
        break;
      }
      if ((P[n]._mc_member != 0) && (P[n]._mc_member != 1)) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad mc_member (2), line %d", ln);
        error = 1;
        break;
      }
    } else {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, 2bad mc_member, line %d", ln);
      error = 1;
      break;
    }
    //****************************
    // Extract multicast_ip_str
    //****************************
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      P[n]._mc_ip_addr.load(cur);
      // Validate only if "multicast_member" is set.
      if (P[n]._mc_member != 0 && !P[n]._mc_ip_addr.isMulticast()) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad multicast ip_addr, line %d", ln);
        error = 1;
        break;
      }
    } else {
      P[n]._mc_ip_addr.invalidate();
    }
    //************************
    // Extract multicast_ttl
    //************************
    // Note: last entry is always terminated with a ":"
    cur     = next;
    next    = next_field(next, fs);
    *next++ = 0;
    if (cur != (next - 1)) {
      P[n]._mc_ttl = atoi(cur);
      if ((P[n]._mc_ttl = atoi(cur)) <= 0) {
        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, bad mc_ttl, line %d", ln);
        error = 1;
        break;
      }
    } else {
      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "read icp.config, 2bad mc_ttl, line %d", ln);
      error = 1;
      break;
    }
    n++; // bump PeerConfigData[] index
  }
  close(fd);

  if (!error) {
    for (int i = 0; i <= MAX_DEFINED_PEERS; i++) {
      *ICPconfig->_peer_cdata_current[i] = P[i];
    }
  }
  delete[] P; // free working buffer
  if (!startup) {
    ICPconfig->Unlock();
  }
  return EVENT_DONE;
}

//-------------------------------------------------------
// Class Peer member functions (abstract base class)
//-------------------------------------------------------
Peer::Peer(PeerType_t t, ICPProcessor *icpPr, bool dynamic_peer)
  : buf(nullptr),
    notFirstRead(0),
    readAction(nullptr),
    writeAction(nullptr),
    _type(t),
    _next(nullptr),
    _ICPpr(icpPr),
    _state(PEER_UP)
{
  notFirstRead = 0;
  if (dynamic_peer) {
    _state |= PEER_DYNAMIC;
  }
  memset((void *)&this->_stats, 0, sizeof(this->_stats));
  ink_zero(fromaddr);
  fromaddrlen = sizeof(fromaddr);
  _id         = 0;
}

void
Peer::LogRecvMsg(ICPMsg_t *m, int valid)
{
  // Note: ICPMsg_t (m) is in native byte order

  // Note numerous stats on a per peer basis
  _stats.last_receive = Thread::get_hrtime();
  if ((m->h.opcode >= ICP_OP_QUERY) && (m->h.opcode <= ICP_OP_LAST)) {
    _stats.recv[m->h.opcode]++;
  } else {
    _stats.recv[ICP_OP_INVALID]++;
  }
  _stats.total_received++;

  if (!valid) {
    // Message arrived, but ICP request no longer on pending list
    _stats.dropped_replies++;
  }
  if ((_state & PEER_UP) == 0) {
    ip_port_text_buffer ipb;
    // Currently marked down so we still send but do not expect reply.
    // Now mark up so we will wait for reply.
    _state |= PEER_UP;
    _stats.total_received = _stats.total_sent; // restart timeout count

    Debug("icp", "Peer [%s] now back online", ats_ip_nptop(this->GetIP(), ipb, sizeof(ipb)));
  }
}

//---------------------------------------------------------------
// Class ParentSiblingPeer (derived from Peer) member functions
//      ICP object describing Parent or Sibling Peers.
//---------------------------------------------------------------
ParentSiblingPeer::ParentSiblingPeer(PeerType_t t, PeerConfigData *p, ICPProcessor *icpPr, bool dynamic_peer)
  : Peer(t, icpPr, dynamic_peer), _pconfig(p)
{
  ats_ip_set(&_ip.sa, _pconfig->GetIPAddr(), htons(_pconfig->GetICPPort()));
}

int
ParentSiblingPeer::GetProxyPort()
{
  return _pconfig->GetProxyPort();
}

int
ParentSiblingPeer::GetICPPort()
{
  return _pconfig->GetICPPort();
}

sockaddr *
ParentSiblingPeer::GetIP()
{
  // The real data is in _pconfig, but I don't think ever changes so
  // it should be OK to have set this in the constructor.
  return &_ip.sa;
}

Action *
ParentSiblingPeer::SendMsg_re(Continuation *cont, void *token, struct msghdr *msg, sockaddr const *to)
{
  // Note: All sends are funneled through the local peer UDP socket.

  Peer *lp = _ICPpr->GetLocalPeer();

  if (to) {
    // Send to specified host
    Peer *p = _ICPpr->FindPeer(IpAddr(to), ntohs(ats_ip_port_cast(to)));
    ink_assert(p);

    msg->msg_name    = &p->GetSendChan()->addr;
    msg->msg_namelen = ats_ip_size(&p->GetSendChan()->addr);
    Action *a        = udpNet.sendmsg_re(cont, token, lp->GetSendFD(), msg);
    return a;
  } else {
    // Send to default host
    msg->msg_name    = &_chan.addr;
    msg->msg_namelen = ats_ip_size(&_chan.addr.sa);
    Action *a        = udpNet.sendmsg_re(cont, token, lp->GetSendFD(), msg);
    return a;
  }
}

Action *
ParentSiblingPeer::RecvFrom_re(Continuation *cont, void *token, IOBufferBlock *bufblock, int size, struct sockaddr *from,
                               socklen_t *fromlen)
{
  // Note: All receives are funneled through the local peer UDP socket.

  Peer *lp  = _ICPpr->GetLocalPeer();
  Action *a = udpNet.recvfrom_re(cont, token, lp->GetRecvFD(), from, fromlen, bufblock, size, true, 0);
  return a;
}

int
ParentSiblingPeer::GetRecvFD()
{
  return _chan.fd;
}

int
ParentSiblingPeer::GetSendFD()
{
  return _chan.fd;
}

int
ParentSiblingPeer::ExpectedReplies(BitMap *expected_replies_list)
{
  if (((_state & PEER_UP) == 0) || ((_stats.total_sent - _stats.total_received) > Peer::OFFLINE_THRESHOLD)) {
    if (_state & PEER_UP) {
      ip_port_text_buffer ipb;
      _state &= ~PEER_UP;
      Debug("icp", "Peer [%s] marked offline", ats_ip_nptop(this->GetIP(), ipb, sizeof(ipb)));
    }
    //
    // We will continue to send messages, but will not wait for a reply
    // until we receive a response.
    //
    return 0;
  } else {
    expected_replies_list->SetBit(this->GetPeerID());
    return 1;
  }
}

int
ParentSiblingPeer::ValidSender(sockaddr *fr)
{
  if (_type == PEER_LOCAL) {
    //
    // We are currently funneling all unicast receives
    // through the local peer UDP socket.  As long as
    // the sender is known within the ICP configuration,
    // consider it valid.
    //
    Peer *p = _ICPpr->FindPeer(fr);
    if (p) {
      return 1; // Valid sender
    } else {
      return 0; // Invalid sender
    }

  } else {
    // Make sure the sockaddr_in corresponds to this peer
    // Need to update once we have support for comparing address
    // and port in a socakddr.
    if (ats_ip_addr_eq(this->GetIP(), fr) && (ats_ip_port_cast(this->GetIP()) == ats_ip_port_cast(fr))) {
      return 1; // Sender is this peer
    } else {
      return 0; // Sender is not this peer
    }
  }
}

void
ParentSiblingPeer::LogSendMsg(ICPMsg_t *m, sockaddr const * /* sa ATS_UNUSED */)
{
  // Note: ICPMsg_t (m) is in network byte order

  // Note numerous stats on a per peer basis
  _stats.last_send = Thread::get_hrtime();
  _stats.sent[m->h.opcode]++;
  _stats.total_sent++;
}

int
ParentSiblingPeer::ExtToIntRecvSockAddr(sockaddr const *in, sockaddr *out)
{
  Peer *p = _ICPpr->FindPeer(IpAddr(in));
  if (p && (p->GetType() != PEER_LOCAL)) {
    // Map from received (ip, port) to defined (ip, port).
    ats_ip_copy(out, p->GetIP());
    return 1;
  } else {
    return 0;
  }
}

//-----------------------------------------------------------
// Class MultiCastPeer (derived from Peer) member functions
//      ICP object describing MultiCast Peers.
//-----------------------------------------------------------
MultiCastPeer::MultiCastPeer(IpAddr const &addr, uint16_t mc_port, int ttl, ICPProcessor *icpPr)
  : Peer(PEER_MULTICAST, icpPr), _mc_ttl(ttl)
{
  ats_ip_set(&_mc_ip.sa, addr, htons(mc_port));
  memset(&this->_mc, 0, sizeof(this->_mc));
}

int
MultiCastPeer::GetTTL()
{
  return _mc_ttl;
}

sockaddr *
MultiCastPeer::GetIP()
{
  return &_mc_ip.sa;
}

Action *
MultiCastPeer::SendMsg_re(Continuation *cont, void *token, struct msghdr *msg, sockaddr const *to)
{
  Action *a;

  if (to) {
    // Send to MultiCast group member (UniCast)
    Peer *p = FindMultiCastChild(IpAddr(to), ats_ip_port_host_order(to));
    ink_assert(p);
    a = ((ParentSiblingPeer *)p)->SendMsg_re(cont, token, msg, nullptr);
  } else {
    // Send to MultiCast group
    msg->msg_name    = (caddr_t)&_send_chan.addr;
    msg->msg_namelen = sizeof(_send_chan.addr);
    a                = udpNet.sendmsg_re(cont, token, _send_chan.fd, msg);
  }
  return a;
}

Action *
MultiCastPeer::RecvFrom_re(Continuation *cont, void *token, IOBufferBlock * /* bufblock ATS_UNUSED */, int len,
                           struct sockaddr *from, socklen_t *fromlen)
{
  Action *a = udpNet.recvfrom_re(cont, token, _recv_chan.fd, from, fromlen, buf.get(), len, true, 0);
  return a;
}

int
MultiCastPeer::GetRecvFD()
{
  return _recv_chan.fd;
}

int
MultiCastPeer::GetSendFD()
{
  return _send_chan.fd;
}

int
MultiCastPeer::ExpectedReplies(BitMap *expected_replies_list)
{
  // TBD: Expected replies should be calculated as a running average
  //      from replies returned from a periodic inquiry message.

  int replies          = 0;
  ParentSiblingPeer *p = (ParentSiblingPeer *)this->_next;
  while (p) {
    replies += p->ExpectedReplies(expected_replies_list);
    p = (ParentSiblingPeer *)p->GetNext();
  }
  return replies;
}

int
MultiCastPeer::ValidSender(sockaddr *sa)
{
  // TBD: Use hash function
  // Make sure sockaddr_in corresponds to a defined peer in the
  //  MultiCast group.
  Peer *P = _next;
  while (P) {
    if (ats_ip_addr_eq(P->GetIP(), sa) && (ats_ip_port_cast(P->GetIP()) == ats_ip_port_cast(sa))) {
      return 1;
    } else {
      P = P->GetNext();
    }
  }
  return 0;
}

void
MultiCastPeer::LogSendMsg(ICPMsg_t *m, sockaddr const *sa)
{
  // Note: ICPMsg_t (m) is in network byte order
  if (sa) {
    // UniCast send on MultiCast interface, only update stats for
    //  target Peer.
    //
    Peer *p;
    p = FindMultiCastChild(IpAddr(sa), ats_ip_port_host_order(sa));
    if (p) {
      ((ParentSiblingPeer *)p)->LogSendMsg(m, sa);
    }

  } else {
    // Note numerous stats on MultiCast peer and each member peer
    _stats.last_send = Thread::get_hrtime();
    _stats.sent[m->h.opcode]++;
    _stats.total_sent++;

    Peer *p = _next;
    while (p) {
      ((ParentSiblingPeer *)p)->LogSendMsg(m, sa);
      p = p->GetNext();
    }
  }
}

int
MultiCastPeer::IsOnline()
{
  return (_ICPpr->GetConfig()->globalConfig()->ICPmulticastConfigured());
}

int
MultiCastPeer::AddMultiCastChild(Peer *P)
{
  // Add (Peer *) to the given MultiCast structure.
  // Make sure child (ip,port) is unique.
  sockaddr const *ip = P->GetIP();
  if (FindMultiCastChild(IpAddr(ip), ats_ip_port_host_order(ip))) {
    ip_text_buffer x;
    Warning("bad icp.config, multiple multicast child definitions for ip=%s", ats_ip_ntop(ip, x, sizeof(x)));
    return 0; // Not added, already exists
  } else {
    P->SetNext(this->_next);
    this->_next = P;
    ++_mc.defined_members;
    return 1; // Added
  }
}

Peer *
MultiCastPeer::FindMultiCastChild(IpAddr const &addr, uint16_t port)
{
  // Locate child (Peer *) with the given (ip,port). This is split out
  // rather than using a sockaddr so we can indicate the port is to not
  // be checked (@a port == 0).
  Peer *curP = this->_next;
  while (curP) {
    sockaddr const *peer_ip = curP->GetIP();
    if (addr == peer_ip && (!port || port == ats_ip_port_host_order(peer_ip))) {
      return curP;
    } else {
      curP = curP->GetNext();
    }
  }
  return nullptr;
}

//-------------------------------------------------------------------------
// Class PeriodicCont member functions (abstract base class)
//      Look for TS ICP configuration changes by periodically looking.
//-------------------------------------------------------------------------
using ICPPeriodicContHandler = int (ICPPeriodicCont::*)(int, void *);
PeriodicCont::PeriodicCont(ICPProcessor *icpP) : Continuation(nullptr), _ICPpr(icpP)
{
  mutex = new_ProxyMutex();
}

PeriodicCont::~PeriodicCont()
{
  mutex = nullptr;
}

//-----------------------------------------
// Class ICPPeriodicCont member functions
//-----------------------------------------
ICPPeriodicCont::ICPPeriodicCont(ICPProcessor *icpP)
  : PeriodicCont(icpP), _last_icp_config_callouts(0), _global_config_changed(0), _peer_config_changed(0)
{
}

int
ICPPeriodicCont::PeriodicEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
  int do_reconfig     = 0;
  ICPConfiguration *C = _ICPpr->GetConfig();

  if (C->GlobalConfigChange()) {
    do_reconfig = 1;
  }

  int configcallouts = C->ICPConfigCallouts();
  if (_last_icp_config_callouts != configcallouts) {
    // We have a "icp.config" change callout which we
    //  have not processed.
    _last_icp_config_callouts = configcallouts;
    do_reconfig               = 1;
  }

  if (do_reconfig) {
    //
    // We have a configuration change, create worker continuation.
    //
    ICPPeriodicCont *rc = new ICPPeriodicCont(_ICPpr);
    SET_CONTINUATION_HANDLER(rc, (ICPPeriodicContHandler)&ICPPeriodicCont::DoReconfigAction);
    eventProcessor.schedule_imm(rc);
  }
  return EVENT_CONT;
}

int
ICPPeriodicCont::DoReconfigAction(int event, Event *e)
{
  //************************************************************
  // Initiate reconfiguration action if any global or peer
  // configuration changes have occured.
  //************************************************************
  ICPConfiguration *C = _ICPpr->GetConfig();

  for (;;) {
    switch (event) {
    case EVENT_IMMEDIATE:
    case EVENT_INTERVAL: {
      ink_assert(!_global_config_changed && !_peer_config_changed);
      if (C->Lock()) {
        ICP_INCREMENT_DYN_STAT(reconfig_polls_stat);
        if (C->GlobalConfigChange()) {
          _global_config_changed = 1;
        }
        //
        // TS Configuration management makes callouts whenever changes
        // are made to "icp.config", which describes the ICP peer
        // configuration.
        //
        if (C->PeerConfigChange()) {
          _peer_config_changed = 1;
        }
        if (_global_config_changed || _peer_config_changed) {
          //
          // Start the reconfiguration sequence.
          //
          ICP_INCREMENT_DYN_STAT(reconfig_events_stat);
          ICPProcessor::ReconfigState_t NextState;

          NextState = _ICPpr->ReconfigureStateMachine(ICPProcessor::RC_RECONFIG, _global_config_changed, _peer_config_changed);
          if (NextState == ICPProcessor::RC_DONE) {
            // Completed all reconfiguration actions.
            // ReconfigureStateMachine() has invoked C->Unlock()
            delete this;
            return EVENT_DONE;
          } else {
            // Delay and restart update.
            _global_config_changed = 0;
            _peer_config_changed   = 0;
            C->Unlock();
            e->schedule_in(HRTIME_MSECONDS(RETRY_INTERVAL_MSECS));
            return EVENT_CONT;
          }

        } else {
          // No configuration changes detected.
          C->Unlock();
        }

      } else {
        // Missed lock, retry later
        e->schedule_in(HRTIME_MSECONDS(RETRY_INTERVAL_MSECS));
        return EVENT_CONT;
      }
      delete this;
      return EVENT_DONE;
    }
    default: {
      ink_release_assert(!"ICPPeriodicCont::DoReconfigAction() bad event");
    }
    } // End of switch
  }   // End of for

  return EVENT_DONE;
}

//----------------------------------------------------------------
// Class ICPlog member functions
//  Basic accessor object used by the new logging subsystem
//  for squid access log data for ICP queries.
//----------------------------------------------------------------
ink_hrtime
ICPlog::GetElapsedTime()
{
  return (Thread::get_hrtime() - _s->_start_time);
}

sockaddr const *
ICPlog::GetClientIP()
{
  return &_s->_sender.sa;
}

in_port_t
ICPlog::GetClientPort()
{
  return _s->_sender.port();
}

SquidLogCode
ICPlog::GetAction()
{
  if (_s->_queryResult == CACHE_EVENT_LOOKUP) {
    return SQUID_LOG_UDP_HIT;
  } else {
    return SQUID_LOG_UDP_MISS;
  }
}

const char *
ICPlog::GetCode()
{
  static const char *const ICPCodeStr = "000";
  return ICPCodeStr;
}

int
ICPlog::GetSize()
{
  return ntohs(_s->_rICPmsg->h.msglen);
}

const char *
ICPlog::GetMethod()
{
  return HTTP_METHOD_ICP_QUERY;
}

const char *
ICPlog::GetURI()
{
  return (const char *)_s->_rICPmsg->un.query.URL;
}

const char *
ICPlog::GetIdent()
{
  static const char *const ICPidentStr = "";
  return ICPidentStr;
}

SquidHierarchyCode
ICPlog::GetHierarchy()
{
  return SQUID_HIER_NONE;
}

const char *
ICPlog::GetFromHost()
{
  static const char *const FromHostStr = "";
  return FromHostStr;
}

const char *
ICPlog::GetContentType()
{
  static const char *const ICPcontentTypeStr = "";
  return ICPcontentTypeStr;
}

//*****************************************************************************
// ICP Debug support.
//*****************************************************************************
//
static const char *ICPstatNames[] = {"icp_stat_def",
                                     "config_mgmt_callouts_stat",
                                     "reconfig_polls_stat",
                                     "reconfig_events_stat",
                                     "invalid_poll_data_stat",
                                     "no_data_read_stat",
                                     "short_read_stat",
                                     "invalid_sender_stat",
                                     "read_not_v2_icp_stat",
                                     "icp_remote_query_requests_stat",
                                     "icp_remote_responses_stat",
                                     "icp_cache_lookup_success_stat",
                                     "icp_cache_lookup_fail_stat",
                                     "query_response_write_stat",
                                     "query_response_partial_write_stat",
                                     "no_icp_request_for_response_stat",
                                     "icp_response_request_nolock_stat",
                                     "icp_start_icpoff_stat",
                                     "send_query_partial_write_stat",
                                     "icp_queries_no_expected_replies_stat",
                                     "icp_query_hits_stat",
                                     "icp_query_misses_stat",
                                     "invalid_icp_query_response_stat",
                                     "icp_query_requests_stat",
                                     "total_icp_response_time_stat",
                                     "total_udp_send_queries_stat",
                                     "total_icp_request_time_stat",
                                     "icp_total_reloads",
                                     "icp_pending_reloads",
                                     "icp_reload_start_aborts",
                                     "icp_reload_connect_aborts",
                                     "icp_reload_read_aborts",
                                     "icp_reload_write_aborts",
                                     "icp_reload_successes",
                                     "icp_stat_count",
                                     ""};

void
dumpICPstatEntry(int i, const char *name)
{
  int l = strlen(name);
  int64_t sval, cval;

  RecRawStat *p = RecGetGlobalRawStatPtr(icp_rsb, i);
  sval          = p->sum;
  cval          = p->count;

  printf("%-32s %12" PRId64 " %16" PRId64 " %17.4f\n", &name[l > 31 ? l - 31 : 0], cval, sval,
         cval ? (((double)sval) / ((double)cval)) : 0.0);
}

void
dumpICPstats()
{
  printf("\n");
  int i;
  for (i = 0; i < icp_stat_count; ++i) {
    dumpICPstatEntry(i, ICPstatNames[i]);
  }
}

void
ICPProcessor::DumpICPConfig()
{
  Peer *P;
  PeerType_t type;
  int id;
  ip_port_text_buffer ipb;

  Debug("icp", "On=%d, MultiCast=%d, Timeout=%d LocalCacheLookup=%d", GetConfig()->globalConfig()->ICPconfigured(),
        GetConfig()->globalConfig()->ICPmulticastConfigured(), GetConfig()->globalConfig()->ICPqueryTimeout(),
        GetConfig()->globalConfig()->ICPLocalCacheLookup());
  Debug("icp", "StaleLookup=%d, ReplyToUnknowPeer=%d, DefaultReplyPort=%d", GetConfig()->globalConfig()->ICPStaleLookup(),
        GetConfig()->globalConfig()->ICPReplyToUnknownPeer(), GetConfig()->globalConfig()->ICPDefaultReplyPort());

  for (int i = 0; i < (_nPeerList + 1); i++) {
    P    = IdToPeer(i);
    id   = P->GetPeerID();
    type = P->GetType();
    const char *str_type;

    switch (type) {
    case PEER_PARENT: {
      str_type = "P";
      break;
    }
    case PEER_SIBLING: {
      str_type = "S";
      break;
    }
    case PEER_LOCAL: {
      str_type = "L";
      break;
    }
    case PEER_MULTICAST: {
      str_type = "M";
      break;
    }
    default: {
      str_type = "N";
      break;
    }
    } // End of switch

    if (*str_type == 'M') {
      Debug("icp", "[%d]: Type=%s IP=%s", id, str_type, ats_ip_nptop(P->GetIP(), ipb, sizeof(ipb)));
    } else {
      ParentSiblingPeer *Pps = static_cast<ParentSiblingPeer *>(P);
      Debug("icp", "[%d]: Type=%s IP=%s PPort=%d Host=%s", id, str_type, ats_ip_nptop(P->GetIP(), ipb, sizeof(ipb)),
            Pps->GetConfig()->GetProxyPort(), Pps->GetConfig()->GetHostname());

      Debug("icp", "[%d]: MC ON=%d MC_IP=%s MC_TTL=%d", id, Pps->GetConfig()->MultiCastMember(),
            Pps->GetConfig()->GetMultiCastIPAddr().toString(ipb, sizeof(ipb)), Pps->GetConfig()->GetMultiCastTTL());
    }
  }
}

//*****************************************************************************

// End of ICPConfig.cc
