Skip to content

Commit

Permalink
Fix internal JSON large number handling (#190)
Browse files Browse the repository at this point in the history
* Fixes nasa/fprime#3174 by fixing interanl and JSON large number handling

* sp 1

* sp 2

* Reworking SaferParser to use full lexing

* Fixing character class

* sp

* Review recommendations
  • Loading branch information
LeStarch authored Feb 12, 2025
1 parent 87baace commit 976b098
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 162 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ANamespace
Anps
argcomplete
argdesc
ARGN
argname
argtype
autoapi
Expand Down
4 changes: 2 additions & 2 deletions src/fprime_gds/common/data_types/cmd_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ def process_args(self, input_values):
args = []
for val, arg_tuple in zip(input_values, self.template.arguments):
try:
_, _, arg_type = arg_tuple
arg_name, _, arg_type = arg_tuple
arg_value = arg_type()
self.convert_arg_value(val, arg_value)
args.append(arg_value)
errors.append("")
except Exception as exc:
errors.append(str(exc))
errors.append(f"{arg_name}[{arg_type.__name__}]: {exc}")
return args, errors

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion src/fprime_gds/flask/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class CommandArgumentsInvalidException(werkzeug.exceptions.BadRequest):
"""Command arguments failed to validate properly"""

def __init__(self, errors):
super().__init__("Failed to validate all arguments")
super().__init__(f"Failed to validate all arguments: {', '.join(errors)}")
self.args = errors


Expand Down
70 changes: 50 additions & 20 deletions src/fprime_gds/flask/static/addons/commanding/arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function command_argument_assignment_helper(argument, squashed_argument_v
command_argument_array_serializable_assignment_helper(argument, squashed_argument_value);
} else {
let is_not_string = typeof(argument.type.MAX_LENGTH) === "undefined";
argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value;
argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value.toString();
}
}

