/** @file WCCP Configuration processing.

    @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.
 */

#include "WccpLocal.h"
#include <sstream>
#include <tsconfig/TsValue.h>
#include <arpa/inet.h>
#include <iostream>
#include <errno.h>
#include <stdlib.h>

using ts::config::Configuration;
using ts::config::Value;

// Support that must go in the standard namespace.
namespace std
{
inline ostream &
operator<<(ostream &s, ts::ConstBuffer const &b)
{
  if (b._ptr)
    s.write(b._ptr, b._size);
  else
    s << b._size;
  return s;
}

} // namespace std

// WCCP related things that are file local.
namespace
{
using namespace wccp;

// Scratch global list of seed router addresses.
// Yeah, not thread safe, but it's just during configuration load.
// Logic somewhat changed - can we move in to the load method?
std::vector<uint32_t> Seed_Router;

// Names used for various elements and properties.
static const char *const SVC_NAME = "service";

static const char *const SVC_PROP_ID           = "id";
static const char *const SVC_PROP_TYPE         = "type";
static const char *const SVC_PROP_PRIORITY     = "priority";
static const char *const SVC_PROP_PROTOCOL     = "protocol";
static const char *const SVC_PROP_PRIMARY_HASH = "primary-hash";
static const char *const SVC_PROP_ALT_HASH     = "alt-hash";
static const char *const SVC_PROP_PORTS        = "ports";
static const char *const SVC_PROP_PORT_TYPE    = "port-type";
static const char *const SVC_PROP_SECURITY     = "security";
static const char *const SVC_PROP_ROUTERS      = "routers";
static const char *const SVC_PROP_FORWARD      = "forward";
static const char *const SVC_PROP_RETURN       = "return";
static const char *const SVC_PROP_ASSIGN       = "assignment";
static const char *const SVC_PROP_PROC         = "proc-name";

static const char *const SECURITY_PROP_OPTION = "option";
static const char *const SECURITY_PROP_KEY    = "key";

/// Helper structure for processing configuration strings.
struct CfgString {
  const char *m_text; ///< Text value of the option.
  bool m_found;       ///< String was found.
};
typedef std::vector<CfgString> CfgOpts;

#define N_OPTS(x) (sizeof(x) / sizeof(*x))

CfgString FORWARD_OPTS[]    = {{"gre", false}, {"l2", false}};
size_t const N_FORWARD_OPTS = sizeof(FORWARD_OPTS) / sizeof(*FORWARD_OPTS);

CfgString RETURN_OPTS[]    = {{"gre", false}, {"l2", false}};
size_t const N_RETURN_OPTS = sizeof(RETURN_OPTS) / sizeof(*RETURN_OPTS);

CfgString ASSIGN_OPTS[] = {{"hash", false}, {"mask", false}};

CfgString HASH_OPTS[] = {{"src_ip", false}, {"dst_ip", false}, {"src_port", false}, {"dst_port", false}};

ts::Errata::Code
code_max(ts::Errata const &err)
{
  ts::Errata::Code zret            = std::numeric_limits<ts::Errata::Code::raw_type>::min();
  ts::Errata::const_iterator spot  = err.begin();
  ts::Errata::const_iterator limit = err.end();
  for (; spot != limit; ++spot)
    zret = std::max(zret, spot->getCode());
  return zret;
}

struct ValueNamePrinter {
  Value const &_v;
  ValueNamePrinter(Value const &v) : _v(v) {}
};

std::ostream &
operator<<(std::ostream &out, ValueNamePrinter const &v)
{
  ts::ConstBuffer const &name = v._v.getName();
  if (name._ptr)
    out << "'" << name << "'";
  else
    out << v._v.getIndex();
  return out;
}

#if 0 /* silence -Wunused-function */
ts::Errata::Message File_Syntax_Error(int line, const char* text) {
  std::ostringstream out;
  out << "Service configuration error. Line "
      << line
      << ": " << text
    ;
  return ts::Errata::Message(1, LVL_FATAL, out.str());
}

ts::Errata::Message File_Read_Error(const char* text) {
  std::ostringstream out;
  out << "Failed to parse configuration file."
      << ": " << text
    ;
  return ts::Errata::Message(2, LVL_FATAL, out.str());
}
#endif

ts::Errata::Message
Unable_To_Create_Service_Group(int line)
{
  std::ostringstream out;
  out << "Unable to create service group at line " << line << " because of configuration errors.";
  return ts::Errata::Message(23, LVL_FATAL, out.str());
}

ts::Errata::Message
Services_Not_Found()
{
  return ts::Errata::Message(3, LVL_INFO, "No services found in configuration.");
}

ts::Errata::Message
Services_Not_A_Sequence()
{
  return ts::Errata::Message(4, LVL_INFO, "The 'services' setting was not a list nor array.");
}

ts::Errata::Message
Service_Not_A_Group(int line)
{
  std::ostringstream out;
  out << "'" << SVC_NAME << "' must be a group at line " << line << ".";
  return ts::Errata::Message(5, LVL_WARN, out.str());
}

ts::Errata::Message
Service_Type_Defaulted(wccp::ServiceGroup::Type type, int line)
{
  std::ostringstream out;
  out << "'type' not found in " << SVC_NAME << " at line " << line << "' -- defaulting to "
      << (type == wccp::ServiceGroup::STANDARD ? "STANDARD" : "DYNAMIC");
  return ts::Errata::Message(6, LVL_INFO, out.str());
}

ts::Errata::Message
Service_Type_Invalid(ts::ConstBuffer const &text, int line)
{
  std::ostringstream out;
  out << "Service type '" << text << "' at line " << line << " invalid. Must be \"STANDARD\" or \"DYNAMIC\"";
  return ts::Errata::Message(7, LVL_WARN, out.str());
}

ts::Errata::Message
Prop_Not_Found(const char *prop_name, char const *group_name, int line)
{
  std::ostringstream out;
  out << "Required '" << prop_name << "' property not found in '" << group_name << "' at line " << line << ".";
  return ts::Errata::Message(8, LVL_WARN, out.str());
}

ts::Errata::Message
Prop_Invalid_Type(Value const &prop_cfg, ts::config::ValueType expected)
{
  std::ostringstream out;
  out << "'" << prop_cfg.getName() << "' at line " << prop_cfg.getSourceLine() << " is of type '" << prop_cfg.getType()
      << "' instead of required type '" << expected << "'.";
  return ts::Errata::Message(9, LVL_WARN, out.str());
}

ts::Errata::Message
Prop_List_Invalid_Type(Value const &elt_cfg, ///< List element.
                       ts::config::ValueType expected)
{
  std::ostringstream out;
  out << "Element " << ValueNamePrinter(elt_cfg) << " at line " << elt_cfg.getSourceLine() << " in the aggregate property '"
      << elt_cfg.getParent().getName() << "' is of type '" << elt_cfg.getType() << "' instead of required type '" << expected
      << "'.";
  return ts::Errata::Message(9, LVL_WARN, out.str());
}

ts::Errata::Message
Svc_Prop_Out_Of_Range(const char *name, Value const &elt_cfg, int v, int min, int max)
{
  std::ostringstream out;
  out << "Service property '" << name << "' at line " << elt_cfg.getSourceLine() << " has a value " << v
      << " that is not in the allowed range of " << min << ".." << max << ".";
  return ts::Errata::Message(10, LVL_WARN, out.str());
}

ts::Errata::Message
Svc_Prop_Ignored(const char *name, int line)
{
  std::ostringstream out;
  out << "Service property '" << name << "' at line " << line << " ignored because the service is of type standard.";
  return ts::Errata::Message(11, LVL_INFO, out.str());
}

#if 0 /* silence -Wunused-function */
ts::Errata::Message Svc_Flags_No_Hash_Set(int line) {
  std::ostringstream out;
  out << "Service flags have no hash set at line " << line
    ;
  return ts::Errata::Message(12, LVL_WARN, out.str());
}

ts::Errata::Message Svc_Flags_Ignored(int line) {
  std::ostringstream out;
  out << "Invalid service flags at line  " << line
      << " ignored."
    ;
  return ts::Errata::Message(13, LVL_INFO, out.str());
}
#endif

ts::Errata::Message
Svc_Ports_Too_Many(int line, int n)
{
  std::ostringstream out;
  out << "Excess ports ignored at line " << line << ". " << n << " ports specified, only" << wccp::ServiceGroup::N_PORTS
      << " supported.";
  return ts::Errata::Message(14, LVL_INFO, out.str());
}

ts::Errata::Message
Svc_Ports_Malformed(int line)
{
  std::ostringstream out;
  out << "Port value ignored (not a number) at line " << line << ".";
  return ts::Errata::Message(15, LVL_INFO, out.str());
}

ts::Errata::Message
Svc_Ports_None_Valid(int line)
{
  std::ostringstream out;
  out << "A '" << SVC_PROP_PORTS << "' property was found at line " << line << " but none of the ports were valid.";
  return ts::Errata::Message(17, LVL_WARN, out.str());
}

ts::Errata::Message
Svc_Ports_Not_Found(int line)
{
  std::ostringstream out;
  out << "Ports not found in service at line " << line << ". Ports must be defined for a dynamic service.";
  return ts::Errata::Message(18, LVL_WARN, out.str());
}

ts::Errata::Message
Svc_Prop_Ignored_In_Standard(const char *name, int line)
{
  std::ostringstream out;
  out << "Service property '" << name << "' at line " << line << " ignored because the service is of type STANDARD.";
  return ts::Errata::Message(19, LVL_INFO, out.str());
}

ts::Errata::Message
Security_Opt_Invalid(ts::ConstBuffer const &text, int line)
{
  std::ostringstream out;
  out << "Security option '" << text << "' at line " << line << " is invalid. It must be 'none' or 'md5'.";
  return ts::Errata::Message(20, LVL_WARN, out.str());
}

ts::Errata::Message
Value_Malformed(const char *name, char const *text, int line)
{
  std::ostringstream out;
  out << "'" << name << "' value '" << text << "' malformed at line " << line << ".";
  return ts::Errata::Message(21, LVL_WARN, out.str());
}

ts::Errata::Message
No_Valid_Routers(int line)
{
  std::ostringstream out;
  out << "No valid IP address for routers found for Service Group at line " << line << ".";
  return ts::Errata::Message(22, LVL_WARN, out.str());
}

ts::Errata::Message
Ignored_Option_Value(ts::ConstBuffer const &text, ts::ConstBuffer const &name, int line)
{
  std::ostringstream out;
  out << "Value '" << text << "' at line " << line << " was ignored because it is not a valid option for '" << name << "'.";
  return ts::Errata::Message(24, LVL_INFO, out.str());
}

ts::Errata::Message
Ignored_Opt_Errors(const char *name, int line)
{
  std::ostringstream out;
  out << "Errors in  '" << name << "' at line " << line << " were ignored.";
  return ts::Errata::Message(28, LVL_INFO, out.str());
}

ts::Errata::Message
List_Valid_Opts(ts::ConstBuffer const &name, int line, CfgString *values, size_t n)
{
  std::ostringstream out;
  out << "Valid values for the '" << name << "' property at line " << line << " are: ";
  out << '"' << values[0].m_text << '"';
  for (size_t i = 1; i < n; ++i)
    out << ", \"" << values[i].m_text << '"';
  out << '.';
  return ts::Errata::Message(29, LVL_INFO, out.str());
}

ts::Errata::Message
Port_Type_Invalid(ts::ConstBuffer const &text, int line)
{
  std::ostringstream out;
  out << "Value '" << text << "' at line " << line << "for property '" << SVC_PROP_PORT_TYPE
      << "' is invalid. It must be 'src' or 'dst'.";
  return ts::Errata::Message(30, LVL_WARN, out.str());
}

} // anon namespace

