indigo_error_t
ind_cxn_cfg_stage(cJSON *config)
{
    indigo_error_t err;
    int i;

    err = ind_cfg_parse_loglevel(config, "logging.connection",
                                 OFCONNECTIONMANAGER_CONFIG_LOG_BITS_DEFAULT,
                                 &staged_config.log_flags);
    if (err != INDIGO_ERROR_NONE) {
        return err;
    }

    /* Not supporting setting log options yet */

    err = ind_cfg_lookup_int(config, "keepalive_period_ms", &staged_config.keepalive_period_ms);
    if (err == INDIGO_ERROR_NONE) {
        if (staged_config.keepalive_period_ms <= 0) {
            AIM_LOG_ERROR("'keepalive_period_ms' must be greater than 0");
            return INDIGO_ERROR_PARAM;
        }
    } else if (err == INDIGO_ERROR_PARAM) {
        AIM_LOG_ERROR("Config: Could not parse 'keepalive_period_ms'");
        return err;
    } else if (err == INDIGO_ERROR_NOT_FOUND) {
        AIM_LOG_ERROR("Config: Mising required key 'keepalive_period_ms'");
        return err;
    }

    err = parse_controllers(config);
    if (err != INDIGO_ERROR_NONE) {
        return err;
    }

    for (i = 0; i < staged_config.num_controllers; i++) {
        /* @FIXME local? listen? priority? */
        staged_config.controllers[i].config.periodic_echo_ms = staged_config.keepalive_period_ms;
        staged_config.controllers[i].config.reset_echo_count = 3; /* @FIXME */
    }

    return INDIGO_ERROR_NONE;
}
indigo_error_t
ind_cxn_cfg_stage(cJSON *config)
{
    indigo_error_t err;

    err = ind_cfg_parse_loglevel(config, "logging.connection",
                                 OFCONNECTIONMANAGER_CONFIG_LOG_BITS_DEFAULT,
                                 &staged_config.log_flags);
    if (err != INDIGO_ERROR_NONE) {
        return err;
    }

    /* Not supporting setting log options yet */

    err = ind_cfg_lookup_int(config, "keepalive_period_ms", &staged_config.keepalive_period_ms);
    if (err == INDIGO_ERROR_NONE) {
        if (staged_config.keepalive_period_ms <= 0) {
            AIM_LOG_ERROR("'keepalive_period_ms' must be greater than 0");
            return INDIGO_ERROR_PARAM;
        }
    } else if (err == INDIGO_ERROR_PARAM) {
        AIM_LOG_ERROR("Config: Could not parse 'keepalive_period_ms'");
        return err;
    } else if (err == INDIGO_ERROR_NOT_FOUND) {
        AIM_LOG_ERROR("Config: Missing required key 'keepalive_period_ms'");
        return err;
    }

    err = parse_controllers(config);
    if (err != INDIGO_ERROR_NONE) {
        return err;
    }

    /* verify TLS parameters */
    /*
     * for now, we should be able to accept:
     * - TLS config with switch cert and key specified,
     *   cipher_list and CA cert optional
     * - if cipher_list is specified, it should be valid
     * - if CA cert is specified, it should be consistent with switch cert
     * - TLS config with parameters specified, but
     *   CA cert, switch cert and switch priv key files not present
     */
    {
        cJSON *node = cJSON_GetObjectItem(config, "tls");
        if (node) {
            if (node->type != cJSON_Object) {
                AIM_LOG_ERROR("Config: expected tls to be an object");
                return INDIGO_ERROR_PARAM;
            }
            err = parse_tls_config(node);
            if (err != INDIGO_ERROR_NONE) {
                AIM_LOG_ERROR("Config: error parsing TLS config: %s",
                              indigo_strerror(err));
                return err;
            }
        } else {
            AIM_LOG_VERBOSE("Config: TLS config not found, continuing");
        }
    }

    staged_config.valid = true;

    return INDIGO_ERROR_NONE;
}
/* Parse a controller string like "tcp:127.0.0.1:6633". */
static indigo_error_t
parse_controller(struct controller *controller, cJSON *root)
{
    indigo_cxn_params_tcp_over_ipv4_t *proto;
    char *proto_str, *ip;
    int port;
    int listen;
    int prio;
    indigo_error_t err;

    err = ind_cfg_lookup_string(root, "ip_addr", &ip);
    if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'ip_addr' must be a string");
        } else if (err == INDIGO_ERROR_NOT_FOUND) {
            AIM_LOG_ERROR("Config: Missing 'ip_addr' in controller spec");
        }
        return err;
    }

    err = ind_cfg_lookup_string(root, "protocol", &proto_str);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        proto_str = "tcp";
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'protocol' must be a string");
        }
        return err;
    }

    /* @TODO support more protocol types */
    if (strcmp(proto_str, "tcp")) {
        AIM_LOG_ERROR("Config: Invalid controller protocol: %s", proto_str);
        return INDIGO_ERROR_PARAM;
    }

    err = ind_cfg_lookup_int(root, "port", &port);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        port = 6633;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'port' must be an integer");
        }
        return err;
    }

    if (port < 0 || port >= 0xffff) {
        AIM_LOG_ERROR("Config: Invalid controller port: %d", port);
        return INDIGO_ERROR_PARAM;
    }

    err = ind_cfg_lookup_bool(root, "listen", &listen);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        listen = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'listen' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_int(root, "priority", &prio);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        prio = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'priority' must be an integer");
        }
        return err;
    }

    /* TODO validate IP */

    proto = &controller->proto.tcp_over_ipv4;
    proto->protocol = INDIGO_CXN_PROTO_TCP_OVER_IPV4;
    strncpy(proto->controller_ip, ip, sizeof(proto->controller_ip));
    proto->controller_port = port;
    controller->config.listen = listen;
    controller->config.cxn_priority = prio;
    controller->config.local = 0;
    controller->config.version = OFCONNECTIONMANAGER_CONFIG_OF_VERSION;

    return INDIGO_ERROR_NONE;
}
/* Parse a controller string like "tcp:127.0.0.1:6633". */
static indigo_error_t
parse_controller(struct controller *controller, cJSON *root)
{
    indigo_cxn_protocol_t proto;
    indigo_cxn_params_tcp_over_ipv4_t *params4;
    indigo_cxn_params_tcp_over_ipv6_t *params6;
    indigo_cxn_params_unix_t *params_unix;
    char *proto_str = NULL, *ip = NULL, *unix_path = NULL;
    int port;
    int listen;
    int local;
    int prio;
    indigo_error_t err;

    err = ind_cfg_lookup_string(root, "protocol", &proto_str);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        /* Allow default protocol value */
        proto_str = "tcp";
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'protocol' must be a string");
        }
        return err;
    }

    if (strcmp(proto_str, "tcp") == 0) {
        proto = INDIGO_CXN_PROTO_TCP_OVER_IPV4;
    } else if (strcmp(proto_str, "tcp6") == 0) {
        proto = INDIGO_CXN_PROTO_TCP_OVER_IPV6;
    } else if (strcmp(proto_str, "tls") == 0) {
        proto = INDIGO_CXN_PROTO_TLS_OVER_IPV4;
    } else if (strcmp(proto_str, "tls6") == 0) {
        proto = INDIGO_CXN_PROTO_TLS_OVER_IPV6;
    } else if (strcmp(proto_str, "unix") == 0) {
        proto = INDIGO_CXN_PROTO_UNIX;
    } else {
        AIM_LOG_ERROR("Config: Invalid controller protocol: %s", proto_str);
        return INDIGO_ERROR_PARAM;
    }

    switch(proto) {
    case INDIGO_CXN_PROTO_TCP_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TCP_OVER_IPV6:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV6:
        err = ind_cfg_lookup_string(root, "ip_addr", &ip);
        if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'ip_addr' must be a string");
            } else if (err == INDIGO_ERROR_NOT_FOUND) {
                AIM_LOG_ERROR("Config: Missing 'ip_addr' in controller spec");
            }
            return err;
        }
        /*
         * WARNING: no validity checks for IP address.
         * Instead, they are performed by the cli front-end.
         */

        err = ind_cfg_lookup_int(root, "port", &port);
        if (err == INDIGO_ERROR_NOT_FOUND) {
            /* Allow default protocol value */
            port = 6633;
        } else if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'port' must be an integer");
            }
            return err;
        }
        if (port < 0 || port >= 0xffff) {
            AIM_LOG_ERROR("Config: Invalid controller port: %d", port);
            return INDIGO_ERROR_PARAM;
        }

        break;

    case INDIGO_CXN_PROTO_UNIX:
        err = ind_cfg_lookup_string(root, "unix_path", &unix_path);
        if (err < 0) {
            if (err == INDIGO_ERROR_PARAM) {
                AIM_LOG_ERROR("Config: 'unix_path' must be a string");
            } else if (err == INDIGO_ERROR_NOT_FOUND) {
                AIM_LOG_ERROR("Config: Missing 'unix_path' in controller spec");
            }
            return err;
        }
        break;

    default:
        AIM_DIE("Config: No handling for protocol %d", proto);
    }

    err = ind_cfg_lookup_bool(root, "listen", &listen);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        listen = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'listen' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_bool(root, "local", &local);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        local = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'local' must be a boolean");
        }
        return err;
    }

    err = ind_cfg_lookup_int(root, "priority", &prio);
    if (err == INDIGO_ERROR_NOT_FOUND) {
        prio = 0;
    } else if (err < 0) {
        if (err == INDIGO_ERROR_PARAM) {
            AIM_LOG_ERROR("Config: 'priority' must be an integer");
        }
        return err;
    }

    /* populate 'controller' struct */
    switch (proto) {
    case INDIGO_CXN_PROTO_TCP_OVER_IPV4:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV4:
        params4 = &controller->proto.tcp_over_ipv4;
        params4->protocol = proto;
        strncpy(params4->controller_ip, ip, sizeof(params4->controller_ip));
        params4->controller_port = port;
        break;
    case INDIGO_CXN_PROTO_TCP_OVER_IPV6:  /* fall-through */
    case INDIGO_CXN_PROTO_TLS_OVER_IPV6:
        params6 = &controller->proto.tcp_over_ipv6;
        params6->protocol = proto;
        strncpy(params6->controller_ip, ip, sizeof(params6->controller_ip));
        params6->controller_port = port;
        break;
    case INDIGO_CXN_PROTO_UNIX:
        params_unix = &controller->proto.unx;
        params_unix->protocol = proto;
        strncpy(params_unix->unix_path, unix_path, INDIGO_CXN_UNIX_PATH_LEN);
        break;
    default:
        AIM_DIE("Config: No handling for protocol %d", proto);
    }

    controller->config.listen = listen;
    controller->config.cxn_priority = prio;
    controller->config.local = local;
    controller->config.version = OFCONNECTIONMANAGER_CONFIG_OF_VERSION;

    if (!local) {
        controller->config.periodic_echo_ms = staged_config.keepalive_period_ms;
        controller->config.reset_echo_count = 3;
    } else {
        controller->config.periodic_echo_ms = 0;
        controller->config.reset_echo_count = 0;
    }

    return INDIGO_ERROR_NONE;
}