static const char CmdPrec[] = "P";
static const char CmdType[] = "T";
static const char CmdUTC[] = "U";
+static const char CmdExamined[] = "x";
static const char TypeBin[] = "B";
static const char TypeBlob[] = "O";
return (struct KEKSSchemaErr){.code = KEKSSchemaErrNo};
}
+struct keksSchemaValidationState {
+ size_t nonExamined;
+ bool eachInList;
+ bool eachInMap;
+ bool nonExaminedCheck;
+ char _pad[5];
+};
+
static struct KEKSSchemaErr
keksSchemaCmd( // NOLINT(misc-no-recursion)
size_t *taken,
- bool *eachInList,
- bool *eachInMap,
+ struct keksSchemaValidationState *state,
const struct KEKSItems *schema,
struct KEKSItems *data,
size_t idxSchema,
size_t idxData)
{
assert(taken != NULL);
- assert(eachInList != NULL);
- assert(eachInMap != NULL);
+ assert(state != NULL);
assert(schema != NULL);
assert(data != NULL);
size_t origIdxSchema = idxSchema;
v = *taken;
err.msg = "TAKE";
err.code = KEKSSchemaErrNo;
+ } else if (KEKSStrEqual(&(schema->list[idxSchema].atom), CmdExamined)) {
+ state->nonExaminedCheck = true;
+ if (v == SIZE_MAX) {
+ err.code = KEKSSchemaErrNo;
+ } else {
+ if (state->nonExamined == 0) {
+ err.code = KEKSSchemaErrInvalidSchema;
+ err.msg = "already zero toExamine";
+ } else {
+ state->nonExamined--;
+ err.code = KEKSSchemaErrNo;
+ }
+ }
} else if (KEKSStrEqual(&(schema->list[idxSchema].atom), CmdEq)) {
err.msg = "EQ";
if (v == SIZE_MAX) {
}
} else if (KEKSStrEqual(&(schema->list[idxSchema].atom), CmdEach)) {
err.msg = "EACH";
- (*eachInList) = false;
- (*eachInMap) = false;
+ state->eachInList = false;
+ state->eachInMap = false;
if (v == SIZE_MAX) {
err.code = KEKSSchemaErrNo;
return err;
}
switch (data->list[v].atom.typ) {
case KEKSItemList:
- (*eachInList) = true;
+ state->eachInList = true;
break;
case KEKSItemMap:
- (*eachInMap) = true;
+ state->eachInMap = true;
break;
case KEKSItemInvalid:
case KEKSItemEOC:
(*taken) = SIZE_MAX;
} else {
(*taken) = data->list[v].atom.v.list.head;
- if (*eachInMap) {
+ if (state->eachInMap) {
(*taken) = data->list[(*taken)].next;
}
}
if (err.code != KEKSSchemaErrNo) {
return err;
}
- if (*eachInList) {
+ if (state->eachInList) {
v = data->list[v].next;
if (v != 0) {
goto Eached;
}
}
- if (*eachInMap) {
+ if (state->eachInMap) {
v = data->list[v].next; // key
if (v != 0) {
v = data->list[v].next; // value
}
}
}
- (*eachInList) = false;
- (*eachInMap) = false;
+ state->eachInList = false;
+ state->eachInMap = false;
return err;
}
}
idxSchema = schema->list[idxSchema].atom.v.list.head;
err.offSchema = schema->offsets[idxSchema];
+ struct keksSchemaValidationState state = {
+ .nonExamined = 0,
+ .eachInList = false,
+ .eachInMap = false,
+ .nonExaminedCheck = false,
+ };
+ if (data->list[idxData].atom.typ == KEKSItemMap) {
+ state.nonExamined = data->list[idxData].atom.v.list.len;
+ }
size_t taken = idxData;
- bool eachInList = false;
- bool eachInMap = false;
while (idxSchema != 0) {
if (schema->list[idxSchema].atom.typ != KEKSItemList) {
err.code = KEKSSchemaErrInvalidSchema;
err.msg = "non-list cmds";
return err;
}
- struct KEKSSchemaErr errCmd = keksSchemaCmd(
- &taken, &eachInList, &eachInMap, schema, data, idxSchema, idxData);
+ struct KEKSSchemaErr errCmd =
+ keksSchemaCmd(&taken, &state, schema, data, idxSchema, idxData);
if (errCmd.code != KEKSSchemaErrNo) {
return errCmd;
}
idxSchema = schema->list[idxSchema].next;
err.offSchema = schema->offsets[idxSchema];
}
+ if (state.nonExaminedCheck && state.nonExamined > 0) {
+ err.code = KEKSSchemaErrInvalidData;
+ err.msg = "non-examined fields left";
+ return err;
+ }
err.code = KEKSSchemaErrNo;
return err;
}
CmdPrec = "P"
CmdType = "T"
CmdUTC = "U"
+ CmdExamined = "x"
Magic = "schema"
)
SchemaName: schemaName, Msg: "no such schema",
}}
}
+ var nonExamined uint64
+ if m, ok := data.(map[string]any); ok {
+ nonExamined = uint64(len(m))
+ }
+ var nonExaminedCheck bool
var taken string
vs := []any{data}
for cmdIdx, cmd := range cmds {
}}
}
}
+ case CmdExamined:
+ nonExaminedCheck = true
+ if vs == nil {
+ continue
+ }
+ if nonExamined == 0 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "already zero nonExamined",
+ }}
+ }
+ nonExamined--
default:
return &SchemaErr{BaseErr: BaseErr{
SchemaName: schemaName,
}}
}
}
+ if nonExaminedCheck && nonExamined > 0 {
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ Taken: taken,
+ Data: nonExamined,
+ }}
+ }
return nil
}
!EXISTS | ["!E"]
Assure that chosen element does not exist.
+EXAMINED | ["x"]
+ Enable validation of excess fields in the MAP, decrement the number
+ of non-examined fields. Initially, if we are checking MAP, then
+ non-examined fields number equals to elements quantity. Each
+ examined command decreases that number. When we are finished
+ executing commands for the given schema, check if number of
+ non-examined fields is zero.
+
EACH | ["*"]
Execute the next command against every element of the chosen
(if it exists) list, or every value of the map.
{"our": [
["T", "M"],
[".", "a"],
+ ["x"],
["E"],
["T", "S"],
[">", 0],
[".", "v"],
+ ["x"],
["E"],
["T", "B", "S"],
[".", "fpr"],
+ ["x"],
["E"],
["T", "B"],
[">", 31],
["<", 33],
[".", "comment"],
+ ["x"],
["T", "S"],
]}
"len=n" checks the exact length of bin/str/list/map, or integer's value.
+len=~ can be set only in "." map field and it allows non-examined,
+excess elements to be present in it. Otherwise only the specified ones
+in the schema are allowed.
+
"=v" checks that given bin/str/hexlet/magic has specified binary value.
"prec=p" issues PREC command, but instead of specifying the raw
########################################################################
cat >schema.tcl <<EOF
-e {{field bar {int}}}
+e {
+ {field . {map} len=~}
+ {field bar {int}}
+}
EOF
$root/schema.tcl schema.tcl | xxd -r -p >schema.keks
$root/keks.tcl >data.keks.hex <<EOF
-MAP {foo NIL bar {INT 0}}
+MAP {bar {INT 0}}
EOF
xxd -r -p <data.keks.hex >data.keks
test_expect_success "map exists" "$SCHEMA_VALIDATE schema.keks e <data.keks"
test_expect_success "map !exists" "! $SCHEMA_VALIDATE schema.keks e <data.keks"
cat >schema.tcl <<EOF
-e {{field bar {int} optional}}
+e {
+ {field . {map} len=~}
+ {field bar {int} optional}
+}
EOF
$root/schema.tcl schema.tcl | xxd -r -p >schema.keks
$root/keks.tcl >data.keks.hex <<EOF
test_expect_success "map optional bad type" "! $SCHEMA_VALIDATE schema.keks e <data.keks"
cat >schema.tcl <<EOF
-e {{field bar {} !exists}}
+e {
+ {field . {map} len=~}
+ {field bar {} !exists}
+}
EOF
$root/schema.tcl schema.tcl | xxd -r -p >schema.keks
$root/keks.tcl >data.keks.hex <<EOF
xxd -r -p <data.keks.hex >data.keks
test_expect_success "map !exists" "$SCHEMA_VALIDATE schema.keks e <data.keks"
+cat >schema.tcl <<EOF
+e {
+ {field . {map}}
+ {field bar {int}}
+ {field baz {int} optional}
+}
+EOF
+$root/schema.tcl schema.tcl | xxd -r -p >schema.keks
+$root/keks.tcl >data.keks.hex <<EOF
+MAP {foo NIL bar {INT 0}}
+EOF
+xxd -r -p <data.keks.hex >data.keks
+test_expect_success "map !len=~ without optional" "
+ ! $SCHEMA_VALIDATE schema.keks e <data.keks"
+
+$root/keks.tcl >data.keks.hex <<EOF
+MAP {foo NIL bar {INT 0} baz {INT 0}}
+EOF
+xxd -r -p <data.keks.hex >data.keks
+test_expect_success "map !len=~ with optional" "
+ ! $SCHEMA_VALIDATE schema.keks e <data.keks"
+
+$root/keks.tcl >data.keks.hex <<EOF
+MAP {bar {INT 0} baz {INT 0}}
+EOF
+xxd -r -p <data.keks.hex >data.keks
+test_expect_success "map !len=~" "$SCHEMA_VALIDATE schema.keks e <data.keks"
+
########################################################################
cat >schema.tcl <<EOF
test_expect_success "hexlet !=" "! $SCHEMA_VALIDATE schema.keks e <data.keks"
cat >schema.tcl <<EOF
-e {{field foo {magic} =world}}
+e {
+ {field . {map}}
+ {field foo {magic} =world}
+}
EOF
$root/schema.tcl schema.tcl | xxd -r -p >schema.keks
$root/keks.tcl >data.keks.hex <<EOF
ai {{field . {str} >0}}
fpr {{field . {bin} len=32}}
our {
+ {field . {map}}
{field a {with ai}}
{field v {bin str}}
{field fpr {with fpr}}
proc SCHEMA {s} {subst {LIST {{STR S} {STR $s}}}}
proc PREC {p} {subst {LIST {{STR P} {INT $p}}}}
proc UTC {} {return {LIST {{STR U}}}}
+proc EXAMINED {} {return {LIST {{STR x}}}}
proc TAKE {k} {
if {$k == "."} {
set v [list NIL]
} elseif {[string first ":" $k] == 0} {
set v [list STR [string range $k 1 end]]
- } elseif {[string is digit $k]} {
+ } elseif {[string is integer $k]} {
set v [list INT $k]
} else {
set v [list STR $k]
set precArgs [dict create s 0 ms 3 us 6 ns 9 ps 12 fs 15]
proc field {k types args} {
- upvar _cmds _cmds buf buf
+ upvar _cmds _cmds fieldsNum fieldsNum examinable examinable
+ if {($fieldsNum == 0 && $k != ".") || ($fieldsNum != 0 && $k == ".")} {
+ error {first field must be "."}
+ }
+ incr fieldsNum
if {$k != "."} {
lappend _cmds [TAKE $k]
+ if {$examinable} {
+ lappend _cmds [EXAMINED]
+ }
if {[lsearch -exact $args !exists] != -1} {
lappend _cmds [!EXISTS]
} elseif {[lsearch -exact $args optional] == -1} {
lappend _cmds [TAKE $k]
}
set i [lsearch -glob $args "len=*"]
- if {$i != -1} {
+ if {$i == -1} {
+ if {$k == "." && [lsearch -exact $types map] != -1} {
+ set examinable true
+ }
+ } else {
set n [string range [lindex $args $i] 4 end]
- lappend _cmds [GT [expr {$n - 1}]]
- lappend _cmds [LT [expr {$n + 1}]]
+ if {$n != "~"} {
+ lappend _cmds [GT [expr {$n - 1}]]
+ lappend _cmds [LT [expr {$n + 1}]]
+ }
}
set i [lsearch -glob $args ">*"]
if {$i != -1} {
process $inc
continue
}
- foreach cmd $cmds {eval $cmd}
+ set fieldsNum 0
+ set examinable false
+ foreach cmd $cmds {
+ eval $cmd
+ }
lappend _pairs $name [list LIST $_cmds]
}
}
}
kem {
- {field . {map}}
+ {field . {map} len=~}
{field a {str} >0}
{field cek {bin} >0}
}
prv {
- {field . {map}}
+ {field . {map} len=~}
{field a {str} >0} {# algorithm identifier}
{field v {bin}}
{field pub-id {with fpr} optional}
expiration {{field . {list} {of exp-tai} len=2}}
pub-sig-tbs {
- {field . {map}}
+ {field . {map} len=~}
{field sid {with fpr}}
{field cid {hexlet}}
{field exp {with expiration}}
}
sig {
- {field . {map}}
+ {field . {map} len=~}
{field tbs {with pub-sig-tbs}}
{field sign {with av}}
}
signed {
{field . {map}}
{field tbs {with tbs}}
- {# field data is optional, arbitrary type}
+ {field data {} optional}
{field pubs {list} {of type map} >0 optional}
{field sigs {list} {of sig} >0 optional}
}
}
sig-tbs {
- {field . {map}}
+ {field . {map} len=~}
{field sid {with fpr}}
{field nonce {bin} >0 optional} {# random bytes}
{field when {tai} utc prec=ms optional}