namespace wccp
{
inline bool
operator==(ts::ConstBuffer const &b, const char *text)
{
  return 0 == strncasecmp(text, b._ptr, b._size);
}

inline bool
operator==(const char *text, ts::ConstBuffer const &b)
{
  return 0 == strncasecmp(text, b._ptr, b._size);
}

ts::Errata
load_option_set(Value const &setting, CfgString *opts, size_t count)
{
  ts::Errata zret;
  CfgString *spot;
  CfgString *limit            = opts + count;
  ts::ConstBuffer const &name = setting.getName();
  int src_line                = setting.getSourceLine();

  // Clear all found flags.
  for (spot = opts; spot < limit; ++spot)
    spot->m_found = false;

  // Walk through the strings in the setting.
  if (setting.isContainer()) {
    int nr         = setting.childCount();
    bool list_opts = false;
    for (int i = 0; i < nr; ++i) {
      Value item = setting[i];
      if (ts::config::StringValue == item.getType()) {
        ts::ConstBuffer text = item.getText();
        for (spot = opts; spot < limit; ++spot) {
          if (spot->m_text == text) {
            spot->m_found = true;
            break;
          }
        }
        if (spot >= limit) {
          zret.push(Ignored_Option_Value(text, name, item.getSourceLine()));
          list_opts = true;
        }
      } else {
        zret.push(Prop_Invalid_Type(setting, ts::config::StringValue));
      }
    }
    if (list_opts)
      zret.push(List_Valid_Opts(name, src_line, opts, count));
  } else {
    zret.push(Prop_Invalid_Type(setting, ts::config::ListValue));
  }
  return zret;
}

/** On success this returns a non @c NULL pointer if the MD5 option is
    set.  In that case the pointer points at the MD5 key.  Otherwise
    the option was none and the pointer is @c NULL
 */
ts::Rv<ts::ConstBuffer>
load_security(Value const &setting ///< Security setting.
              )
{
  ts::Rv<ts::ConstBuffer> zret;
  int src_line;
  ts::ConstBuffer text;

  zret.result().set(0);

  src_line = setting.getSourceLine();
  if (ts::config::GroupValue == setting.getType()) {
    Value opt = setting[SECURITY_PROP_OPTION];
    if (opt) {
      if (ts::config::StringValue == opt.getType()) {
        text = opt.getText();
        if ("none" == text) {
        } else if ("md5" == text) {
          Value key = setting[SECURITY_PROP_KEY];
          if (key) {
            if (ts::config::StringValue == key.getType()) {
              zret = key.getText();
            } else {
              zret.push(Prop_Invalid_Type(key, ts::config::StringValue));
            }
          } else {
            zret.push(Prop_Not_Found(SECURITY_PROP_KEY, SVC_PROP_SECURITY, src_line));
          }
        } else {
          zret.push(Security_Opt_Invalid(text, opt.getSourceLine()));
        }
      } else {
        zret.push(Prop_Invalid_Type(opt, ts::config::StringValue));
      }
    } else {
      zret.push(Prop_Not_Found(SECURITY_PROP_OPTION, SVC_PROP_SECURITY, src_line));
    }
  } else {
    zret.push(Prop_Invalid_Type(setting, ts::config::GroupValue));
  }
  return zret;
}

/// Process a router address list.
ts::Errata
load_routers(Value const &setting,        ///< Source of addresses.
             std::vector<uint32_t> &addrs ///< Output list
             )
{
  ts::Errata zret;
  const char *text;
  static const char *const NAME = "IPv4 Address";

  if (setting.isContainer()) {
    int nr = setting.childCount();
    for (int i = 0; i < nr; ++i) {
      Value const &addr_cfg = setting[i];
      int addr_line         = addr_cfg.getSourceLine();
      in_addr addr;
      if (ts::config::StringValue == addr_cfg.getType()) {
        text = addr_cfg.getText()._ptr;
        if (inet_aton(text, &addr))
          addrs.push_back(addr.s_addr);
        else
          zret.push(Value_Malformed(NAME, text, addr_line));
      } else {
        zret.push(Prop_List_Invalid_Type(addr_cfg, ts::config::StringValue));
      }
    }
  } else {
    zret.push(Prop_Invalid_Type(setting, ts::config::ListValue));
  }
  return zret;
}

ts::Errata
CacheImpl::loadServicesFromFile(const char *path)
{
  ts::Errata zret;
  int src_line = 0;              // scratch for local source line caching.
  std::vector<uint32_t> routers; // scratch per service loop.
  Value prop;                    // scratch var.

  ts::Rv<Configuration> cv = Configuration::loadFromPath(path);
  if (!cv.isOK())
    return cv.errata();

  ts::config::Configuration cfg = cv.result();
  Value svc_list                = cfg.find("services");
  // No point in going on from here.
  if (!svc_list)
    return Services_Not_Found();

  if (!svc_list.isContainer())
    return Services_Not_A_Sequence();

  // Check for global (default) security setting.
  if ((prop = cfg[SVC_PROP_SECURITY]).hasValue()) {
    ts::Rv<ts::ConstBuffer> rv = load_security(prop);
    if (rv.isOK())
      this->useMD5Security(rv);
    else
      zret.pull(rv.errata());
  }

  if ((prop = cfg[SVC_PROP_ROUTERS]).hasValue()) {
    zret.pull(load_routers(prop, Seed_Router).doNotLog());
  }

  int idx, nsvc;
  for (idx = 0, nsvc = svc_list.childCount(); idx < nsvc; ++idx) {
    int x; // scratch int.
    const char *md5_key = 0;
    ts::ConstBuffer text;
    SecurityOption security_style = SECURITY_NONE;
    bool use_group_local_security = false;
    Value const &svc_cfg          = svc_list[idx];
    int svc_line                  = svc_cfg.getSourceLine();
    ServiceGroup svc_info;

    if (ts::config::GroupValue != svc_cfg.getType()) {
      zret.push(Service_Not_A_Group(svc_line));
      continue;
    }

    // Get the service ID.
    if ((prop = svc_cfg[SVC_PROP_ID]).hasValue()) {
      if (ts::config::IntegerValue == prop.getType()) {
        x = atoi(prop.getText()._ptr);
        if (0 <= x && x <= 255)
          svc_info.setSvcId(x);
        else
          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, prop, x, 0, 255));
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::IntegerValue));
      }
    } else {
      zret.push(Prop_Not_Found(SVC_PROP_ID, SVC_NAME, svc_line));
    }

    // Service type.
    if ((prop = svc_cfg[SVC_PROP_TYPE]).hasValue()) {
      if (ts::config::StringValue == prop.getType()) {
        text = prop.getText();
        if ("DYNAMIC" == text)
          svc_info.setSvcType(ServiceGroup::DYNAMIC);
        else if ("STANDARD" == text)
          svc_info.setSvcType(ServiceGroup::STANDARD);
        else
          zret.push(Service_Type_Invalid(text, prop.getSourceLine()));
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::StringValue));
      }
    } else { // default type based on ID.
      ServiceGroup::Type svc_type = svc_info.getSvcId() <= ServiceGroup::RESERVED ? ServiceGroup::STANDARD : ServiceGroup::DYNAMIC;
      svc_info.setSvcType(svc_type);
      zret.push(Service_Type_Defaulted(svc_type, svc_line));
    }

    // Get the protocol.
    if ((prop = svc_cfg[SVC_PROP_PROTOCOL]).hasValue()) {
      if (svc_info.getSvcType() == ServiceGroup::STANDARD) {
        zret.push(Svc_Prop_Ignored(SVC_PROP_PROTOCOL, prop.getSourceLine()));
      } else if (ts::config::IntegerValue == prop.getType()) {
        x = atoi(prop.getText()._ptr);
        if (0 <= x && x <= 255)
          svc_info.setProtocol(x);
        else
          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, prop, x, 0, 255));
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::IntegerValue));
      }
    } else if (svc_info.getSvcType() != ServiceGroup::STANDARD) {
      // Required if it's not standard / predefined.
      zret.push(Prop_Not_Found(SVC_PROP_PROTOCOL, SVC_NAME, svc_line));
    }

    // Get the priority.
    svc_info.setPriority(0); // OK to default to this value.
    if ((prop = svc_cfg[SVC_PROP_PRIORITY]).hasValue()) {
      if (svc_info.getSvcType() == ServiceGroup::STANDARD) {
        zret.push(Svc_Prop_Ignored(SVC_PROP_PRIORITY, prop.getSourceLine()));
      } else if (ts::config::IntegerValue == prop.getType()) {
        x = atoi(prop.getText()._ptr);
        if (0 <= x && x <= 255)
          svc_info.setPriority(x);
        else
          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, prop, x, 0, 255));
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::IntegerValue));
      }
    }

    // Service flags.
    svc_info.setFlags(0);
    if ((prop = svc_cfg[SVC_PROP_PRIMARY_HASH]).hasValue()) {
      ts::Errata status = load_option_set(prop, HASH_OPTS, N_OPTS(HASH_OPTS));
      uint32_t f        = 0;
      src_line          = prop.getSourceLine();
      for (size_t i = 0; i < N_OPTS(HASH_OPTS); ++i)
        if (HASH_OPTS[i].m_found)
          f |= ServiceGroup::SRC_IP_HASH << i;
      if (f) {
        svc_info.enableFlags(f);
        if (!status)
          zret.push(Ignored_Opt_Errors(SVC_PROP_PRIMARY_HASH, src_line).set(status));
      } else {
        zret.push(List_Valid_Opts(prop.getName(), src_line, HASH_OPTS, N_OPTS(HASH_OPTS)).set(status));
      }
    } else {
      zret.push(Prop_Not_Found(SVC_PROP_PRIMARY_HASH, SVC_NAME, svc_line));
    }

    if ((prop = svc_cfg[SVC_PROP_ALT_HASH]).hasValue()) {
      ts::Errata status = load_option_set(prop, HASH_OPTS, N_OPTS(HASH_OPTS));
      uint32_t f        = 0;
      src_line          = prop.getSourceLine();
      for (size_t i = 0; i < N_OPTS(HASH_OPTS); ++i)
        if (HASH_OPTS[i].m_found)
          f |= ServiceGroup::SRC_IP_ALT_HASH << i;
      if (f)
        svc_info.enableFlags(f);
      if (!status)
        zret.push(Ignored_Opt_Errors(SVC_PROP_ALT_HASH, src_line).set(status));
    }

    if ((prop = svc_cfg[SVC_PROP_PORT_TYPE]).hasValue()) {
      src_line = prop.getSourceLine();
      if (ts::config::StringValue == prop.getType()) {
        text = prop.getText();
        if ("src" == text)
          svc_info.enableFlags(ServiceGroup::PORTS_SOURCE);
        else if ("dst" == text)
          svc_info.disableFlags(ServiceGroup::PORTS_SOURCE);
        else
          zret.push(Port_Type_Invalid(text, src_line));
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::StringValue));
      }
    }

    // Ports for service.
    svc_info.clearPorts();
    if ((prop = svc_cfg[SVC_PROP_PORTS]).hasValue()) {
      src_line = prop.getSourceLine();
      if (ServiceGroup::STANDARD == svc_info.getSvcType()) {
        zret.push(Svc_Prop_Ignored_In_Standard(SVC_PROP_PORTS, src_line));
      } else {
        if (prop.isContainer()) {
          size_t nport = prop.childCount();
          size_t pidx, sidx;
          bool malformed_error = false;
          // Clip to maximum protocol allowed ports.
          if (nport > ServiceGroup::N_PORTS) {
            zret.push(Svc_Ports_Too_Many(src_line, nport));
            nport = ServiceGroup::N_PORTS;
          }
          // Step through the ports.
          for (pidx = sidx = 0; pidx < nport; ++pidx) {
            Value const &port_cfg = prop[pidx];
            if (ts::config::IntegerValue == port_cfg.getType()) {
              x = atoi(port_cfg.getText()._ptr);
              if (0 <= x && x <= 65535)
                svc_info.setPort(sidx++, x);
              else
                zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_PORTS, port_cfg, x, 0, 65535));
            } else if (!malformed_error) { // only report this once.
              zret.push(Svc_Ports_Malformed(src_line));
              malformed_error = true;
            }
          }
          if (sidx)
            svc_info.enableFlags(ServiceGroup::PORTS_DEFINED);
          else
            zret.push(Svc_Ports_None_Valid(src_line));
        } else {
          zret.push(Prop_Invalid_Type(prop, ts::config::ListValue));
        }
      }
    } else if (ServiceGroup::STANDARD != svc_info.getSvcType()) {
      zret.push(Svc_Ports_Not_Found(svc_line));
    }

    // Security option for this service group.
    if ((prop = svc_cfg[SVC_PROP_SECURITY]).hasValue()) {
      ts::Rv<ts::ConstBuffer> security = load_security(prop);
      if (security.isOK()) {
        use_group_local_security = true;
        if (security.result()._ptr) {
          md5_key        = security.result()._ptr;
          security_style = SECURITY_MD5;
        } else {
          security_style = SECURITY_NONE;
        }
      }
      zret.pull(security.errata());
    }

    // Get any group specific routers.
    routers.clear(); // reset list.
    if ((prop = svc_cfg[SVC_PROP_ROUTERS]).hasValue()) {
      ts::Errata status = load_routers(prop, routers);
      if (!status)
        zret.push(ts::Errata::Message(23, LVL_INFO, "Router specification invalid.").set(status));
    }
    if (!routers.size() && !Seed_Router.size())
      zret.push(No_Valid_Routers(svc_line));

    // See if can proceed with service group creation.
    ts::Errata::Code code = code_max(zret);
    if (code >= LVL_WARN) {
      zret = Unable_To_Create_Service_Group(svc_line).set(zret);
      return zret;
    }

    // Properties after this are optional so we can proceed if they fail.
    GroupData &svc = this->defineServiceGroup(svc_info);

    // Is there a process we should track?
    if ((prop = svc_cfg[SVC_PROP_PROC]).hasValue()) {
      if (ts::config::StringValue == prop.getType()) {
        svc.setProcName(prop.getText());
      } else {
        zret.push(Prop_Invalid_Type(prop, ts::config::StringValue));
      }
    }

    // Add seed routers.
    std::vector<uint32_t>::iterator rspot, rlimit;
    for (rspot = routers.begin(), rlimit = routers.end(); rspot != rlimit; ++rspot)
      svc.seedRouter(*rspot);
    for (rspot = Seed_Router.begin(), rlimit = Seed_Router.end(); rspot != rlimit; ++rspot)
      svc.seedRouter(*rspot);

    if (use_group_local_security)
      svc.setSecurity(security_style).setKey(md5_key);

    // Look for optional properties.

    svc.m_packet_forward = ServiceGroup::GRE; // default
    if ((prop = svc_cfg[SVC_PROP_FORWARD]).hasValue()) {
      ts::Errata status = load_option_set(prop, FORWARD_OPTS, N_FORWARD_OPTS);
      bool gre          = FORWARD_OPTS[0].m_found;
      bool l2           = FORWARD_OPTS[1].m_found;
      if (gre || l2) {
        svc.m_packet_forward = gre ? l2 ? ServiceGroup::GRE_OR_L2 : ServiceGroup::GRE : ServiceGroup::L2;
        if (!status.isOK())
          zret.push(Ignored_Opt_Errors(SVC_PROP_FORWARD, prop.getSourceLine()).set(status));
      } else {
        zret.push(ts::Errata::Message(26, LVL_INFO, "Defaulting to GRE forwarding.").set(status));
      }
    }

    svc.m_packet_return = ServiceGroup::GRE; // default.
    if ((prop = svc_cfg[SVC_PROP_RETURN]).hasValue()) {
      ts::Errata status = load_option_set(prop, RETURN_OPTS, N_RETURN_OPTS);
      bool gre          = RETURN_OPTS[0].m_found;
      bool l2           = RETURN_OPTS[1].m_found;
      if (gre || l2) {
        svc.m_packet_return = gre ? l2 ? ServiceGroup::GRE_OR_L2 : ServiceGroup::GRE : ServiceGroup::L2;
        if (!status.isOK())
          zret.push(Ignored_Opt_Errors(SVC_PROP_RETURN, prop.getSourceLine()).set(status));
      } else {
        zret.push(ts::Errata::Message(26, LVL_INFO, "Defaulting to GRE return.").set(status));
      }
    }

    svc.m_cache_assign = ServiceGroup::HASH_ONLY; // default
    if ((prop = svc_cfg[SVC_PROP_ASSIGN]).hasValue()) {
      ts::Errata status = load_option_set(prop, ASSIGN_OPTS, N_OPTS(ASSIGN_OPTS));
      bool hash         = ASSIGN_OPTS[0].m_found;
      bool mask         = ASSIGN_OPTS[1].m_found;
      if (hash || mask) {
        svc.m_cache_assign = hash ? mask ? ServiceGroup::HASH_OR_MASK : ServiceGroup::HASH_ONLY : ServiceGroup::MASK_ONLY;
        if (!status.isOK())
          zret.push(Ignored_Opt_Errors(SVC_PROP_ASSIGN, prop.getSourceLine()).set(status));
      } else {
        status.push(ts::Errata::Message(26, LVL_INFO, "Defaulting to hash assignment only."));
        zret.push(List_Valid_Opts(prop.getName(), src_line, ASSIGN_OPTS, N_OPTS(ASSIGN_OPTS)).set(status));
      }
    }
  }
  return zret;
}

} // namespace.
