fota_testing/lib/testing_code.dart

1764 lines
46 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:testing/model/json_element.dart';
import 'package:testing/src/esp_log.dart';
import 'package:testing/src/hexjson.dart';
import 'package:testing/src/hexvar.dart';
import 'package:testing/src/instr_desc.dart';
import 'package:testing/src/utility.dart';
enum FlashStatus { idle, start, completed, aborted }
enum DataSt { DS_DT, DS_VAR }
const int CC_VARIABLE_NOT_FOUND = -53;
const int CC_OK = 0;
const int CC_ERROR = -1;
const int CC_LINE_COMMENT = 1;
const int CC_RPTEND = -50;
const int CC_INVALID_FUNCTION_NAME = -55;
const int CC_UNIMPLEMENTED_FUNCTION = -52;
const int CC_ECU_NO_RESPONSE = -1;
bool tpModeActive = true;
class RptHandle {
String hdl;
int strtPos;
int endPos;
String varName;
RptHandle({
required this.hdl,
required this.strtPos,
required this.endPos,
required this.varName,
});
@override
String toString() =>
'RptHandle(hdl: $hdl, strtPos: $strtPos, endPos: $endPos, varName: $varName)';
}
const int MAX_REPEATS = 20;
final List<RptHandle> rphdls = List.generate(
MAX_REPEATS,
(_) => RptHandle(hdl: "", strtPos: 0, endPos: 0, varName: ""),
);
FlashStatus flashStatus = FlashStatus.idle;
JsonElement? jsroot;
Future<File?> openFsqFile(String fsqFileName) async {
final file = File(fsqFileName);
if (!await file.exists()) {
Logger.log("Failed to open fsq file for reading");
return null;
} else {
Logger.log("File opened");
return file;
}
}
Future<List<String?>> waitForSimulatedECUResponse() async {
// Simulate ECU response arriving in < 2 seconds
Logger.log('sending ecu response....');
await Future.delayed(const Duration(milliseconds: 200));
return ['0x7F', 'XX', '0x78'];
}
Future<bool> sendTesterPresent() async {
try {
// If ECU does NOT respond within 2 seconds → throw timeout
final resp = await waitForSimulatedECUResponse().timeout(
const Duration(seconds: 2),
);
Logger.log("ECU Response (Tester Present): $resp");
if (resp.length >= 2 && resp[0] == '0x7E' && resp[1] == '0x00') {
return true; // VALID
}
return false; // Wrong response
} catch (e) {
Logger.log("TesterPresent TIMEOUT — ECU did not respond in 2 seconds");
return false; // TIMEOUT = FAILURE
}
}
class TesterPresentController {
bool active = false;
bool abortFSQ = false;
Future<void> start({
required Future<bool> Function() sendTesterPresent,
Duration timeout = const Duration(seconds: 2),
}) async {
if (active) return;
active = true;
abortFSQ = false;
tpModeActive = true;
while (active) {
Logger.log("TesterPresent: Sending 3E 00");
// Send Tester Present (3E 00)
final ok = await sendTesterPresent();
if (!ok) {
Logger.log("TesterPresent: NO VALID RESPONSE → STOP FOTA");
abortFSQ = true;
active = false;
break;
}
Logger.log("TesterPresent: Valid 7E 00 received, waiting before next TP");
// Wait exactly 2 seconds AFTER receiving OK
await Future.delayed(timeout);
}
}
void stop() {
Logger.log("TesterPresent STOPPED");
tpModeActive = false;
active = false;
}
}
final tpController = TesterPresentController();
Future<bool> waitFor2709Response() async {
Logger.log("Waiting for ECU response for 2709...");
final start = DateTime.now();
while (true) {
// --------- GLOBAL TIMEOUT (3 sec) ---------
if (DateTime.now().difference(start) > const Duration(seconds: 3)) {
Logger.log("2709 TIMEOUT → FOTA CANCEL");
return false;
}
// ======== WAIT FOR ECU RESPONSE ========
final resp = await waitForSimulatedECUResponse();
Logger.log("ECU Response: $resp");
// ======================================================
// CASE 1: ECU SILENT (resp empty)
// ======================================================
if (resp.isEmpty) {
Logger.log("ECU silent → sending 3E 80");
await send3E80();
await Future.delayed(Duration(milliseconds: 300));
continue;
}
// ======================================================
// CASE 2: ECU → 7F XX 78 (RESPONSE PENDING)
// After sending 3E 80, keep checking response.
// ======================================================
if (resp.length >= 3 && resp[0] == '0x7F' && resp[2] == '0x78') {
Logger.log("ECU pending → sending 3E 80");
await Future.delayed(Duration(milliseconds: 300));
await send3E80();
// keep looping to wait for next ECU message
continue;
}
// ======================================================
// CASE 3: POSITIVE RESPONSE
// ======================================================
if (resp.length >= 2 && resp[0] == '0x67' && resp[1] == '0x09') {
Logger.log("ECU accepted 2709");
return true;
}
// ======================================================
// ANY OTHER RESPONSE = FAILURE
// ======================================================
Logger.log("Unexpected ECU response → $resp");
return false;
}
}
Future<void> send3E80() async {
Uint8List data = convertHexStringToBytes("3E80");
Logger.log("Sending: $data");
// CAN send function
// await handleSendToECU(data);
}
Future<int> readLine(RandomAccessFile fp, Uint8List buffer) async {
int bytesRead = 0;
while (true) {
int byte;
try {
byte = await fp.readByte();
} catch (_) {
break; // unexpected error
}
if (byte == -1) {
break; // EOF reached
}
buffer[bytesRead++] = byte;
if (byte == '\n'.codeUnitAt(0)) {
return bytesRead;
}
if (bytesRead >= buffer.length - 1) {
break;
}
}
return bytesRead; // returns 0 if true EOF
}
int trimCRLF(Uint8List buffer, int length) {
while (length > 0) {
final ch = buffer[length - 1];
if (ch == '\r'.codeUnitAt(0) || ch == '\n'.codeUnitAt(0)) {
buffer[length - 1] = 0;
length--;
} else {
break;
}
}
return length;
}
class CmdOpResult {
final Uint8List fn;
final int fnLen;
final Uint8List? data;
final int dataLen;
CmdOpResult({
required this.fn,
required this.fnLen,
required this.data,
required this.dataLen,
});
}
CmdOpResult cmdopsep(Uint8List buffer, int length) {
final colonIndex = buffer.indexOf(':'.codeUnitAt(0));
if (colonIndex == -1) {
// No colon found
return CmdOpResult(
fn: buffer.sublist(0, length),
fnLen: length,
data: null,
dataLen: 0,
);
}
// Split function and data
final fn = buffer.sublist(0, colonIndex);
final data = buffer.sublist(colonIndex + 1, length);
return CmdOpResult(
fn: fn,
fnLen: fn.length,
data: data,
dataLen: data.length,
);
}
class CmdOpSep {
final List<int> fn;
final List<int> data;
CmdOpSep(this.fn, this.data);
}
CmdOpSep cmdOpSep(Uint8List buff, int len) {
int colonIndex = -1;
for (int i = 0; i < len; i++) {
if (buff[i] == ':'.codeUnitAt(0)) {
colonIndex = i;
break;
}
}
if (colonIndex == -1) {
// no colon
return CmdOpSep(buff.sublist(0, len), []);
}
return CmdOpSep(
buff.sublist(0, colonIndex),
buff.sublist(colonIndex + 1, len),
);
}
final Map<String, int> variableValues = {};
void setVariableValue(String varName, int v) {
variableValues[varName] = v;
}
void freeLoopHandle(RptHandle h) {
h.hdl = "";
h.strtPos = 0;
h.endPos = 0;
h.varName = "";
}
Future<int> commandRepeatStart(
RandomAccessFile fp,
String jsonFileName,
Uint8List fdata,
int fdataLen,
) async {
Logger.log("Repeat Start Command");
String content = String.fromCharCodes(fdata.sublist(0, fdataLen));
List<String> parts = content.split(",");
if (parts.length < 4) {
Logger.log("repeatstart parse error: not enough args");
return CC_ERROR;
}
String rptHdl = parts[0].trim(); // handle name
String rptVar = parts[1].trim(); // loop variable name
String varInitVal = parts[2].trim();
String varEndVal = parts[3].trim();
int initVal = int.tryParse(varInitVal) ?? 0;
int? fetched = await getVariableValue(jsonFileName, varEndVal);
int endVal;
if (fetched != null) {
endVal = fetched;
} else {
endVal = convertHexStringToInt(varEndVal);
}
RptHandle? h = getHandle(rptHdl);
if (h == null) {
Logger.log("Failed to get repeat handle");
return CC_ERROR;
}
// Save file start position (ftell)
h.strtPos = await fp.position();
h.hdl = rptHdl;
h.varName = rptVar;
Logger.log("Handle startPos = ${h.strtPos}");
Var? nv = allocateNewVal(rptVar);
if (nv == null) {
Logger.log("RepeatStart: failed to allocate loop variable");
return CC_ERROR;
}
Logger.log("RepeatStart: loop variable '$rptVar' allocated");
Logger.log("INNER LOOP WILL RUN FOR $endVal times");
for (int i = initVal; i <= endVal; i++) {
nv.value = i;
// Reset file position
await fp.setPosition(h.strtPos);
Logger.log("Running iteration $i");
int crawlerRet = await crawler(fp, jsonFileName, h);
if (crawlerRet != CC_OK && crawlerRet != CC_RPTEND) {
removeVariable(rptVar);
return crawlerRet;
}
}
// ---------------------------------------------------------
// Cleanup
removeVariable(rptVar);
await fp.setPosition(h.endPos);
// freeLoopHandle(h)
h.hdl = "";
h.varName = "";
h.strtPos = 0;
h.endPos = 0;
return CC_OK;
}
Future<int> getSectorData(
String jsonFileName,
int index,
int loopCount,
int sizePerPacket,
int upto,
Uint8List buff,
ValueSetter<int> setLen,
) async {
int transmittedSoFar = loopCount * sizePerPacket;
if (transmittedSoFar >= upto) {
return -1;
}
int fetchableSize = 0;
if (transmittedSoFar + sizePerPacket <= upto) {
fetchableSize = sizePerPacket;
} else {
fetchableSize = upto - transmittedSoFar;
}
// JSON Path `SectorData[index].JsonData`
String actJsonSrch = "SectorData[$index].JsonData";
// Read JSON file completely
final file = File(jsonFileName);
if (!file.existsSync()) {
print("Failed to open file");
return -1;
}
final jsonText = await file.readAsString();
final json = jsonDecode(jsonText);
if (!json.containsKey("SectorData")) {
print("SectorData not found!");
return -1;
}
String hexString;
try {
hexString = json["SectorData"][index]["JsonData"];
} catch (e) {
print("JsonData not found!");
return -1;
}
List<int> chars = hexString.codeUnits;
int seekIncr = 1 + (transmittedSoFar * 2);
if (seekIncr >= chars.length) {
return -1;
}
int totalChars = fetchableSize * 2;
for (int i = 0; i < totalChars; i++) {
if (seekIncr + i >= chars.length) break;
buff[i] = chars[seekIncr + i];
}
setLen(totalChars);
return 0;
}
Future<int> sendCANFrame(Uint8List data) async {
// This function must:
// 1. Transmit the CAN frame via UART/Wifi
// 2. Wait for TX ACK from your firmware (Indication)
// 3. Set 'Indication' to 0 or negative
Logger.log("Sending CAN Frame...");
await Future.delayed(const Duration(milliseconds: 50)); // simulate tx
// Simulated indication:
//Indication = 0; // success
//Indication = -1; // fail
if (indication == 0) {
Logger.log("ECU TX SUCCESS (Indication=0)");
return 0;
} else {
Logger.log("ECU TX FAIL (Indication=$indication)");
return -1;
}
}
int indication = 0;
// Dummy handleSend - REPLACE with your actual send function.
Future<int> handleSend(Uint8List data) async {
Logger.log('handleSend (stub) sending ${data.length} bytes: $data');
// return 0 for success, non-zero for failure
// Send to communica, wait for indication.
// if indication = 0 -> restart tpController (reason -> succesfull TX CAN msg)
// if indication < 0 -> couldnt TX CAN MSG, stop TP controller. retry CAN msg
// indication = 0 after retry -> start tpController.
// indication < 0 after retry -> stop FOTA altogether -> returns here with negative 1.
indication = await sendCANFrame(data);
if (indication == 0) {
Logger.log("CAN TX SUCCESS → restarting TP");
tpController.start(sendTesterPresent: sendTesterPresent);
return 0;
}
Logger.log("CAN TX FAILED → stopping TP & retrying once.");
tpController.stop(); // disable TesterPresent so it doesn't interfere
// ------------ STEP 2: Retry --------------
int retryResult = await sendCANFrame(data);
if (retryResult == 0) {
Logger.log("RETRY SUCCESS → restarting TP");
tpController.start(sendTesterPresent: sendTesterPresent);
return 0; // success
}
// ------------ STEP 3: Both attempts failed → FOTA FAIL --------------
Logger.log("RETRY FAILED → FOTA MUST STOP");
tpController.stop();
return -1;
// wait for ECU response from communica within given timeout -> 1 second.
// pass the msg from communica via the modal, and find out ECU response.
// if ECU's response [0] == data[0] + 0x40 -> positive response -> return here with 0.
// if ECU's response [0] == 0x7f && response [2] == 0x78 -> wait response, there is no timeout for ECU response anymore. we'll wait indefinately.
// wait response = keep on sending 0x3e 0x80 (tp without response), as long as we dont get a +ve response or NRC, we keep on sending it, with given timeout of 1.5 seconds.
// if ECU's response is anything else, then, consider it a random response, it is not what we need, ignore the other response
// if ECU's response [0] == 0x7f && response [2] != 0x78 || response [2] != 0x00, then it is an NRC, stop FOTA altogether
// return -ve in case of NRC, or timeout.
// after a successful CAN TX and a +ve response, if there is no CAN TX to or no CAN RX from ECU i.e. no msg from ECU as incoming, or we havent sent any msg, under given timeout (1.5 seconds for now)
// send a tester present -> 0x3e, 0x00. wait for response (1 second) -> 0x7e, 0x00
// if on sending tester present with ack, we get no 0x7e 0x00, it means ECU isnt responding, stop FOTA altogether, and send 0x11 0x02 for ECU soft restart. intentional.
// implement this entire logic using tpcontroller.
return 0;
}
Future<int> commandSendBulkData(
String jsonFileName,
RptHandle? hin,
Uint8List fdata, // bytes carrying "bsc,1,36+bsc+json_sectordata[...]" etc.
) async {
Logger.log("Sending bulk data");
// -----------------------------
// Step 1: Parse fdata arguments
// -----------------------------
final raw = utf8.decode(fdata); // convert bytes to String
Logger.log("RAW sendbulkdata: $raw");
// Split on first two commas; the rest stays as sendcmd (commas may appear inside bracket)
final firstComma = raw.indexOf(',');
if (firstComma == -1) {
Logger.log("sendbulkdata: invalid args (no comma)");
return CC_ERROR;
}
final secondComma = raw.indexOf(',', firstComma + 1);
if (secondComma == -1) {
Logger.log("sendbulkdata: invalid args (only one comma)");
return CC_ERROR;
}
final seqvar = raw.substring(0, firstComma).trim();
final initvalstr = raw.substring(firstComma + 1, secondComma).trim();
String sendcmdstr = raw.substring(secondComma + 1).trim();
// Normalize spaces around brackets and commas (C's strtok is forgiving)
sendcmdstr = sendcmdstr
.replaceAll(" [", "[")
.replaceAll("[ ", "[")
.replaceAll(" ]", "]")
.replaceAll("] ", "]")
.replaceAll(" ,", ",")
.replaceAll(", ", ",")
.trim();
Logger.log(
"Parsed args: seqvar=$seqvar initval=$initvalstr sendcmd=$sendcmdstr",
);
// -----------------------------
// Initialize sequence variable
// -----------------------------
final int init = int.tryParse(initvalstr) ?? 0;
ValuePointer? seqPtr = getValuePointer(seqvar);
if (seqPtr == null) {
// allocate and re-fetch pointer
await Future.microtask(() => allocateNewVal(seqvar));
seqPtr = getValuePointer(seqvar);
if (seqPtr == null) {
Logger.log("sendbulkdata: failed to allocate seq variable '$seqvar'");
return CC_ERROR;
}
}
seqPtr.value = init;
// ---------------------------------------------------
// Step 2: Extract packet size and base command string
// ---------------------------------------------------
final open = sendcmdstr.indexOf('[');
final close = sendcmdstr.lastIndexOf(']');
if (open == -1 || close == -1 || close <= open) {
Logger.log("sendbulkdata: malformed command string, missing brackets");
return CC_ERROR;
}
final baseCmdStr = sendcmdstr
.substring(0, open)
.trim(); // "36+bsc+json_sectordata"
final bracketContentRaw = sendcmdstr
.substring(open + 1, close)
.trim(); // "json_sector_len[i],0FC"
// Clean bracket content and split on first comma only (like strtok)
var inside = bracketContentRaw
.replaceAll(", ", ",")
.replaceAll(" ,", ",")
.trim();
final commaIndex = inside.indexOf(',');
if (commaIndex == -1) {
Logger.log("sendbulkdata: invalid bracket content (no comma)");
return CC_ERROR;
}
final totalLenStr = inside.substring(0, commaIndex).trim();
final pktSizeStr = inside.substring(commaIndex + 1).trim();
final pktSize = convertHexStringToInt(pktSizeStr);
if (pktSize <= 0) {
Logger.log("sendbulkdata: invalid packet size hex '$pktSizeStr'");
return CC_ERROR;
}
Logger.log("Packet size parsed = $pktSize");
// Resolve total buffer length variable
final int? totalBuffLenNullable = await getVariableValue(
jsonFileName,
totalLenStr,
);
if (totalBuffLenNullable == null) {
Logger.log(
"sendbulkdata: Could not resolve total buffer length for $totalLenStr",
);
return CC_ERROR;
}
final int totalBuffLen = totalBuffLenNullable;
Logger.log("Total buffer length = $totalBuffLen");
// ---------------------------------------------------
// Step 3: Allocate buffers once (Dart typed arrays)
// ---------------------------------------------------
final Uint8List cdata = Uint8List(pktSize + 40);
final Uint8List sectorHexChars = Uint8List(
pktSize * 2 + 8,
); // ascii hex chars
int transmittedBuffLen = 0;
int iIter = 0;
int status = CC_OK;
// Pre-split tokens from baseCmdStr by '+'
final tokens = baseCmdStr
.split('+')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
if (tokens.isEmpty) {
Logger.log("sendbulkdata: baseCmdStr tokens empty");
return CC_ERROR;
}
// ---------------------------------------------------
// Step 4: Transmission loop - iterate until all bytes sent
// ---------------------------------------------------
while (transmittedBuffLen < totalBuffLen) {
// Clear cdata
cdata.fillRange(0, cdata.length, 0);
int cdatai = 0;
bool foundSectorData = false;
int sizeOfHexTransmitetdData = 0;
// Parse tokens (like inner strtok loop in C)
for (final tok in tokens) {
if (tok == seqvar) {
// write one byte from sequence variable
final int seqVal = seqPtr.value;
cdata[cdatai++] = seqVal & 0xFF;
} else if (tok.toLowerCase() == 'json_sectordata') {
// Resolve index from hin->varName
if (hin == null || hin.varName.isEmpty) {
Logger.log("sendbulkdata: missing repeat handle or handle.varName");
status = CC_ERROR;
break;
}
final int? idxVal = await getVariableValue(jsonFileName, hin.varName);
if (idxVal == null) {
Logger.log(
"sendbulkdata: Failed to resolve index from ${hin.varName}",
);
status = CC_ERROR;
break;
}
final int sectorIndexForJson = idxVal > 0 ? idxVal - 1 : 0;
// Ask getSectorData to fill sectorHexChars with ASCII hex chars
int lenChars = 0;
final gsdRet = await getSectorData(
jsonFileName,
sectorIndexForJson,
iIter,
pktSize,
totalBuffLen,
sectorHexChars,
(v) => lenChars = v,
);
if (gsdRet != 0) {
Logger.log("sendbulkdata: getSectorData failed");
status = CC_ERROR;
break;
}
// convert ascii hex chars to bytes
final hexString = utf8.decode(sectorHexChars.sublist(0, lenChars));
final Uint8List bytes = convertHexStringToBytes(hexString);
cdata.setRange(cdatai, cdatai + bytes.length, bytes);
cdatai += bytes.length;
sizeOfHexTransmitetdData = bytes.length;
foundSectorData = true;
} else {
// If token is even length and hex-like → treat as hex string
final maybeHex = tok.replaceAll(' ', '');
final isHexCandidate = RegExp(r'^[0-9A-Fa-f]+$').hasMatch(maybeHex);
if ((maybeHex.length & 1) == 0 && isHexCandidate) {
final bytes = convertHexStringToBytes(maybeHex);
cdata.setRange(cdatai, cdatai + bytes.length, bytes);
cdatai += bytes.length;
} else {
Logger.log("sendbulkdata: Unhandled token: '$tok'");
status = CC_ERROR;
break;
}
}
} // end tokens loop
if (status != CC_OK || !foundSectorData) {
Logger.log("sendbulkdata: status error or no sector data found");
status = CC_ERROR;
break;
}
// ---------------------------------------
// Send buffer (call your transport)
// ---------------------------------------
final packet = Uint8List.fromList(cdata.sublist(0, cdatai));
final sendRet = await handleSend(packet);
if (sendRet != 0) {
Logger.log("sendbulkdata: handleSend failed: $sendRet");
status = CC_ERROR;
break;
}
transmittedBuffLen += sizeOfHexTransmitetdData;
Logger.log(
"Sent packet ($cdatai bytes), progress $transmittedBuffLen/$totalBuffLen",
);
// increment sequence & loop counter
seqPtr.value = seqPtr.value + 1;
iIter++;
} // end while
// ---------------------------------------------------
// Step 5: Cleanup (Dart GC will reclaim arrays)
// ---------------------------------------------------
if (status != CC_OK) {
Logger.log("sendbulkdata: finished with status $status");
return CC_ERROR;
}
return CC_OK;
}
// store values like your getValuePointer + allocateNewVal
final Map<String, int> _vars = {};
void setValue(String name, int value) {
_vars[name] = value;
}
int? getValue(String name) => _vars[name];
Future<int> performFn(
RandomAccessFile fp,
Uint8List fn,
int fnLen,
Uint8List fdata,
int fdataLen,
RptHandle? hin,
String jsonFileName,
) async {
// Convert function name
final fnStr = String.fromCharCodes(fn.sublist(0, fnLen)).trim();
Logger.log('[performFn] FN = "$fnStr" (len=$fnLen)');
// Print incoming data
Logger.log(
'[performFn] RAW DATA ($fdataLen bytes): "${String.fromCharCodes(fdata.sublist(0, fdataLen))}"',
);
Logger.log(
'[performFn] HEX BYTES: ${fdata.sublist(0, fdataLen).map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}',
);
if (fdataLen == 0) {
Logger.log("[performFn] WARNING: fdata is EMPTY → nothing to send.");
}
// -------------------------------
// SEND
// -------------------------------
if (fnStr == "send") {
return await commandSend(jsonFileName, fdata.sublist(0, fdataLen));
}
// -------------------------------
// REPEAT START
// -------------------------------
if (fnStr == "repeatstart") {
return await commandRepeatStart(fp, jsonFileName, fdata, fdataLen);
}
// -------------------------------
// REPEAT END
// -------------------------------
if (fnStr == "repeatend") {
Logger.log("Repeat END Command");
if (hin != null) hin.endPos = await fp.position();
return CC_RPTEND;
}
// -------------------------------
// SEND BULK DATA
// -------------------------------
if (fnStr == "sendbulkdata") {
return commandSendBulkData(jsonFileName, hin, fdata.sublist(0, fdataLen));
}
// -------------------------------
// FUNCTION()
// -------------------------------
if (fnStr == "function") {
final raw = String.fromCharCodes(fdata);
final funcName = raw.split("[").first;
final argsStr = raw.contains("[") ? raw.split("[")[1].split("]")[0] : "";
final args = argsStr.split(",");
final algo = args.isNotEmpty ? args[0] : "";
final keyStr = args.length > 1 ? args[1] : "";
int keyLen = int.tryParse(keyStr) ?? 0;
if (funcName == "CalculateKeyFromSeed") {
int key = 0;
int nrev = 0;
for (int i = 0; i < keyLen; i++) {
final originalByte = ((key >> ((keyLen - 1 - i) * 8)) & 0xFF);
nrev |= (originalByte << (i * 8));
}
setValue("key", nrev);
return CC_OK;
}
return CC_INVALID_FUNCTION_NAME;
}
// -------------------------------
// PROTOCOL
// -------------------------------
if (fnStr == "protocol") {
final protocolStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
Logger.log("protocol: $protocolStr");
const protocolMap = {
"ISO15765-250KB-11BIT-CAN": PROTOCOL_ISO15765_250KB_11BIT_CAN,
"ISO15765-250Kb-29BIT-CAN": PROTOCOL_ISO15765_250KB_29BIT_CAN,
"ISO15765-500KB-11BIT-CAN": PROTOCOL_ISO15765_500KB_11BIT_CAN,
"ISO15765-500KB-29BIT-CAN": PROTOCOL_ISO15765_500KB_29BIT_CAN,
"ISO15765-1MB-11BIT-CAN": PROTOCOL_ISO15765_1MB_11BIT_CAN,
"ISO15765-1MB-29BIT-CAN": PROTOCOL_ISO15765_1MB_29BIT_CAN,
"250KB-11BIT-CAN": PROTOCOL_250KB_11BIT_CAN,
"250Kb-29BIT-CAN": PROTOCOL_250KB_29BIT_CAN,
"500KB-11BIT-CAN": PROTOCOL_500KB_11BIT_CAN,
"500KB-29BIT-CAN": PROTOCOL_500KB_29BIT_CAN,
"1MB-11BIT-CAN": PROTOCOL_1MB_11BIT_CAN,
"1MB-29BIT-CAN": PROTOCOL_1MB_29BIT_CAN,
"OE-IVN-250KBPS-11BIT-CAN": PROTOCOL_OE_IVN_250KB_11BIT_CAN,
"OE-IVN-250KBPS-29BIT-CAN": PROTOCOL_OE_IVN_250KB_29BIT_CAN,
"OE-IVN-500KBPS-11BIT-CAN": PROTOCOL_OE_IVN_500KB_11BIT_CAN,
"OE-IVN-500KBPS-29BIT-CAN": PROTOCOL_OE_IVN_500KB_29BIT_CAN,
"OE-IVN-1MBPS-11BIT-CAN": PROTOCOL_OE_IVN_1MB_11BIT_CAN,
"OE-IVN-1MBPS-29BIT-CAN": PROTOCOL_OE_IVN_1MB_29BIT_CAN,
};
final protocolToSet = protocolMap[protocolStr];
if (protocolToSet == null) {
Logger.log("Unknown Protocol");
return CC_UNIMPLEMENTED_FUNCTION;
}
Logger.log("Protocol set: $protocolToSet");
return CC_OK;
}
// -------------------------------
// TXID
// -------------------------------
if (fnStr == "txid") {
final valueStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
final int hexValue = int.parse(valueStr, radix: 16);
Logger.log("Txid set: ${hexValue.toRadixString(16).toUpperCase()}");
return CC_OK;
}
// -------------------------------
// RXID
// -------------------------------
if (fnStr == "rxid") {
final valueStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
final int hexValue = int.parse(valueStr, radix: 16);
Logger.log("Rxid set: ${hexValue.toRadixString(16).toUpperCase()}");
return CC_OK;
}
// -------------------------------
// START PADDING
// -------------------------------
if (fnStr == "strtpadding") {
final valueStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
final int hexValue = int.parse(valueStr, radix: 16) & 0xFF;
Logger.log("Padding byte: ${hexValue.toRadixString(16).toUpperCase()}");
return CC_OK;
}
// -------------------------------
// STOP PADDING
// -------------------------------
if (fnStr == "Stoppadding") {
Logger.log("Stop padding");
return CC_OK;
}
// -------------------------------
// TP START
// -------------------------------
if (fnStr == "strtTP") {
Logger.log("Tester Present: START");
tpController.start(sendTesterPresent: sendTesterPresent);
return CC_OK;
}
// -------------------------------
// TP STOP
// -------------------------------
if (fnStr == "stopTP") {
tpController.stop();
Logger.log("Tester Present: STOP");
return CC_OK;
}
// -------------------------------
// SET T2MAX
// -------------------------------
if (fnStr == "setT2MAX") {
final valueStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
final int timeMs = int.parse(valueStr, radix: 16) & 0xFFFF;
Logger.log("T2MAX set: ${timeMs.toRadixString(16).toUpperCase()} ms");
return CC_OK;
}
// -------------------------------
// SET STMIN
// -------------------------------
if (fnStr == "setstmin") {
final valueStr = String.fromCharCodes(fdata, 0, fdataLen).trim();
final int timeMs = int.parse(valueStr, radix: 16) & 0xFFFF;
Logger.log("STmin set: ${timeMs.toRadixString(16).toUpperCase()} ms");
return CC_OK;
}
// -------------------------------
// ECUMAPFILE / CHKSUM
// -------------------------------
if (fnStr == "EcuMapFile" || fnStr == "chksum") {
return CC_OK;
}
// -------------------------------
// UNKNOWN COMMAND
// -------------------------------
Logger.log("Unrecognized command: $fnStr");
return CC_UNIMPLEMENTED_FUNCTION;
}
Future<int> lineOp(
RandomAccessFile fp,
Uint8List buff,
int len,
RptHandle? h,
String jsonFileName,
) async {
Logger.log("FN($len): ${String.fromCharCodes(buff)}");
// Comment check
if (len >= 2 &&
buff[0] == '/'.codeUnitAt(0) &&
buff[1] == '/'.codeUnitAt(0)) {
Logger.log("Comment Only");
return CC_LINE_COMMENT;
}
// Split fn:data
final sep = cmdOpSep(buff, len);
final fn = sep.fn; // List<int>
final data = sep.data; // List<int>
Logger.log("FN(${fn.length}): ${String.fromCharCodes(fn)}");
if (data.isNotEmpty) {
Logger.log("DATA(${data.length}): ${String.fromCharCodes(data)}");
}
// Convert List<int> --> Uint8List
final Uint8List fnBytes = Uint8List.fromList(fn);
final Uint8List dataBytes = Uint8List.fromList(data);
Logger.log('fnBytes: $fnBytes');
Logger.log('dataBytes: $dataBytes');
return await performFn(
fp,
fnBytes,
fnBytes.length,
dataBytes,
dataBytes.length,
h,
jsonFileName,
);
// return 0;
}
Future<int?> getVariableValue(String jsonFileName, String nameIn) async {
// Step 1: Copy & clean name
String name = nameIn.trim();
// Step 2: Check for array-like syntax
final regex = RegExp(r'(\w+)\[(.+)\]');
final match = regex.firstMatch(name);
if (match != null) {
final tok = match.group(1)!;
final indexStr = match.group(2)!;
final vindex = await getVariableValue(jsonFileName, indexStr);
if (vindex == null || vindex < 0) return null;
final indexingForJson = vindex > 0 ? vindex - 1 : 0;
// Handle JSON variable types
if (tok == "json_strt_addr") {
final val = await getJsonAddressVal(
jsonFileName,
"JsonStartAddress",
indexingForJson,
);
return val;
} else if (tok == "json_end_addr") {
final val = await getJsonAddressVal(
jsonFileName,
"JsonEndAddress",
indexingForJson,
);
return val;
} else if (tok == "json_sector_len") {
final start = await getJsonAddressVal(
jsonFileName,
"JsonStartAddress",
indexingForJson,
);
final end = await getJsonAddressVal(
jsonFileName,
"JsonEndAddress",
indexingForJson,
);
return end - start + 1; // inclusive
} else if (tok == "json_checksum") {
final val = await getJsonAddressVal(
jsonFileName,
"JsonCheckSum",
indexingForJson,
);
return val;
} else {
Logger.log("Unknown JSON variable requested: $tok");
return null;
}
}
// Step 3: Handle no_of_sectors or noofsectors
else if (name == "no_of_sectors" || name == "noofsectors") {
final val = await getNumberOfSectors(jsonFileName);
Logger.log("Got no of sector value: $val");
return val;
}
// Step 4: Otherwise, try to find normal variable
final actVal = getValuePointer(name);
if (actVal == null) {
Logger.log("Unexpected variable not found: $name");
return null;
} else {
Logger.log("Found variable $name = ${actVal.value}");
}
return actVal.value;
}
RptHandle? getHandle(String hdl) {
RptHandle? nextFree;
for (var h in rphdls) {
// Empty entry (hdl == "")
if (h.hdl.isEmpty) {
nextFree ??= h;
}
// hdl matches existing
else if (h.hdl == hdl) {
return h;
}
}
// No free slot found → return null
if (nextFree == null) {
return null;
}
// Assign name (C strcpy)
nextFree.hdl = hdl;
return nextFree;
}
Future<int> commandSend(String jsonFileName, Uint8List fdata) async {
Logger.log("Send Command");
// Output buffer (same as hdata[1024])
final Uint8List hdata = Uint8List(1024);
int hlen = 0;
DataSt ds = DataSt.DS_DT; // DS_DT = hex-data, DS_VAR = <var> section
int usedLen = 0;
// Process entire fdata buffer
while (usedLen < fdata.length) {
int ch = fdata[usedLen];
// ================================
// (1) DATA MODE (hex text)
// ================================
if (ds == DataSt.DS_DT) {
// If we see '+', switch to variable mode
if (ch == '+'.codeUnitAt(0)) {
ds = DataSt.DS_VAR;
usedLen++;
continue;
}
// Find next '+' or end
int plusIndex = fdata.indexOf('+'.codeUnitAt(0), usedLen);
if (plusIndex == -1) plusIndex = fdata.length;
// Extract hex token as string
String tok = String.fromCharCodes(fdata.sublist(usedLen, plusIndex));
int toklen = tok.length;
if (toklen % 2 != 0) {
Logger.log("Hexstring not even: $tok");
} else {
Uint8List bytes = convertHexStringToBytes(tok);
hdata.setRange(hlen, hlen + bytes.length, bytes);
hlen += bytes.length;
}
// Jump past this token and + null-like separator
ds = DataSt.DS_VAR;
usedLen = plusIndex + 1;
continue;
}
// ================================
// (2) VARIABLE MODE (<var,bytes>)
// ================================
if (ds == DataSt.DS_VAR) {
if (ch != '<'.codeUnitAt(0)) {
Logger.log("Unexpected non-var start: '${String.fromCharCode(ch)}'");
return CC_VARIABLE_NOT_FOUND;
}
// Find closing '>'
int closing = fdata.indexOf('>'.codeUnitAt(0), usedLen);
if (closing == -1) {
Logger.log("Variable enclosure missing");
return CC_VARIABLE_NOT_FOUND;
}
// Extract full <...> contents
String inside = String.fromCharCodes(
fdata.sublist(usedLen + 1, closing),
); // skip '<' and '>'
List<String> parts = inside.split(',');
String varName = parts[0].trim();
int varByteSize = (parts.length > 1) ? int.tryParse(parts[1]) ?? 1 : 1;
Logger.log("Fetching variable $varName (size: $varByteSize)");
// ---- Get variable value
final variableValue = await getVariableValue(jsonFileName, varName);
if (variableValue == null) {
Logger.log("Could not get value for variable: $varName");
return CC_VARIABLE_NOT_FOUND;
}
// ---- Write variable bytes (BIG-ENDIAN)
for (int i = 0; i < varByteSize; i++) {
int shift = (8 * (varByteSize - 1 - i));
hdata[hlen++] = (variableValue >> shift) & 0xFF;
}
// Move past <...>
usedLen = closing + 1;
ds = DataSt.DS_DT;
continue;
}
}
// LOG FINAL PACKET
Logger.log("SEND CMD FINAL LEN = $hlen");
Logger.log("FINAL BYTES = ${hdata.sublist(0, hlen)}");
// ---- SEND THROUGH CAN
return handleSend(hdata.sublist(0, hlen));
// return CC_OK;
}
/// Correct version of convertHexStringToBytes (from previous chat)
Uint8List convertHexStringToBytes(String hexString) {
hexString = hexString.replaceAll(RegExp(r'[^0-9A-Fa-f]'), '');
int length = hexString.length;
Uint8List bytes = Uint8List(length ~/ 2);
for (int i = 0; i < length; i += 2) {
bytes[i ~/ 2] = int.parse(hexString.substring(i, i + 2), radix: 16);
}
return bytes;
}
int convertHexStringToInt(String hexStr) {
// Clean and normalize hex
hexStr = hexStr.replaceAll(RegExp(r'[^0-9A-Fa-f]'), '');
if (hexStr.length.isOdd) hexStr = "0$hexStr";
final bytes = convertHexStringToBytes(hexStr);
// Step 1: Place into END of 8-byte buffer (rev)
final rev = List<int>.filled(8, 0);
int hblen = bytes.length;
int offset = 8 - hblen;
for (int i = 0; i < hblen; i++) {
rev[offset + i] = bytes[i];
}
// Step 2: Reverse to get nrev
final nrev = List<int>.filled(8, 0);
for (int i = 0; i < 8; i++) {
nrev[i] = rev[7 - i];
}
// Step 3: Interpret nrev AS LITTLE-ENDIAN INT64 (this matches C behavior)
final bd = ByteData(8);
for (int i = 0; i < 8; i++) {
bd.setUint8(i, nrev[i]);
}
// LITTLE ENDIAN !!
int result = bd.getInt64(0, Endian.little);
return result;
}
String normalizeHex(String raw) {
raw = raw.replaceAll(RegExp(r'[^0-9A-Fa-f]'), '');
if (raw.isEmpty) return "00";
if (raw.length.isOdd) raw = "0$raw";
return raw;
}
Future<int> getJsonHexValueOfKey(String jsonFilePath, String searchKey) async {
final buffer = Uint8List(200);
int arlen = 0;
int jr = await getJsonValue(
jsonFilePath,
searchKey,
buffer,
(newLen) => arlen = newLen,
);
if (jr != 0) {
Logger.log("Json value failed to obtain");
return 0;
}
// Extract raw string
String raw = utf8.decode(buffer.sublist(0, arlen), allowMalformed: true);
raw = raw.replaceAll('\u0000', '').trim();
// Logger.log("RAW = $raw");
// Logger.log("CODE UNITS = ${raw.codeUnits}");
// FINAL FIX DO NOT SUBSTRING
final extracted = raw;
// Logger.log("EXTRACTED = $extracted");
return int.parse(extracted, radix: 16);
}
Future<int> getJsonAddressVal(
String jsonFilePath,
String vname,
int vindex,
) async {
// Equivalent to: sprintf(actJsonSrch, "SectorData[%d].%s", vindex, vname);
final actJsonSrch = "SectorData[$vindex].$vname";
// print("Actual Json search str = $actJsonSrch");
// Call your Dart version of getJsonHexValueOfKey()
return await getJsonHexValueOfKey(jsonFilePath, actJsonSrch);
}
Future<String> getElementStringFromFile(
String filePath,
int start,
int length,
) async {
if (length > 100) {
return ""; // same as arr[0] = '\0'
}
final file = File(filePath);
final raf = await file.open();
// Save current pointer
final originalPos = await raf.position();
// Move pointer like fseek(fp, s)
await raf.setPosition(start);
// Read l bytes
final data = await raf.read(length);
// Null terminate equivalent: Dart strings do NOT need it
final str = utf8.decode(data, allowMalformed: true);
// Restore pointer (like fseek(fp, n))
await raf.setPosition(originalPos);
await raf.close();
return str;
}
dynamic searchKey(dynamic root, String key) {
if (root == null) return null;
// Convert key to a mutable buffer like C
final keycp = key.split('').toList();
keycp.add('\x00'); // null-terminator like C
int keyOpIndex = 0;
dynamic t = root;
while (true) {
if (keyOpIndex >= keycp.length) return t;
final char = keycp[keyOpIndex];
// ---------------------------
// Case 1: array indexing "[n]"
// ---------------------------
if (char == '[') {
int start = keyOpIndex + 1;
int end = start;
// find closing bracket ']'
while (end < keycp.length && keycp[end] != ']') end++;
if (end >= keycp.length) return null;
final indexStr = keycp.sublist(start, end).join();
final index = int.tryParse(indexStr);
if (index == null) return null;
if (t is! List || index >= t.length) return null;
t = t[index];
keyOpIndex = end + 1;
continue;
}
// ---------------------------
// Case 2: dot separator "."
// ---------------------------
if (char == '.') {
keyOpIndex++;
continue;
}
// ---------------------------
// Case 3: End of key
// ---------------------------
if (char == '\x00') {
return t;
}
// ---------------------------
// Case 4: Normal object key
// read until '[' or '.' or '\0'
// ---------------------------
int start = keyOpIndex;
while (keyOpIndex < keycp.length &&
keycp[keyOpIndex] != '[' &&
keycp[keyOpIndex] != '.' &&
keycp[keyOpIndex] != '\x00') {
keyOpIndex++;
}
final keyName = keycp.sublist(start, keyOpIndex).join();
// Access Map key
if (t is Map<String, dynamic>) {
if (!t.containsKey(keyName)) {
return null;
}
t = t[keyName];
} else {
return null;
}
// loop continues
}
}
String getElementStringFromBytes(Uint8List bytes, int s, int l) {
if (l > 100) {
return ""; // same as C arr[0] = '\0';
}
if (s < 0 || l <= 0 || s + l > bytes.length) {
return "";
}
// Slice bytes [s .. s+l)
final Uint8List sub = bytes.sublist(s, s + l);
// Decode UTF-8 and return
return String.fromCharCodes(sub);
}
Future<int> getJsonValue(
String jsonFilePath,
String key,
Uint8List outBuffer,
void Function(int) updateLength,
) async {
// Load JSON file
final file = File(jsonFilePath);
if (!await file.exists()) {
Logger.log("JSON file not found: $jsonFilePath");
return -1;
}
final content = await file.readAsString();
dynamic parsedJson;
try {
parsedJson = json.decode(content);
} catch (e) {
Logger.log("Failed to parse JSON: $e");
return -1;
}
// Extract the value using nested key search
final value = searchKey(parsedJson, key);
if (value == null) {
return -3; // Key not found
}
final raw = value.toString();
final length = raw.length;
// Set length (like writing to pointer in C)
updateLength(length);
// Ensure buffer is large enough
if (length > outBuffer.length) {
return -2;
}
// Write into output buffer
for (int i = 0; i < length; i++) {
outBuffer[i] = raw.codeUnitAt(i);
}
return 0;
}
Future<int> getNumberOfSectors(String jsonFilePath) async {
// Must be Uint8List because native code receives raw bytes
final buffer = Uint8List(200);
int len = 0; // We will set this using callback
int result = await getJsonValue(
jsonFilePath,
"NoOfSectors",
buffer,
(int v) => len = v, // callback sets length
);
if (result != 0 || len == 0) {
Logger.log("Failed to obtain NoOfSectors");
return 0;
}
// Extract string from raw bytes
final value = utf8.decode(buffer.sublist(0, len));
return int.tryParse(value.trim()) ?? 0;
}
Future<int> scrollJson(String jsonFilePath) async {
try {
final file = File(jsonFilePath);
if (!await file.exists()) {
Logger.log("Failed to open file");
return -1;
}
// Reset pool index
njsoi = 0;
for (int i = 0; i < MAX_JSON_ITEMS; i++) {
// optional: reset fields of each node if you reuse pool
final node = jsonElementStorageArray[i];
node.start = 0;
node.end = 0;
node.len = 0;
node.parent = null;
node.child = null;
node.next = null;
node.prev = null;
node.waitingForColon = false;
node.elemIndex = -1;
node.type = JsonElementType.object;
}
final bytes = await file.readAsBytes();
int n = 0;
JsonElement? current;
final root = getNewRootElement();
// iterate through bytes and feed parser with a single-byte buffer each time
while (n < bytes.length && n < 0xFFFFFF) {
final Uint8List buf = Uint8List(1);
buf[0] = bytes[n];
final int ret = parseBytes(buf, 1, n, root, current, (cur) {
current = cur;
});
if (ret != 0) {
Logger.log("parse error at index $n (ret=$ret)");
return -2;
}
n++;
}
Logger.log("Read Bytes count: $n");
jsroot = root;
return 0;
} catch (e) {
Logger.log("Exception reading JSON: $e");
return -2;
}
}
Future<int> fetchTotalBytesToFlash(String jsonFilePath) async {
final noOfSectors = await getNumberOfSectors(jsonFilePath);
Logger.log("Got no of sector value: $noOfSectors");
int total = 0;
for (int i = 0; i < noOfSectors; i++) {
final startKey = "SectorData[$i].JsonStartAddress";
final endKey = "SectorData[$i].JsonEndAddress";
Logger.log("Searching for $startKey");
final startAddr = await getJsonHexValueOfKey(jsonFilePath, startKey);
Logger.log("Searching for $endKey");
final endAddr = await getJsonHexValueOfKey(jsonFilePath, endKey);
final size = (endAddr - startAddr) + 1;
Logger.log(
"Sector $i → Start: ${startAddr.toRadixString(16)} End: ${endAddr.toRadixString(16)} Size: $size",
);
total += size;
}
Logger.log("Total bytes to flash = $total");
return total;
}
Future<int> crawler(
RandomAccessFile fp,
String jsonFilePath,
RptHandle? h,
) async {
final Uint8List buff = Uint8List(1024);
int rl;
do {
rl = await readLine(fp, buff);
Logger.log('readline: $rl ${String.fromCharCodes(buff.sublist(0, rl))}');
if (rl > 0) {
final rx = trimCRLF(buff, rl);
Logger.log('trimCRLF: $rx ${String.fromCharCodes(buff.sublist(0, rx))}');
if (rx > 0) {
if (tpController.abortFSQ) {
Logger.log("TP ABORT → FSQ TERMINATED");
return CC_ECU_NO_RESPONSE;
}
final clean = Uint8List.fromList(buff.sublist(0, rx));
final loRet = await lineOp(fp, clean, clean.length, h, jsonFilePath);
Logger.log('loRet: $loRet');
if (loRet < CC_OK) {
return loRet;
}
}
}
} while (rl > 0);
return CC_OK;
}
Future<void> start(String jsonFilePath, String fsqFilePath) async {
try {
final jsonFile = File(jsonFilePath);
final fsqFile = File(fsqFilePath);
if (!await jsonFile.exists()) {
Logger.log("JSON file not found: $jsonFilePath");
return;
}
if (!await fsqFile.exists()) {
Logger.log("FSQ file not found: $fsqFilePath");
return;
}
Logger.log("JSON file path: $jsonFilePath");
Logger.log("FSQ file path: $fsqFilePath");
final stopwatch = Stopwatch()..start();
final scrollResult = await scrollJson(jsonFilePath);
if (scrollResult != 0) {
Logger.log("Error: Failed to scroll JSON file (ERR: $scrollResult)");
return;
} else {
Logger.log("Sucess: scroll JSON file: $scrollResult");
}
Logger.log("Time spent parsing JSON: ${stopwatch.elapsedMicroseconds} µs");
fetchTotalBytesToFlash(jsonFilePath);
flashStatus = FlashStatus.start;
final checkFsqFile = await openFsqFile(fsqFilePath);
if (checkFsqFile == null) {
return;
}
initVars();
final fp = await fsqFile.open();
int crRet = await crawler(fp, jsonFilePath, null);
await fp.close();
flashStatus = FlashStatus.completed;
if (crRet != 0) {
flashStatus = FlashStatus.aborted;
Logger.log('Crawler Returned with error: $crRet');
}
Logger.log('crawler result: $crRet');
} catch (e) {
Logger.log('Exception opening directory, $e');
return;
}
}