Expand Down Expand Up @@ -119,7 +119,21 @@ export function squashify_argument(argument) {
let field = argument.type.MEMBER_LIST[i][0];
value[field] = squashify_argument(argument.value[field]);
}
} else if (["U64Type", "U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) != -1) {
} else if (["U64Type"].indexOf(argument.type.name) !== -1) {
if (argument.value.startsWith("0x")) {
// Hexadecimal
value = BigInt(argument.value, 16);
} else if (argument.value.startsWith("0b")) {
// Binary
value = BigInt(argument.value.slice(2), 2);
} else if (argument.value.startsWith("0o")) {
// Octal
value = BigInt(argument.value.slice(2), 8);
} else {
// Decimal
value = BigInt(argument.value, 10);
}
} else if (["U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) !== -1) {
if (argument.value.startsWith("0x")) {
// Hexadecimal
value = parseInt(argument.value, 16);
Expand All @@ -134,10 +148,13 @@ export function squashify_argument(argument) {
value = parseInt(argument.value, 10);
}
}
else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) != -1) {
else if (["I64Type"].indexOf(argument.type.name) !== -1) {
value = BigInt(argument.value, 10);
}
else if (["I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) !== -1) {
value = parseInt(argument.value, 10);
}
else if (["F64Type", "F32Type"].indexOf(argument.type.name) != -1) {
else if (["F64Type", "F32Type"].indexOf(argument.type.name) !== -1) {
value = parseFloat(argument.value);
}
else if (argument.type.name == "BoolType") {
Expand All @@ -160,22 +177,35 @@ export function squashify_argument(argument) {
* @returns: string to display
*/
export function argument_display_string(argument) {
// Base assignment of the value
let string = `${(argument.value == null || argument.value === "") ? FILL_NEEDED: argument.value}`;

if (argument.type.LENGTH) {
string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
} else if (argument.type.MEMBER_LIST) {
let fields = [];
for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
let field = argument.type.MEMBER_LIST[i][0];
fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
let string = FILL_NEEDED;
try {
// Check for array
if (argument.type.LENGTH) {
string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
}
// Serializable
else if (argument.type.MEMBER_LIST) {
let fields = [];
for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
let field = argument.type.MEMBER_LIST[i][0];
fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
}
string = `{${fields.join(", ")}}`
}
// String type
else if (argument.type.MAX_LENGTH) {
let value = (argument.value == null) ? "" : argument.value;
value = value.replace(/"/g, '\\\"');
string = `"${value}"`
}
// Unassigned values
else if (argument.value == null || argument.value === "") {
string = FILL_NEEDED;
} else {
string = squashify_argument(argument);
}
string = `{${fields.join(", ")}}`
} else if (argument.type.MAX_LENGTH) {
let value = (argument.value == null) ? "" : argument.value;
value = value.replace(/"/g, '\\\"');
string = `"${value}"`
} catch (e) {
string = FILL_NEEDED;
}
return string;
}
Expand Down Expand Up @@ -291,7 +321,7 @@ Vue.component("command-scalar-argument", {
return ["text", "0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|[1-9]\\d*|0", ""];
}
else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(this.argument.type.name) != -1) {
return ["number", null, "1"];
return ["text", "-?[1-9]\\d*|0", ""];
}
else if (["F64Type", "F32Type"].indexOf(this.argument.type.name) != -1) {
return ["number", null, "any"];
Expand Down
25 changes: 3 additions & 22 deletions src/fprime_gds/flask/static/addons/commanding/command-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {command_argument_assignment_helper} from "./arguments.js";
import {listExistsAndItemNameNotInList, timeToString} from "../../js/vue-support/utils.js";
import {command_history_template} from "./command-history-template.js";
import {command_display_string} from "./command-string.js";

import { SaferParser } from "../../js/json.js";
SaferParser.register();

/**
* command-history:
Expand Down Expand Up @@ -103,27 +104,7 @@ Vue.component("command-history", {
// Can only set command if it is a child of a command input
if (this.$parent.selectCmd) {
// command-input expects an array of strings as arguments
this.$parent.selectCmd(cmd.full_name, this.preprocess_args(cmd.args));
}
},
/**
* Process the arguments for a command. If the argument is (or contains) a number, it
* is converted to a string. Other types that should be pre-processed can be added here.
*
* @param {*} args
* @returns args processed for command input (numbers converted to strings)
*/
preprocess_args(args) {
if (Array.isArray(args)) {
return args.map(el => this.preprocess_args(el));
} else if (typeof args === 'object' && args !== null) {
return Object.fromEntries(
Object.entries(args).map(([key, value]) => [key, this.preprocess_args(value)])
);
} else if (typeof args === 'number') {
return args.toString();
} else {
return args;
this.$parent.selectCmd(cmd.full_name, cmd.args);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from "../../addons/commanding/arguments.js";
import {_settings} from "../../js/settings.js";
import {command_input_template} from "./command-input-template.js";
import { SaferParser } from "../../js/json.js";
SaferParser.register();

/**
* This helper will help assign command and values in a safe manner by searching the command store, finding a reference,
Expand All @@ -38,6 +40,7 @@ function command_assignment_helper(desired_command_name, desired_command_args, p
return null;
}
let selected = _datastore.commands[command_name];

// Set arguments here
for (let i = 0; i < selected.args.length; i++) {
let assign_value = (desired_command_args.length > i)? desired_command_args[i] : null;
Expand Down Expand Up @@ -147,6 +150,7 @@ Vue.component("command-input", {
* command reaches the ground system.
*/
sendCommand() {

// Validate the command before sending anything
if (!this.validate()) {
return;
Expand All @@ -158,8 +162,9 @@ Vue.component("command-input", {
let _self = this;
_self.active = true;
let command = this.selected;
let squashed_args = command.args.map(serialize_arg);
this.loader.load("/commands/" + command.full_name, "PUT",
{"key":0xfeedcafe, "arguments": command.args.map(serialize_arg)})
{"key":0xfeedcafe, "arguments": squashed_args})
.then(function() {
_self.active = false;
// Clear errors, as there is not a problem further
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
*
* Contains the templates used to render the command string input box.
*/
export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...]";
export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...] " +
"where ARGN is a decimal number, quoted string, or an enumerated constant";

export let command_string_template = `
<div class="fp-flex-repeater">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
command_string_template
} from "./command-string-template.js";
import {argument_display_string, FILL_NEEDED} from "./arguments.js"
import {SaferParser} from "../../js/json.js";
SaferParser.register();

let STRING_PREPROCESSOR = new RegExp(`(?:"((?:[^\"]|\\\")*)")|([a-zA-Z_][a-zA-Z_0-9.]*)|(${FILL_NEEDED})`, "g");

Expand Down Expand Up @@ -61,7 +63,9 @@ Vue.component("command-text", {
} catch (e) {
// JSON parsing exceptions
if (e instanceof SyntaxError) {
this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}`;
this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}.`;
} else {
throw e;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/fprime_gds/flask/static/addons/sequencer/addon.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {_datastore} from "../../js/datastore.js";
import {basicSetup, EditorState, EditorView, linter} from "./third/code-mirror.es.js"
import {sequenceLanguageSupport} from "./autocomplete.js"
import {processResponse} from "./lint.js";
import { SaferParser } from "../../js/json.js";
SaferParser.register();

/**
* Sequence sender function used to uplink the sequence and return a promise of how to handle the server's return.
Expand Down
Loading

0 comments on commit 976b098

Please sign in to comment.