ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
const struct ovsdb_base_type *base)
{
+ if (base->enum_
+ && ovsdb_datum_find_key(base->enum_, atom, base->type) == UINT_MAX) {
+ struct ovsdb_error *error;
+ struct ds actual = DS_EMPTY_INITIALIZER;
+ struct ds valid = DS_EMPTY_INITIALIZER;
+
+ ovsdb_atom_to_string(atom, base->type, &actual);
+ ovsdb_datum_to_string(base->enum_,
+ ovsdb_base_type_get_enum_type(base->type),
+ &valid);
+ error = ovsdb_error("constraint violation",
+ "%s is not one of the allowed values (%s)",
+ ds_cstr(&actual), ds_cstr(&valid));
+ ds_destroy(&actual);
+ ds_destroy(&valid);
+
+ return error;
+ }
+
switch (base->type) {
case OVSDB_TYPE_VOID:
NOT_REACHED();
}
struct ovsdb_datum_sort_cbdata {
- const struct ovsdb_type *type;
+ enum ovsdb_atomic_type key_type;
struct ovsdb_datum *datum;
};
return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
&cbdata->datum->keys[b],
- cbdata->type->key.type);
+ cbdata->key_type);
}
static void
struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
- if (cbdata->type->value.type != OVSDB_TYPE_VOID) {
+ if (cbdata->datum->values) {
ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
}
}
struct ovsdb_error *
-ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+ovsdb_datum_sort(struct ovsdb_datum *datum, enum ovsdb_atomic_type key_type)
{
if (datum->n < 2) {
return NULL;
struct ovsdb_datum_sort_cbdata cbdata;
size_t i;
- cbdata.type = type;
+ cbdata.key_type = key_type;
cbdata.datum = datum;
sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
&cbdata);
for (i = 0; i < datum->n - 1; i++) {
if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
- type->key.type)) {
- if (ovsdb_type_is_map(type)) {
+ key_type)) {
+ if (datum->values) {
return ovsdb_error(NULL, "map contains duplicate key");
} else {
return ovsdb_error(NULL, "set contains duplicate");
}
}
+void
+ovsdb_datum_sort_assert(struct ovsdb_datum *datum,
+ enum ovsdb_atomic_type key_type)
+{
+ struct ovsdb_error *error = ovsdb_datum_sort(datum, key_type);
+ if (error) {
+ NOT_REACHED();
+ }
+}
+
/* Checks that each of the atoms in 'datum' conforms to the constraints
* specified by its 'type'. Returns an error if a constraint is violated,
* otherwise a null pointer.
datum->n++;
}
- error = ovsdb_datum_sort(datum, type);
+ error = ovsdb_datum_sort(datum, type->key.type);
if (error) {
goto error;
}
goto error;
}
- dberror = ovsdb_datum_sort(datum, type);
+ dberror = ovsdb_datum_sort(datum, type->key.type);
if (dberror) {
ovsdb_error_destroy(dberror);
if (ovsdb_type_is_map(type)) {
if (n != a->n) {
struct ovsdb_error *error;
a->n = n;
- error = ovsdb_datum_sort(a, type);
+ error = ovsdb_datum_sort(a, type->key.type);
assert(!error);
}
}
}
}
if (changed) {
- struct ovsdb_error *error = ovsdb_datum_sort(a, a_type);
- assert(!error);
+ ovsdb_datum_sort_assert(a, a_type->key.type);
}
}
\f
/* Checking and maintaining invariants. */
struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
- const struct ovsdb_type *);
+ enum ovsdb_atomic_type key_type)
+ WARN_UNUSED_RESULT;
+
+void ovsdb_datum_sort_assert(struct ovsdb_datum *,
+ enum ovsdb_atomic_type key_type);
+
struct ovsdb_error *ovsdb_datum_check_constraints(
const struct ovsdb_datum *, const struct ovsdb_type *)
WARN_UNUSED_RESULT;
OP_INTEGER = 1 << JSON_INTEGER, /* 123. */
OP_NONINTEGER = 1 << JSON_REAL, /* 123.456. */
OP_STRING = 1 << JSON_STRING, /* "..." */
+ OP_ANY = (OP_NULL | OP_FALSE | OP_TRUE | OP_OBJECT | OP_ARRAY
+ | OP_INTEGER | OP_NONINTEGER | OP_STRING),
OP_BOOLEAN = OP_FALSE | OP_TRUE,
OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
#include "dynamic-string.h"
#include "json.h"
+#include "ovsdb-data.h"
#include "ovsdb-error.h"
#include "ovsdb-parser.h"
ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
{
base->type = type;
+ base->enum_ = NULL;
switch (base->type) {
case OVSDB_TYPE_VOID:
}
}
+/* Returns the type of the 'enum_' member for an ovsdb_base_type whose 'type'
+ * is 'atomic_type'. */
+const struct ovsdb_type *
+ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type atomic_type)
+{
+ static struct ovsdb_type *types[OVSDB_N_TYPES];
+
+ if (!types[atomic_type]) {
+ struct ovsdb_type *type;
+
+ types[atomic_type] = type = xmalloc(sizeof *type);
+ ovsdb_base_type_init(&type->key, atomic_type);
+ ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
+ type->n_min = 1;
+ type->n_max = UINT_MAX;
+ }
+ return types[atomic_type];
+}
+
void
ovsdb_base_type_clone(struct ovsdb_base_type *dst,
const struct ovsdb_base_type *src)
{
*dst = *src;
+ if (src->enum_) {
+ dst->enum_ = xmalloc(sizeof *dst->enum_);
+ ovsdb_datum_clone(dst->enum_, src->enum_,
+ ovsdb_base_type_get_enum_type(dst->type));
+ }
+
switch (dst->type) {
case OVSDB_TYPE_VOID:
case OVSDB_TYPE_INTEGER:
ovsdb_base_type_destroy(struct ovsdb_base_type *base)
{
if (base) {
+ if (base->enum_) {
+ ovsdb_datum_destroy(base->enum_,
+ ovsdb_base_type_get_enum_type(base->type));
+ free(base->enum_);
+ }
+
switch (base->type) {
case OVSDB_TYPE_VOID:
case OVSDB_TYPE_INTEGER:
bool
ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
{
+ if (base->enum_) {
+ return true;
+ }
+
switch (base->type) {
case OVSDB_TYPE_VOID:
NOT_REACHED();
{
struct ovsdb_parser parser;
struct ovsdb_error *error;
- const struct json *type;
+ const struct json *type, *enum_;
if (json->type == JSON_STRING) {
error = ovsdb_atomic_type_from_json(&base->type, json);
}
ovsdb_base_type_init(base, base->type);
- if (base->type == OVSDB_TYPE_INTEGER) {
+
+ enum_ = ovsdb_parser_member(&parser, "enum", OP_ANY | OP_OPTIONAL);
+ if (enum_) {
+ base->enum_ = xmalloc(sizeof *base->enum_);
+ error = ovsdb_datum_from_json(
+ base->enum_, ovsdb_base_type_get_enum_type(base->type),
+ enum_, NULL);
+ if (error) {
+ free(base->enum_);
+ base->enum_ = NULL;
+ }
+ } else if (base->type == OVSDB_TYPE_INTEGER) {
const struct json *min, *max;
min = ovsdb_parser_member(&parser, "minInteger",
json = json_object_create();
json_object_put_string(json, "type",
ovsdb_atomic_type_to_string(base->type));
+
+ if (base->enum_) {
+ const struct ovsdb_type *type;
+
+ type = ovsdb_base_type_get_enum_type(base->type);
+ json_object_put(json, "enum", ovsdb_datum_to_json(base->enum_, type));
+ }
+
switch (base->type) {
case OVSDB_TYPE_VOID:
NOT_REACHED();
struct ovsdb_error *
ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
{
- type->value.type = OVSDB_TYPE_VOID;
+ ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
type->n_min = 1;
type->n_max = 1;
struct ovsdb_base_type {
enum ovsdb_atomic_type type;
+
+ /* If nonnull, a datum with keys of type 'type' that expresses all the
+ * valid values for this base_type. */
+ struct ovsdb_datum *enum_;
+
union {
struct ovsdb_integer_constraints {
int64_t min; /* minInteger or INT64_MIN. */
bool ovsdb_base_type_is_valid(const struct ovsdb_base_type *);
bool ovsdb_base_type_has_constraints(const struct ovsdb_base_type *);
void ovsdb_base_type_clear_constraints(struct ovsdb_base_type *);
+const struct ovsdb_type *ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type);
struct ovsdb_error *ovsdb_base_type_from_json(struct ovsdb_base_type *,
const struct json *)
<atomic-type> or a JSON object with the following members:
"type": <atomic-type> required
+ "enum": <value> optional
"minInteger": <integer> optional, integers only
"maxInteger": <integer> optional, integers only
"minReal": <real> optional, reals only
An <atomic-type> by itself is equivalent to a JSON object with a
single member "type" whose value is the <atomic-type>.
+ "enum" may be specified as a <value> whose type is a set of one
+ or more values specified for the member "type". If "enum" is
+ specified, then the valid values of the <base-type> are limited to
+ those in the <value>.
+
+ "enum" is mutually exclusive with the following constraints.
+
If "type" is "integer", then "minInteger" or "maxInteger" or both
may also be specified, restricting the valid integer range. If
both are specified, then the maxInteger must be greater than or
}
}
- error = ovsdb_datum_sort(dst, dst_type);
+ error = ovsdb_datum_sort(dst, dst_type->key.type);
if (error) {
ovsdb_error_destroy(error);
return ovsdb_error("constraint violation",
def getMember(json, name, validTypes, description, default=None):
if name in json:
member = json[name]
- if type(member) not in validTypes:
+ if len(validTypes) and type(member) not in validTypes:
raise Error("%s: type mismatch for '%s' member"
% (description, name))
return member
dst += c
return dst
+class UUID:
+ x = "[0-9a-fA-f]"
+ uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
+ % (x, x, x, x, x, x))
+
+ def __init__(self, value):
+ self.value = value
+
+ @staticmethod
+ def fromString(s):
+ if not uuidRE.match(s):
+ raise Error("%s is not a valid UUID" % s)
+ return UUID(s)
+
+ @staticmethod
+ def fromJson(json):
+ if UUID.isValidJson(json):
+ return UUID(json[1])
+ else:
+ raise Error("%s is not valid JSON for a UUID" % json)
+
+ @staticmethod
+ def isValidJson(json):
+ return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
+
+ def toJson(self):
+ return ["uuid", self.value]
+
+ def cInitUUID(self, var):
+ m = re.match(self.value)
+ return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
+ "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
+ "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
+ "%s.parts[3] = 0x%s;" % (var, m.group(6))]
+
+class Atom:
+ def __init__(self, type, value):
+ self.type = type
+ self.value = value
+
+ @staticmethod
+ def fromJson(type_, json):
+ if ((type_ == 'integer' and type(json) in [int, long])
+ or (type_ == 'real' and type(json) in [int, long, float])
+ or (type_ == 'boolean' and json in [True, False])
+ or (type_ == 'string' and type(json) in [str, unicode])):
+ return Atom(type_, json)
+ elif type_ == 'uuid':
+ return UUID.fromJson(json)
+ else:
+ raise Error("%s is not valid JSON for type %s" % (json, type_))
+
+ def toJson(self):
+ if self.type == 'uuid':
+ return self.value.toString()
+ else:
+ return self.value
+
+ def cInitAtom(self, var):
+ if self.type == 'integer':
+ return ['%s.integer = %d;' % (var, self.value)]
+ elif self.type == 'real':
+ return ['%s.real = %.15g;' % (var, self.value)]
+ elif self.type == 'boolean':
+ if self.value:
+ return ['%s.boolean = true;']
+ else:
+ return ['%s.boolean = false;']
+ elif self.type == 'string':
+ return ['%s.string = xstrdup("%s");'
+ % (var, escapeCString(self.value))]
+ elif self.type == 'uuid':
+ return self.value.cInitUUID(var)
+
class BaseType:
def __init__(self, type,
+ enum=None,
refTable=None,
minInteger=None, maxInteger=None,
minReal=None, maxReal=None,
minLength=None, maxLength=None):
self.type = type
+ self.enum = enum
self.refTable = refTable
self.minInteger = minInteger
self.maxInteger = maxInteger
return BaseType(json)
else:
atomicType = mustGetMember(json, 'type', [unicode], description)
+ enum = getMember(json, 'enum', [], description)
+ if enum:
+ enumType = Type(atomicType, None, 0, 'unlimited')
+ enum = Datum.fromJson(enumType, enum)
refTable = getMember(json, 'refTable', [unicode], description)
minInteger = getMember(json, 'minInteger', [int, long], description)
maxInteger = getMember(json, 'maxInteger', [int, long], description)
maxReal = getMember(json, 'maxReal', [int, long, float], description)
minLength = getMember(json, 'minLength', [int], description)
maxLength = getMember(json, 'minLength', [int], description)
- return BaseType(atomicType, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
+ return BaseType(atomicType, enum, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
def toEnglish(self):
if self.type == 'uuid' and self.refTable:
stmts = []
stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
var, self.type.upper()),)
+ if self.enum:
+ stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
+ % (var, var))
+ stmts += self.enum.cInitDatum("%s.enum_" % var)
if self.type == 'integer':
if self.minInteger != None:
stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
initMax = "%s%s.n_max = %s;" % (indent, var, max)
return "\n".join((initKey, initValue, initMin, initMax))
+class Datum:
+ def __init__(self, type, values):
+ self.type = type
+ self.values = values
+
+ @staticmethod
+ def fromJson(type_, json):
+ if not type_.value:
+ if len(json) == 2 and json[0] == "set":
+ values = []
+ for atomJson in json[1]:
+ values += [Atom.fromJson(type_.key, atomJson)]
+ else:
+ values = [Atom.fromJson(type_.key, json)]
+ else:
+ if len(json) != 2 or json[0] != "map":
+ raise Error("%s is not valid JSON for a map" % json)
+ values = []
+ for pairJson in json[1]:
+ values += [(Atom.fromJson(type_.key, pairJson[0]),
+ Atom.fromJson(type_.value, pairJson[1]))]
+ return Datum(type_, values)
+
+ def cInitDatum(self, var):
+ if len(self.values) == 0:
+ return ["ovsdb_datum_init_empty(%s);" % var]
+
+ s = ["%s->n = %d;" % (var, len(self.values))]
+ s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
+ % (var, len(self.values), var)]
+
+ for i in range(len(self.values)):
+ key = self.values[i]
+ if self.type.value:
+ key = key[0]
+ s += key.cInitAtom("%s->keys[%d]" % (var, i))
+
+ if self.type.value:
+ s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
+ % (var, len(self.values), var)]
+ for i in range(len(self.values)):
+ value = self.values[i][1]
+ s += key.cInitAtom("%s->values[%d]" % (var, i))
+ else:
+ s += ["%s->values = NULL;" % var]
+
+ if len(self.values) > 1:
+ s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
+ % (var, self.type.key.upper())]
+
+ return s
+
def parseSchema(filename):
return DbSchema.fromJson(json.load(open(filename, "r")))
AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])],
[1], [], [ovs-vsctl: constraint violation: 4096 is not in the valid range 0 to 4095 (inclusive)
], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])],
+ [1], [], [[ovs-vsctl: constraint violation: xyz is not one of the allowed values ([in-band, out-of-band])
+]], [OVS_VSCTL_CLEANUP])
AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])],
[1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
], [OVS_VSCTL_CLEANUP])
[parse-atom-strings '[["uuid"]]' '1234-5678'],
["1234-5678" is not a valid UUID])
\f
-AT_BANNER([OVSDB -- atoms with constraints])
+AT_BANNER([OVSDB -- atoms with enum constraints])
+
+OVSDB_CHECK_POSITIVE([integer atom enum],
+ [[parse-atoms '[{"type": "integer", "enum": ["set", [1, 6, 8, 10]]}]' \
+ '[0]' \
+ '[1]' \
+ '[2]' \
+ '[3]' \
+ '[6]' \
+ '[7]' \
+ '[8]' \
+ '[9]' \
+ '[10]' \
+ '[11]']],
+ [[constraint violation: 0 is not one of the allowed values ([1, 6, 8, 10])
+1
+constraint violation: 2 is not one of the allowed values ([1, 6, 8, 10])
+constraint violation: 3 is not one of the allowed values ([1, 6, 8, 10])
+6
+constraint violation: 7 is not one of the allowed values ([1, 6, 8, 10])
+8
+constraint violation: 9 is not one of the allowed values ([1, 6, 8, 10])
+10
+constraint violation: 11 is not one of the allowed values ([1, 6, 8, 10])]])
+
+OVSDB_CHECK_POSITIVE([real atom enum],
+ [[parse-atoms '[{"type": "real", "enum": ["set", [-1.5, 1.5]]}]' \
+ '[-2]' \
+ '[-1]' \
+ '[-1.5]' \
+ '[0]' \
+ '[1]' \
+ '[1.5]' \
+ '[2]']],
+ [[constraint violation: -2 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: -1 is not one of the allowed values ([-1.5, 1.5])
+-1.5
+constraint violation: 0 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: 1 is not one of the allowed values ([-1.5, 1.5])
+1.5
+constraint violation: 2 is not one of the allowed values ([-1.5, 1.5])]])
+
+OVSDB_CHECK_POSITIVE([boolean atom enum],
+ [[parse-atoms '[{"type": "boolean", "enum": false}]' \
+ '[false]' \
+ '[true]']],
+ [[false
+constraint violation: true is not one of the allowed values ([false])]])
+
+OVSDB_CHECK_POSITIVE([string atom enum],
+ [[parse-atoms '[{"type": "string", "enum": ["set", ["abc", "def"]]}]' \
+ '[""]' \
+ '["ab"]' \
+ '["abc"]' \
+ '["def"]' \
+ '["defg"]' \
+ '["DEF"]']],
+ [[constraint violation: "" is not one of the allowed values ([abc, def])
+constraint violation: ab is not one of the allowed values ([abc, def])
+"abc"
+"def"
+constraint violation: defg is not one of the allowed values ([abc, def])
+constraint violation: DEF is not one of the allowed values ([abc, def])]])
+
+OVSDB_CHECK_POSITIVE([uuid atom enum],
+ [[parse-atoms '[{"type": "uuid", "enum": ["set", [["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"], ["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]]]}]' \
+ '["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"]' \
+ '["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]' \
+ '["uuid", "dab2a6b2-6094-4f43-a7ef-4c0f0608f176"]']],
+ [[["uuid","6d53a6dd-2da7-4924-9927-97f613812382"]
+["uuid","52cbc842-137a-4db5-804f-9f34106a0ba3"]
+constraint violation: dab2a6b2-6094-4f43-a7ef-4c0f0608f176 is not one of the allowed values ([52cbc842-137a-4db5-804f-9f34106a0ba3, 6d53a6dd-2da7-4924-9927-97f613812382])]])
+\f
+AT_BANNER([OVSDB -- atoms with other constraints])
OVSDB_CHECK_POSITIVE([integers >= 5],
[[parse-atoms '[{"type": "integer", "minInteger": 5}]' \
AT_BANNER([OVSDB -- base types])
+OVSDB_CHECK_POSITIVE([integer enum],
+ [[parse-base-type '{"type": "integer", "enum": ["set", [-1, 4, 5]]}' ]],
+ [[{"enum":["set",[-1,4,5]],"type":"integer"}]])
OVSDB_CHECK_POSITIVE([integer >= 5],
[[parse-base-type '{"type": "integer", "minInteger": 5}' ]],
[{"minInteger":5,"type":"integer"}])
[[parse-base-type '{"type": "integer", "minInteger": 5, "maxInteger": 3}']],
[minInteger exceeds maxInteger])
+OVSDB_CHECK_POSITIVE([real enum],
+ [[parse-base-type '{"type": "real", "enum": ["set", [1.5, 0, 2.75]]}' ]],
+ [[{"enum":["set",[0,1.5,2.75]],"type":"real"}]])
OVSDB_CHECK_POSITIVE([real >= -1.5],
[[parse-base-type '{"type": "real", "minReal": -1.5}']],
[{"minReal":-1.5,"type":"real"}])
OVSDB_CHECK_POSITIVE([boolean],
[[parse-base-type '[{"type": "boolean"}]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([boolean enum],
+ [[parse-base-type '{"type": "boolean", "enum": true}' ]],
+ [[{"enum":true,"type":"boolean"}]])
+OVSDB_CHECK_POSITIVE([string enum],
+ [[parse-base-type '{"type": "string", "enum": ["set", ["def", "abc"]]}']],
+ [[{"enum":["set",["abc","def"]],"type":"string"}]])
OVSDB_CHECK_POSITIVE([string minLength],
[[parse-base-type '{"type": "string", "minLength": 1}']],
[{"minLength":1,"type":"string"}])
[[parse-base-type '{"type": "string", "maxLength": -1}']],
[maxLength out of valid range 0 to 4294967295])
+OVSDB_CHECK_POSITIVE([uuid enum],
+ [[parse-base-type '{"type": "uuid", "enum": ["uuid", "36bf19c0-ad9d-4232-bb85-b3d73dfe2123"]}' ]],
+ [[{"enum":["uuid","36bf19c0-ad9d-4232-bb85-b3d73dfe2123"],"type":"uuid"}]])
OVSDB_CHECK_POSITIVE([uuid refTable],
[[parse-base-type '{"type": "uuid", "refTable": "myTable"}' ]],
[{"refTable":"myTable","type":"uuid"}])
"type": {"key": "integer", "min": 0, "max": 1}},
"fail_mode": {
"comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
- "type": {"key": {"type": "string"},
+ "type": {"key": {"type": "string",
+ "enum": ["set", ["standalone", "secure"]]},
"min": 0, "max": 1}},
"discover_accept_regex": {
"comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated. If not specified, the default is implementation-specific.",
"type": {"key": "boolean", "min": 0, "max": 1}},
"connection_mode": {
"comment": "Either \"in-band\" or \"out-of-band\". If not specified, the default is implementation-specific.",
- "type": {"key": {"type": "string"},
+ "type": {"key": {"type": "string",
+ "enum": ["set", ["in-band", "out-of-band"]]},
"min": 0, "max": 1}},
"local_ip": {
"comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",