Adapt WSS to new boards and use MQTT domain for eSIM connectivity

Preserve main GNSS and volume behavior while porting verified TLS, receive fixes and full-handshake optimizations. Add MQTT connection diagnostics and route MQTT through banban.api.talkingq.com:1883.

Include migration documentation and regression tools. Firmware builds and package checks passed; user confirmed new-board connectivity after switching the MQTT host to the domain.
This commit is contained in:
chenjintao
2026-09-16 17:27:21 +08:00
parent 2746cc3d4e
commit 3deb51d838
45 changed files with 3724 additions and 4815 deletions
+61
View File
@@ -0,0 +1,61 @@
"""Check actual ARMCC kernel / GCC APP TLS layouts and enabled features."""
import json
import os
from pathlib import Path
import struct
import subprocess
root = Path(__file__).resolve().parents[1]
output = root / 'artifacts/wss/new-board'
output.mkdir(parents=True, exist_ok=True)
toolchain = root / 'ql-cross-tool/win32/owtoolchain'
gcc = toolchain / 'gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe'
objcopy = gcc.with_name('arm-none-eabi-objcopy.exe')
armcc = toolchain / 'ARM_Compiler_5/bin/armcc.exe'
types = ['ssl_context', 'ssl_config', 'net_context', 'x509_crt', 'pk_context',
'entropy_context', 'ctr_drbg_context', 'ecp_group', 'ecp_point', 'mpi',
'ssl_handshake_params', 'ecdh_context']
fields = {'ssl_context': ['conf', 'state', 'in_buf', 'out_buf', 'session', 'handshake', 'hostname'],
'ssl_config': ['ca_chain', 'cert_profile', 'f_rng', 'p_rng', 'curve_list'],
'net_context': ['fd', 'cid'], 'x509_crt': ['pk', 'sig', 'next'],
'ssl_handshake_params': ['ecdh_ctx'], 'ecdh_context': ['grp_id']}
labels = ['sizeof(mbedtls_' + t + ')' for t in types]
labels += ['offsetof(mbedtls_' + t + ', ' + f + ')' for t, fs in fields.items() for f in fs]
source = output / 'tls_layout.c'
source.write_text('#include <stddef.h>\n#include "mbedtls/ssl_internal.h"\n'
'#include "mbedtls/net_sockets.h"\n#include "mbedtls/entropy.h"\n'
'#include "mbedtls/ctr_drbg.h"\n'
'const unsigned int talkingq_tls_layout[] = {\n' + ',\n'.join(labels) + '\n};\n')
env = os.environ.copy()
env['ARMLMD_LICENSE_FILE'] = str(toolchain / 'license.dat')
layouts, macros = {}, {}
for target in ['kernel', 'app']:
include = root / ('ql-kernel' if target == 'kernel' else 'ql-application') / 'threadx/common/include'
obj = output / (target + '-tls-layout.o')
flags = ['-I' + str(include), '-DQUECTEL_OPEN_CPU']
if target == 'kernel':
command = [str(armcc), '--cpu', 'Cortex-R5F', '--fpu=softvfp+vfpv3_d16',
'--gnu', '--thumb', '-c', str(source), '-o', str(obj)] + flags
section = '.constdata'
else:
command = [str(gcc), '-mcpu=cortex-r5', '-mthumb', '-mfloat-abi=softfp', '-mfpu=vfpv3-d16',
'-c', str(source), '-o', str(obj)] + flags
section = '.rodata'
subprocess.run(command, env=env, check=True)
binary = obj.with_suffix('.bin')
subprocess.run([str(objcopy), '-O', 'binary', '-j', section, str(obj), str(binary)], check=True)
data = binary.read_bytes()
assert len(data) == len(labels) * 4, (target, len(data))
layouts[target] = dict(zip(labels, struct.unpack('<' + 'I' * len(labels), data)))
defines = subprocess.check_output([str(gcc), '-E', '-dM', '-x', 'c', '-include', 'mbedtls/config.h',
'NUL'] + flags, text=True)
macros[target] = sorted(line for line in defines.splitlines() if line.startswith('#define MBEDTLS_')
or line.startswith('#define QUECTEL_MBEDTLS_'))
assert layouts['kernel'] == layouts['app'], json.dumps(layouts, indent=2)
assert macros['kernel'] == macros['app'], json.dumps({k: sorted(set(macros[k]) - set(macros['app' if k == 'kernel' else 'kernel'])) for k in macros})
for feature in ['ECDSA_C', 'ECP_NIST_OPTIM', 'KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 'GCM_C', 'HAVE_TIME_DATE', 'PLATFORM_TIME_ALT']:
assert any(line.startswith('#define MBEDTLS_' + feature + ' ') for line in macros['kernel'])
report = {'result': 'PASS', 'layout': layouts['kernel'], 'features': macros['kernel']}
(output / 'tls-abi.json').write_text(json.dumps(report, indent=2))
print('PASS: ARMCC kernel / GCC APP TLS sizes, field offsets and feature macros agree')
print(json.dumps(layouts['kernel'], indent=2))
+59
View File
@@ -0,0 +1,59 @@
param([string]$OutputDir = "$PSScriptRoot/../artifacts/wss", [ValidateSet(443, 8080)][int]$ServerPort = 443)
$ErrorActionPreference = 'Stop'
$serverHost = 'banban.api.talkingq.com'
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
$OutputDir = (Resolve-Path -LiteralPath $OutputDir).Path
$tcp = New-Object System.Net.Sockets.TcpClient
$tls = $null
try {
$pending = $tcp.BeginConnect($serverHost, $serverPort, $null, $null)
if (-not $pending.AsyncWaitHandle.WaitOne(15000)) { throw 'TCP connect timeout' }
$tcp.EndConnect($pending)
$tcp.ReceiveTimeout = 15000
$tcp.SendTimeout = 15000
# Default SslStream validation checks the trusted chain and server hostname.
$tls = New-Object System.Net.Security.SslStream($tcp.GetStream(), $false)
$tls.ReadTimeout = 15000
$tls.WriteTimeout = 15000
$tls.AuthenticateAsClient($serverHost, $null, [System.Security.Authentication.SslProtocols]::Tls12, $false)
$leaf = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tls.RemoteCertificate)
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
if (-not $chain.Build($leaf)) { throw 'Trusted certificate chain could not be built' }
$certificates = @()
for ($i = 0; $i -lt $chain.ChainElements.Count; $i++) {
$cert = $chain.ChainElements[$i].Certificate
$pem = "-----BEGIN CERTIFICATE-----`n" + [Convert]::ToBase64String($cert.RawData, [Base64FormattingOptions]::InsertLineBreaks).Replace("`r`n", "`n") + "`n-----END CERTIFICATE-----`n"
[IO.File]::WriteAllText((Join-Path $OutputDir "chain-$i.pem"), $pem, [Text.Encoding]::ASCII)
$certificates += [ordered]@{subject=$cert.Subject; issuer=$cert.Issuer; not_before=$cert.NotBefore.ToUniversalTime().ToString('o'); not_after=$cert.NotAfter.ToUniversalTime().ToString('o'); thumbprint=$cert.Thumbprint}
if ($i -eq $chain.ChainElements.Count - 1) {
if ($cert.Subject -ne $cert.Issuer) { throw 'Chain did not terminate at a root CA' }
[IO.File]::WriteAllText((Join-Path $OutputDir 'root-ca.pem'), $pem, [Text.Encoding]::ASCII)
}
}
$nonce = New-Object byte[] 16
$rng = [Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($nonce)
$rng.Dispose()
$key = [Convert]::ToBase64String($nonce)
$request = "GET /ws HTTP/1.1`r`nHost: ${serverHost}:${serverPort}`r`nUpgrade: websocket`r`nConnection: Upgrade`r`nSec-WebSocket-Key: $key`r`nSec-WebSocket-Version: 13`r`n`r`n"
$bytes = [Text.Encoding]::ASCII.GetBytes($request)
$tls.Write($bytes, 0, $bytes.Length)
$response = ''
while (-not $response.EndsWith("`r`n`r`n") -and $response.Length -lt 16384) {
$next = $tls.ReadByte()
if ($next -lt 0) { break }
$response += [char]$next
}
$sha1 = [Security.Cryptography.SHA1]::Create()
$expected = [Convert]::ToBase64String($sha1.ComputeHash([Text.Encoding]::ASCII.GetBytes($key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')))
$sha1.Dispose()
$accepted = $response -match '^HTTP/1\.1 101 ' -and $response -match ('(?im)^Sec-WebSocket-Accept:\s*' + [regex]::Escape($expected) + '\s*$')
$report = [ordered]@{url="wss://${serverHost}:${serverPort}/ws"; tested_utc=[DateTime]::UtcNow.ToString('o'); tls_protocol=$tls.SslProtocol.ToString(); cipher=$tls.CipherAlgorithm.ToString(); cipher_bits=$tls.CipherStrength; certificate_validation='System trust and hostname validation passed'; certificates=$certificates; websocket_upgrade=$accepted; response_headers=$response; device_auth_sent=$false}
$report | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath (Join-Path $OutputDir 'server-probe.json')
$report | ConvertTo-Json -Depth 6
if (-not $accepted) { throw 'WebSocket upgrade did not return a valid HTTP 101 response' }
} finally {
if ($tls) { $tls.Dispose() }
$tcp.Close()
}
+40
View File
@@ -0,0 +1,40 @@
"""Probe TLS 1.2 full-handshake group negotiation with the firmware root CA."""
import json
from pathlib import Path
import re
import subprocess
root = Path(__file__).resolve().parents[1]
source = (root / 'ql-application/threadx/interface/custom_app/talkingq_root_ca.c').read_text()
pem = ''.join(json.loads('"' + value + '"') for value in re.findall(r'"(.*?)"', source)
if 'CERTIFICATE' in value or value.endswith(r'\n'))
output = root / 'artifacts/wss/full-handshake'
output.mkdir(parents=True, exist_ok=True)
ca = output / 'compiled-root.pem'
ca.write_text(pem)
openssl = Path('C:/Program Files/Git/usr/bin/openssl.exe')
groups = {
'sdk_order': 'P-521:brainpoolP512r1:P-384:brainpoolP384r1:P-256:brainpoolP256r1:P-224:secp224k1:P-192:secp192k1:X25519:X448',
'x25519_first': 'X25519:P-256:P-384:P-521',
'p256_first': 'P-256:X25519:P-384:P-521',
}
firmware = (root / 'ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c').read_bytes().decode('latin1')
array = re.search(r'rws_tls_preferred_curves\[\]\s*=\s*\{(.*?)\}', firmware, re.S).group(1)
names = {'CURVE25519': 'X25519', 'CURVE448': 'X448',
'SECP256R1': 'P-256', 'SECP384R1': 'P-384', 'SECP521R1': 'P-521',
'SECP224R1': 'P-224', 'SECP192R1': 'P-192',
'SECP224K1': 'secp224k1', 'SECP192K1': 'secp192k1',
'BP512R1': 'brainpoolP512r1', 'BP384R1': 'brainpoolP384r1', 'BP256R1': 'brainpoolP256r1'}
groups['firmware_policy'] = ':'.join(names[name] for name in re.findall(r'MBEDTLS_ECP_DP_(\w+)', array) if name != 'NONE')
report = {}
for name, group_list in groups.items():
result = subprocess.run([str(openssl), 's_client', '-connect', 'banban.api.talkingq.com:443',
'-servername', 'banban.api.talkingq.com', '-verify_hostname', 'banban.api.talkingq.com',
'-verify_return_error', '-CAfile', str(ca), '-no-CApath', '-no-CAstore',
'-tls1_2', '-no_ticket', '-groups', group_list, '-brief'],
input='', text=True, capture_output=True, timeout=20)
text = result.stdout + result.stderr
report[name] = {'groups': group_list, 'returncode': result.returncode, 'output': text}
print(name + ':\n' + text, flush=True)
assert result.returncode == 0 and 'Verification: OK' in text, name
(output / 'group-probe.json').write_text(json.dumps(report, indent=2))
+36
View File
@@ -0,0 +1,36 @@
import hashlib
import json
import re
import socket
import ssl
from pathlib import Path
root = Path(__file__).resolve().parents[1]
source = (root / 'ql-application/threadx/interface/custom_app/talkingq_root_ca.c').read_text()
pem = ''.join(json.loads('"' + value + '"') for value in re.findall(r'"(.*?)"', source) if 'CERTIFICATE' in value or value.endswith(r'\n'))
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(cadata=pem)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_2
server = 'banban.api.talkingq.com'
report = {'root_sha256': hashlib.sha256(ssl.PEM_cert_to_DER_cert(pem)).hexdigest(), 'trust': 'only the compiled root CA; no OS trust or intermediate download'}
with socket.create_connection((server, 443), timeout=15) as tcp:
with context.wrap_socket(tcp, server_hostname=server) as tls:
report['tls'] = tls.version()
report['cipher'] = tls.cipher()
report['root_only_validation'] = 'PASS'
try:
with socket.create_connection((server, 443), timeout=15) as tcp:
with context.wrap_socket(tcp, server_hostname='wrong.invalid'):
raise AssertionError('wrong hostname accepted')
except ssl.SSLCertVerificationError:
report['wrong_hostname_rejected'] = True
empty = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
try:
with socket.create_connection((server, 443), timeout=15) as tcp:
with empty.wrap_socket(tcp, server_hostname=server):
raise AssertionError('untrusted CA accepted')
except ssl.SSLCertVerificationError:
report['untrusted_ca_rejected'] = True
(root / 'artifacts/wss/root-only-probe.json').write_text(json.dumps(report, indent=2))
print(json.dumps(report, indent=2))
+169
View File
@@ -0,0 +1,169 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include "mbedtls/x509_crt.h"
#include "mbedtls/platform_time.h"
#include "mbedtls/gcm.h"
#include "mbedtls/ssl_ciphersuites.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/ctr_drbg.h"
#include "talkingq_wss.h"
typedef union { size_t bytes; long double align; void *ptr; } alloc_header;
static size_t live_bytes, peak_bytes;
void *mbedtls_calloc(size_t n, size_t size) {
alloc_header *h;
size_t bytes;
if (size && n > (SIZE_MAX - sizeof(*h)) / size) return NULL;
bytes = n * size;
h = (alloc_header *)calloc(1, sizeof(*h) + bytes);
if (!h) return NULL;
h->bytes = bytes; live_bytes += bytes;
if (live_bytes > peak_bytes) peak_bytes = live_bytes;
return h + 1;
}
void mbedtls_free(void *p) {
if (p) { alloc_header *h = (alloc_header *)p - 1; live_bytes -= h->bytes; free(h); }
}
static time_t test_time;
static time_t read_test_time(time_t *p) { if (p) *p = test_time; return test_time; }
mbedtls_time_t (*mbedtls_time)(mbedtls_time_t *) = read_test_time;
void CPUartLogPrintf(const char *format, ...) { (void)format; }
int mbedtls_snprintf(char *buffer, size_t size, const char *format, ...)
{
int result;
va_list args;
va_start(args, format);
result = vsnprintf(buffer, size, format, args);
va_end(args);
return result;
}
/* Deterministic entropy is confined to this host benchmark, never firmware. */
static int test_entropy(void *ctx, unsigned char *out, size_t n)
{
size_t i; unsigned int *counter = (unsigned int *)ctx;
for (i = 0; i < n; ++i) out[i] = (unsigned char)(++*counter);
return 0;
}
static void benchmark_ecdh(mbedtls_ecp_group_id id, const char *name)
{
mbedtls_ecp_group peer, client;
mbedtls_ecp_point peer_q, client_q;
mbedtls_mpi peer_d, client_d, secret, expected;
mbedtls_ctr_drbg_context rng;
unsigned int counter = 0;
clock_t elapsed = 0, started;
size_t base;
int i;
mbedtls_ctr_drbg_init(&rng);
assert(mbedtls_ctr_drbg_seed(&rng, test_entropy, &counter, NULL, 0) == 0);
mbedtls_ecp_group_init(&peer); mbedtls_ecp_point_init(&peer_q); mbedtls_mpi_init(&peer_d);
assert(mbedtls_ecp_group_load(&peer, id) == 0);
assert(mbedtls_ecdh_gen_public(&peer, &peer_d, &peer_q, mbedtls_ctr_drbg_random, &rng) == 0);
base = live_bytes; peak_bytes = base;
for (i = 0; i < 20; ++i) {
mbedtls_ecp_group_init(&client); mbedtls_ecp_point_init(&client_q);
mbedtls_mpi_init(&client_d); mbedtls_mpi_init(&secret); mbedtls_mpi_init(&expected);
assert(mbedtls_ecp_group_load(&client, id) == 0);
started = clock();
assert(mbedtls_ecdh_gen_public(&client, &client_d, &client_q, mbedtls_ctr_drbg_random, &rng) == 0);
assert(mbedtls_ecdh_compute_shared(&client, &secret, &peer_q, &client_d, mbedtls_ctr_drbg_random, &rng) == 0);
elapsed += clock() - started;
assert(mbedtls_ecdh_compute_shared(&peer, &expected, &client_q, &peer_d, mbedtls_ctr_drbg_random, &rng) == 0);
assert(mbedtls_mpi_cmp_mpi(&secret, &expected) == 0);
mbedtls_mpi_free(&expected); mbedtls_mpi_free(&secret); mbedtls_mpi_free(&client_d);
mbedtls_ecp_point_free(&client_q); mbedtls_ecp_group_free(&client);
assert(live_bytes == base);
}
printf("Host fresh ECDH %s: %.2f ms, extra peak heap %zu bytes (20 runs, includes peer check heap)\n",
name, 1000.0 * elapsed / CLOCKS_PER_SEC / 20, peak_bytes - base);
mbedtls_ecp_group_free(&peer); mbedtls_ecp_point_free(&peer_q); mbedtls_mpi_free(&peer_d);
mbedtls_ctr_drbg_free(&rng);
}
int main(int argc, char **argv)
{
mbedtls_x509_crt root, chain;
mbedtls_gcm_context gcm;
uint32_t flags;
int ret, i;
char path[1024];
char pem[16384];
FILE *file;
size_t length;
clock_t verify_started;
mbedtls_ecp_group group;
unsigned char key[16] = {0}, iv[12] = {0}, plain[16] = {0}, cipher[16], tag[16];
const unsigned char expected_cipher[16] = {0x03,0x88,0xda,0xce,0x60,0xb6,0xa3,0x92,0xf3,0x28,0xc2,0xb9,0x71,0xb2,0xfe,0x78};
const unsigned char expected_tag[16] = {0xab,0x6e,0x47,0xd4,0x2c,0xec,0x13,0xbd,0xf5,0x3a,0x67,0xb2,0x12,0x57,0xbd,0xdf};
assert(argc == 2);
test_time = time(NULL);
mbedtls_x509_crt_init(&root);
mbedtls_x509_crt_init(&chain);
ret = mbedtls_x509_crt_parse(&root, (const unsigned char *)talkingq_root_ca, talkingq_root_ca_size);
printf("SDK root CA parse: %d (previous device failure: -9774)\n", ret);
assert(ret == 0);
for (i = 0; i < 3; ++i) {
snprintf(path, sizeof(path), "%s/chain-%d.pem", argv[1], i);
file = fopen(path, "rb");
assert(file != NULL);
length = fread(pem, 1, sizeof(pem)-1, file);
assert(feof(file) && !ferror(file));
fclose(file);
pem[length] = 0;
ret = mbedtls_x509_crt_parse(&chain, (const unsigned char *)pem, length + 1);
assert(ret == 0);
}
ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0);
printf("SDK server chain + hostname verify: %d, flags=0x%08x\n", ret, (unsigned int)flags);
assert(ret == 0 && flags == 0);
verify_started = clock();
peak_bytes = live_bytes;
for (i = 0; i < 20; ++i) {
ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0);
assert(ret == 0 && flags == 0);
}
printf("Host certificate chain verify average: %.2f ms (20 runs)\n",
1000.0 * (clock() - verify_started) / CLOCKS_PER_SEC / 20);
printf("Host certificate verify peak heap: %zu bytes, ECP window: %d\n", peak_bytes, MBEDTLS_ECP_WINDOW_SIZE);
mbedtls_ecp_group_init(&group);
assert(mbedtls_ecp_group_load(&group, MBEDTLS_ECP_DP_SECP384R1) == 0);
#ifdef MBEDTLS_ECP_NIST_OPTIM
assert(group.modp != NULL);
#else
assert(group.modp == NULL);
#endif
mbedtls_ecp_group_free(&group);
ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "wrong.invalid", &flags, NULL, NULL, 0);
assert(ret != 0 && (flags & MBEDTLS_X509_BADCERT_CN_MISMATCH));
ret = mbedtls_x509_crt_verify(&chain, NULL, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0);
assert(ret != 0 && (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED));
test_time = 1900000000; /* 2030: this server certificate is expired. */
ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0);
assert(ret != 0 && (flags & MBEDTLS_X509_BADCERT_EXPIRED));
test_time = 1700000000; /* 2023: this server certificate is not yet valid. */
ret = mbedtls_x509_crt_verify(&chain, &root, NULL, "banban.api.talkingq.com", &flags, NULL, NULL, 0);
assert(ret != 0 && (flags & MBEDTLS_X509_BADCERT_FUTURE));
assert(mbedtls_ssl_ciphersuite_from_id(MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) != NULL);
mbedtls_gcm_init(&gcm);
assert(mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, 128) == 0);
assert(mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, 16, iv, 12, NULL, 0,
plain, cipher, 16, tag) == 0);
assert(!memcmp(cipher, expected_cipher, 16) && !memcmp(tag, expected_tag, 16));
mbedtls_gcm_free(&gcm);
mbedtls_x509_crt_free(&chain);
mbedtls_x509_crt_free(&root);
assert(live_bytes == 0);
benchmark_ecdh(MBEDTLS_ECP_DP_CURVE25519, "X25519");
benchmark_ecdh(MBEDTLS_ECP_DP_SECP256R1, "P-256");
benchmark_ecdh(MBEDTLS_ECP_DP_SECP384R1, "P-384");
benchmark_ecdh(MBEDTLS_ECP_DP_SECP521R1, "P-521");
assert(live_bytes == 0);
puts("PASS: SDK ECDSA certificate chain, hostname, trust, dates, TLS suite and AES-GCM vector");
return 0;
}
+37
View File
@@ -0,0 +1,37 @@
"""Build the SDK's real crypto/X.509 sources with MSVC and test the server chain.
Run from a Visual Studio developer shell. Chain PEMs come from the WSS probe.
"""
from pathlib import Path
import argparse
import subprocess
root = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser()
parser.add_argument('chain', nargs='?', default=str(root / 'artifacts/wss/retest-443'))
parser.add_argument('--compare-nist', action='store_true')
parser.add_argument('--compare-windows', action='store_true')
args = parser.parse_args()
output = root / 'artifacts/wss/new-board/host-cert'
output.mkdir(parents=True, exist_ok=True)
chain = Path(args.chain).resolve()
sources = '''asn1parse asn1write bignum base64 pem oid pk pkparse pk_wrap rsa rsa_internal
ecp ecp_curves ecdsa md md5 sha1 sha256 sha512 aes des gcm cipher cipher_wrap
chacha20 chachapoly poly1305 constant_time ctr_drbg x509 x509_crt platform_util
ecdh ssl_ciphersuites'''.split()
command = ['cl', '/nologo', '/O2', '/Gy', '/TC', '/D_CRT_SECURE_NO_WARNINGS',
'/DMBEDTLS_CONFIG_FILE="wss_tls_host_config.h"',
'/I' + str(root / 'tools'), '/I' + str(root / 'tools/wss_host_stubs'),
'/I' + str(root / 'ql-kernel/threadx/common/include'),
'/I' + str(root / 'ql-application/threadx/interface/custom_app/inc'),
str(root / 'tools/test_mbedtls_cert.c'),
str(root / 'ql-application/threadx/interface/custom_app/talkingq_root_ca.c')]
command += [str(root / ('ql-kernel/threadx/quec_mbedtls/' + name + '.c')) for name in sources]
variants = ['window4', 'window5', 'window6'] if args.compare_windows else (['baseline', 'nist'] if args.compare_nist else ['nist'])
for variant in variants:
exe = output / ('test_mbedtls_cert_' + variant + '.exe')
flags = ['/DWSS_TEST_DISABLE_NIST_OPTIM'] if variant == 'baseline' else []
if variant.startswith('window'):
flags += ['/DWSS_TEST_ECP_WINDOW=' + variant[-1]]
subprocess.run(command + flags + ['/Fe' + str(exe), '/link', '/OPT:REF'], cwd=output, check=True)
print('Certificate test variant:', variant, flush=True)
subprocess.run([str(exe), str(chain)], check=True)
+107
View File
@@ -0,0 +1,107 @@
"""Exercise actual MQTT diagnostic callbacks without a broker or credentials."""
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[1]
output = root / 'artifacts/wss/new-board/mqtt-diag/tests'
output.mkdir(parents=True, exist_ok=True)
(output / 'MQTTClient.h').write_text(r'''
#ifndef TEST_MQTT_CLIENT_H
#define TEST_MQTT_CLIENT_H
typedef struct Network Network;
struct Network {
int (*mqttread)(Network *, unsigned char *, int, int);
int (*mqttwrite)(Network *, unsigned char *, int, int);
};
typedef struct { Network *ipstack; unsigned int command_timeout_ms; int isconnected; } MQTTClient;
typedef struct { unsigned char MQTTVersion, cleansession; unsigned short keepAliveInterval; } MQTTPacket_connectData;
typedef struct { unsigned char rc, sessionPresent; } MQTTConnackData;
int MQTTConnectWithResults(MQTTClient *, MQTTPacket_connectData *, MQTTConnackData *);
unsigned int ql_rtos_get_systicks(void);
#endif
''')
(output / 'sockets.h').write_text('int ql_get_soc_errno(void);\n')
harness = r'''
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "mqtt_connect_diag.h"
static unsigned int ticks;
static int mode, offset, writes, reads;
unsigned int ql_rtos_get_systicks(void) { return ticks; }
int ql_get_soc_errno(void) { return 104; }
static int fake_write(Network *n, unsigned char *buf, int size, int timeout) {
(void)n;
assert(timeout == 30000 && size == 19);
assert(memcmp(buf, "PRIVATE_TEST_SECRET", 19) == 0);
++writes; ticks += 2;
return mode == 2 ? -1 : size;
}
static int fake_read(Network *n, unsigned char *buf, int size, int timeout) {
unsigned char response[] = {0x20, 2, 0, 0};
(void)n;
assert(timeout == 30000 && (size == 1 || size == 2));
++reads; ticks += 3;
if (mode == 3) return -1;
if (mode == 4) return 0;
if (mode == 1) response[3] = 5;
if (mode == 5) response[0] = 0x30;
if (mode == 7) { response[2] = 1; response[3] = 5; }
memcpy(buf, response + offset, size); offset += size;
return size;
}
int MQTTConnectWithResults(MQTTClient *client, MQTTPacket_connectData *options, MQTTConnackData *ack) {
unsigned char packet[] = "PRIVATE_TEST_SECRET";
unsigned char response[4] = {0};
int result;
assert(options->MQTTVersion == 3);
assert(ack->rc == 255);
if (mode == 6) return -1;
result = client->ipstack->mqttwrite(client->ipstack, packet, 19, 30000);
if (result != 19) { assert(result == -1); return -1; }
/* SDK reads fixed header, remaining length, then the two-byte body. */
result = client->ipstack->mqttread(client->ipstack, response, 1, 30000);
if (mode == 3 || mode == 4) { assert(result == (mode == 3 ? -1 : 0)); return -1; }
assert(result == 1);
assert(client->ipstack->mqttread(client->ipstack, response + 1, 1, 30000) == 1);
assert(client->ipstack->mqttread(client->ipstack, response + 2, 2, 30000) == 2);
assert(response[1] == 2);
return mode == 0 ? 0 : (mode == 1 ? 5 : -1);
}
int main(void) {
Network network = {fake_read, fake_write};
MQTTClient client = {&network, 30000, 0};
MQTTPacket_connectData options = {3, 0, 60};
for (mode = 0; mode < 8; ++mode) {
int expected = mode == 0 ? 0 : (mode == 1 ? 5 : -1);
ticks = mode == 0 ? 0xfffffffeU : 0; offset = writes = reads = 0;
printf("CASE %d\n", mode);
assert(talkingq_mqtt_connect_diagnosed(&client, &options) == expected);
assert(network.mqttread == fake_read && network.mqttwrite == fake_write);
assert(writes == (mode == 6 ? 0 : 1));
if (mode == 6 || mode == 2) assert(reads == 0);
}
return 0;
}
'''
(output / 'test.c').write_text(harness)
include = root / 'ql-application/threadx/interface/custom_app/inc'
source = root / 'ql-application/threadx/interface/custom_app/mqtt_connect_diag.c'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', '/I' + str(output), '/I' + str(include),
str(source), str(output / 'test.c'), '/Fe' + str(output / 'test.exe')], cwd=output, check=True)
result = subprocess.check_output([str(output / 'test.exe')], text=True)
assert 'PRIVATE_TEST_SECRET' not in result
cases = [part for part in result.split('CASE ')[1:]]
assert len(cases) == 8
assert 'CONNACK code=0 reason=accepted' in cases[0] and 'elapsed_ms=55' in cases[0]
assert 'CONNACK code=5 reason=not_authorized' in cases[1]
assert 'write request=19 rc=-1' in cases[2]
assert 'read request=1 rc=-1' in cases[3]
assert 'read request=1 rc=0' in cases[4]
assert 'no valid initial CONNACK' in cases[5]
assert 'failed before transport write' in cases[6]
assert 'no valid initial CONNACK' in cases[7]
for case in cases[2:]:
assert '[MQTT-DIAG] CONNACK code=' not in case
(output / 'test-output.log').write_text(result)
print('PASS: transport arguments/return values, callback restoration, split CONNACK, rejection, invalid response, errors, no-I/O failure, tick wrap and credential redaction')
+58
View File
@@ -0,0 +1,58 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "talkingq_wss.h"
#include "rws_handshake.h"
static const char accept[] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
static const char good[] = "HTTP/1.1 101 Switching Protocols\r\n"
"uPgRaDe: WebSocket\r\nConnection: keep-alive, Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n";
int main(void)
{
char host[64], path[128], data[RWS_HANDSHAKE_MAX + 20];
int port, status;
size_t i, header;
const char *bad[] = {"ws://example.com/ws", "https://example.com/ws", "wss:///ws",
"wss://user@example.com/ws", "wss://example.com:0/ws", "wss://example.com:65536/ws",
"wss://example.com:-1/ws", "wss://example.com:/ws", "wss://example.com:abc/ws",
"wss://example.com/ws#frag", "wss://example.com/ws\r\nInjected:yes",
"wss://example.com:99999999999999999999999/ws", "wss://example.com/bad path"};
assert(!talkingq_parse_wss_url(TALKINGQ_WSS_URL, host, sizeof(host), &port, path, sizeof(path)));
assert(!strcmp(host, "banban.api.talkingq.com") && port == 443 && !strcmp(path, "/ws"));
assert(!talkingq_parse_wss_url("wss://example.com", host, sizeof(host), &port, path, sizeof(path)));
assert(port == 443 && !strcmp(path, "/ws"));
assert(!talkingq_parse_wss_url("wss://example.com:8080/ws?x=y", host, sizeof(host), &port, path, sizeof(path)));
assert(port == 8080 && !strcmp(path, "/ws?x=y"));
for (i = 0; i < sizeof(bad)/sizeof(bad[0]); ++i)
assert(talkingq_parse_wss_url(bad[i], host, sizeof(host), &port, path, sizeof(path)) == -1);
assert(talkingq_parse_wss_url(TALKINGQ_WSS_URL, host, 3, &port, path, sizeof(path)) == -1);
assert(talkingq_parse_wss_url(TALKINGQ_WSS_URL, host, sizeof(host), &port, path, 3) == -1);
assert(talkingq_is_legacy_ws_url("ws://101.35.224.118:8080"));
assert(talkingq_is_legacy_ws_url("ws://101.35.224.118:8080/ws"));
assert(!talkingq_is_legacy_ws_url(TALKINGQ_WSS_URL));
assert(!talkingq_is_legacy_ws_url("ws://custom.example:8080/ws"));
/* Every possible TLS fragment boundary, with no NUL terminator available. */
for (i = 0; i < sizeof(good)-1; ++i) {
memset(data, 'X', sizeof(data));
memcpy(data, good, i);
assert(rws_check_handshake(data, i, accept, &header, &status) == 0);
}
memcpy(data, good, sizeof(good)-1);
((unsigned char *)data)[sizeof(good)-1] = 0x81; data[sizeof(good)] = 0;
assert(rws_check_handshake(data, sizeof(good)+1, accept, &header, &status) == 1);
assert(status == 101 && header == sizeof(good)-1);
assert(rws_check_handshake(data, sizeof(good)-1, "wrong", &header, &status) == -1);
memcpy(data + 9, "404", 3);
assert(rws_check_handshake(data, sizeof(good)-1, accept, &header, &status) == -1 && status == 404);
strcpy(data, "HTTP/1.1 101 OK\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n");
assert(rws_check_handshake(data, strlen(data), accept, &header, &status) == -1);
memcpy(data, good, sizeof(good));
strcpy(data + sizeof(good)-3, "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n");
assert(rws_check_handshake(data, strlen(data), accept, &header, &status) == -1);
memset(data, 'X', sizeof(data));
assert(rws_check_handshake(data, sizeof(data), accept, &header, &status) == -1);
puts("PASS: WSS URL policy, legacy migration, fragmented upgrade, status, accept, headers and limits");
return 0;
}
+17
View File
@@ -0,0 +1,17 @@
@echo off
setlocal
cd /d "%~dp0.."
if not exist artifacts\wss mkdir artifacts\wss
where cl >nul 2>nul
if errorlevel 1 call "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Auxiliary\Build\vcvars64.bat" >nul
if errorlevel 1 exit /b 1
cl /nologo /W4 /WX /D_CRT_SECURE_NO_WARNINGS /TC /Iql-application\threadx\interface\custom_app\inc /Iql-application\threadx\interface\custom_app\lib\librws\inc tools\test_wss.c ql-application\threadx\interface\custom_app\talkingq_wss.c ql-application\threadx\interface\custom_app\lib\librws\src\rws_handshake.c /Foartifacts\wss\ /Feartifacts\wss\test_wss.exe
if errorlevel 1 exit /b 1
artifacts\wss\test_wss.exe
if errorlevel 1 exit /b 1
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_wss_io.py
if errorlevel 1 exit /b 1
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_wss_receive.py
if errorlevel 1 exit /b 1
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_wss_handshake.py
exit /b %errorlevel%
+75
View File
@@ -0,0 +1,75 @@
"""Test the actual timed handshake loop with scripted SDK steps and 5 ms ticks."""
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[1]
source = (root / 'ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c').read_bytes().decode('latin1')
body = source[source.index('static const char *rws_tls_phase_name('):].split('int rws_ssl_conn(', 1)[0]
harness = r'''
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "mbedtls/ssl_internal.h"
typedef struct { int ret, next_state; unsigned int duration; } step;
static step steps[16];
static int count, calls, sleeps;
static unsigned int ticks;
static void test_log(const char *fmt, ...) { (void)fmt; }
#define LOG_I test_log
static unsigned int ql_rtos_get_systicks(void) { return ticks; }
static void rws_thread_sleep(unsigned int ms) { ticks += ms / 5; ++sleeps; }
int mbedtls_ssl_handshake_step(mbedtls_ssl_context *ctx) {
step s;
assert(ctx->state != MBEDTLS_SSL_HANDSHAKE_OVER);
if (calls >= count) { ++calls; return MBEDTLS_ERR_SSL_WANT_READ; }
s = steps[calls++]; ticks += s.duration; ctx->state = s.next_state; return s.ret;
}
'''
harness += body
harness += r'''
static void reset(mbedtls_ssl_context *ctx) {
memset(ctx, 0, sizeof(*ctx));
ctx->state = MBEDTLS_SSL_CLIENT_HELLO;
count = calls = sleeps = 0; ticks = 0; memset(steps, 0, sizeof(steps));
}
int main(void) {
mbedtls_ssl_context ctx;
mbedtls_ssl_handshake_params handshake = {0};
reset(&ctx);
steps[0] = (step){0, MBEDTLS_SSL_SERVER_HELLO, 1};
steps[1] = (step){MBEDTLS_ERR_SSL_WANT_READ, MBEDTLS_SSL_SERVER_HELLO, 0};
steps[2] = (step){0, MBEDTLS_SSL_SERVER_CERTIFICATE, 2};
steps[3] = (step){0, MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, 10};
steps[4] = (step){MBEDTLS_ERR_SSL_WANT_WRITE, MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, 1};
steps[5] = (step){0, MBEDTLS_SSL_SERVER_FINISHED, 5};
steps[6] = (step){0, MBEDTLS_SSL_HANDSHAKE_OVER, 1}; count = 7;
assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 7 && sleeps == 2 && ticks == 24);
reset(&ctx); ctx.handshake = &handshake; ctx.state = MBEDTLS_SSL_SERVER_KEY_EXCHANGE;
handshake.ecdh_ctx.grp_id = MBEDTLS_ECP_DP_CURVE25519;
steps[0] = (step){0, MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, 1};
steps[1] = (step){0, MBEDTLS_SSL_HANDSHAKE_OVER, 1}; count = 2;
assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 2 && sleeps == 0);
reset(&ctx); steps[0] = (step){-99, MBEDTLS_SSL_SERVER_CERTIFICATE, 1}; count = 1;
assert(rws_tls_handshake_timed(&ctx) == -99 && calls == 1 && sleeps == 0);
reset(&ctx);
assert(rws_tls_handshake_timed(&ctx) == MBEDTLS_ERR_SSL_TIMEOUT && ticks == 4000 && calls < 2100);
reset(&ctx); ticks = (unsigned int)-1000;
assert(rws_tls_handshake_timed(&ctx) == MBEDTLS_ERR_SSL_TIMEOUT && calls < 2100);
reset(&ctx); steps[0] = (step){0, MBEDTLS_SSL_SERVER_CERTIFICATE, 4001}; count = 1;
assert(rws_tls_handshake_timed(&ctx) == MBEDTLS_ERR_SSL_TIMEOUT && calls == 1 && sleeps == 0);
reset(&ctx); ctx.state = MBEDTLS_SSL_HANDSHAKE_OVER;
assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 0);
puts("PASS: timed SDK handshake loop: progress, WANT_READ/WRITE, no progress sleeps, fatal, deadline, tick wrap, complete state");
return 0;
}
'''
output = root / 'artifacts/wss'
output.mkdir(parents=True, exist_ok=True)
cfile = output / 'test_wss_handshake.c'
cfile.write_text(harness, encoding='utf-8')
exe = output / 'test_wss_handshake.exe'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
'/I' + str(root / 'ql-application/threadx/common/include'),
'/Fo' + str(output / 'test_wss_handshake.obj'), '/Fe' + str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+80
View File
@@ -0,0 +1,80 @@
"""Compile actual SDK-independent function bodies with scripted TLS/RTOS stubs."""
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[1]
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws/src'
private = (lib / 'rws_socketpriv.c').read_bytes().decode('latin1')
public = (lib / 'rws_socketpub.c').read_bytes().decode('latin1')
send = private[private.index('static int rws_tls_send_all('):].split('#endif', 1)[0]
cancel = public[public.index('void rws_socket_disconnect_and_release('):].split('rws_bool rws_socket_send_text(', 1)[0]
harness = r'''
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#define MBEDTLS_ERR_SSL_WANT_READ -2
#define MBEDTLS_ERR_SSL_WANT_WRITE -3
#define COMMAND_NONE 0
#define COMMAND_DISCONNECT 6
#define COMMAND_END 9999
typedef struct {int ssl_ctx;} tls;
typedef struct {tls *ssl; int work_mutex, is_connected, worker_started, work_thread, command;} _rws_socket;
typedef _rws_socket *rws_socket;
static int replies[16], calls, reply_count, expected_offset, freed, locks;
static unsigned int ticks;
static const unsigned char bytes[] = "abcdef";
static unsigned int ql_rtos_get_systicks(void) { return ticks; }
static void rws_thread_sleep(int ms) { ticks += (unsigned int)ms; }
static int mbedtls_ssl_write(int *ctx, const unsigned char *data, size_t size) {
int ret;
(void)ctx;
assert(data == bytes + expected_offset);
assert(size == 6 - (size_t)expected_offset);
ret = calls < reply_count ? replies[calls] : MBEDTLS_ERR_SSL_WANT_WRITE;
++calls;
if (ret > 0) expected_offset += ret;
return ret;
}
static void rws_mutex_lock(int mutex) {(void)mutex; ++locks;}
static void rws_mutex_unlock(int mutex) {(void)mutex; --locks;}
static void rws_socket_delete(_rws_socket *s) {(void)s; assert(locks == 0); ++freed;}
'''
harness += send + cancel
harness += r'''
int main(void) {
tls ctx = {0};
_rws_socket s = {0};
s.ssl = &ctx;
replies[0] = -2; replies[1] = 2; replies[2] = -3; replies[3] = 4; reply_count = 4;
assert(rws_tls_send_all(&s, bytes, 6) == 6 && calls == 4 && expected_offset == 6);
calls = 0; expected_offset = 0; replies[0] = 2; replies[1] = -99; reply_count = 2;
assert(rws_tls_send_all(&s, bytes, 6) == -1 && calls == 2);
calls = 0; expected_offset = 0; replies[0] = 0; reply_count = 1;
assert(rws_tls_send_all(&s, bytes, 6) == -1 && calls == 1);
calls = 0; expected_offset = 0; reply_count = 0; ticks = 0;
assert(rws_tls_send_all(&s, bytes, 6) == -1 && ticks >= 1000 && calls < 1000);
calls = 0; ticks = (unsigned int)-500;
assert(rws_tls_send_all(&s, bytes, 6) == -1 && calls < 1000);
s.ssl = NULL; calls = 0;
assert(rws_tls_send_all(&s, bytes, 6) == -1 && calls == 0);
s.is_connected = 1; rws_socket_disconnect_and_release(&s);
assert(s.command == COMMAND_DISCONNECT && locks == 0 && !freed);
s.is_connected = 0; s.worker_started = 1; s.command = 1;
rws_socket_disconnect_and_release(&s);
assert(s.command == COMMAND_END && locks == 0 && !freed);
s.worker_started = 0; s.command = COMMAND_NONE;
rws_socket_disconnect_and_release(&s);
assert(freed == 1 && locks == 0);
rws_socket_disconnect_and_release(NULL);
puts("PASS: TLS partial writes, WANT retry, fatal/EOF, timeout, tick wrap, null context, worker cancellation");
return 0;
}
'''
output = root / 'artifacts/wss'
output.mkdir(parents=True, exist_ok=True)
cfile = output / 'test_wss_io.c'
cfile.write_text(harness, encoding='utf-8')
exe = output / 'test_wss_io.exe'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
'/Fo' + str(output / 'test_wss_io.obj'), '/Fe' + str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+126
View File
@@ -0,0 +1,126 @@
"""Exercise the real receive/upgrade loop with delayed TLS records."""
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[1]
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws'
source = (lib / 'src/rws_socketpriv.c').read_bytes().decode('latin1')
receive = source[source.index('rws_bool rws_socket_recv('):].split('/* RT-Thread Team add */', 1)[0]
upgrade = source[source.index('void rws_socket_wait_handshake_responce('):].split('void rws_socket_send_disconnect(', 1)[0]
harness = r'''
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rws_handshake.h"
#define LIBRWS_USING_MBED_TLS
#define MBEDTLS_ERR_SSL_WANT_READ -2
#define MBEDTLS_ERR_SSL_WANT_WRITE -3
#define MBEDTLS_ERR_NET_RECV_FAILED -4
#define WSAEWOULDBLOCK 11
#define WSAEINPROGRESS 115
#define RWS_RECEIVE_BUFF_SIZE 1024
#define COMMAND_WAIT_HANDSHAKE_RESPONCE 3
#define COMMAND_INFORM_CONNECTED 4
#define COMMAND_INFORM_DISCONNECTED 5
#define rws_error_code_read_write_socket 4
#define rws_error_code_parse_handshake 5
#define LOG_E(...) ((void)0)
#define LOG_D(...) ((void)0)
#define LOG_I(...) ((void)0)
typedef int rws_bool;
enum { rws_false, rws_true };
typedef struct { int ssl_ctx; } tls;
typedef struct {
const char *scheme, *expected_accept;
tls *ssl;
char *payload_buff, *received;
size_t received_size, received_len;
int socket, command, is_connected;
const char *error;
} _rws_socket;
static int replies[16], reply_count, calls, closes;
static size_t offset;
static unsigned int ticks;
static const char response[] =
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n"
"Connection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\nDATA";
static unsigned int ql_rtos_get_systicks(void) { return ticks; }
static void rws_thread_sleep(int ms) { ticks += (unsigned int)ms; }
static void rws_error_delete_clean(const char **e) { *e = NULL; }
static const char *rws_error_new_code_descr(int code, const char *text) { (void)code; return text; }
static void rws_socket_close(_rws_socket *s) { (void)s; ++closes; }
static void rws_socket_resize_received(_rws_socket *s, size_t size) {
s->received = (char *)realloc(s->received, size);
assert(s->received); s->received_size = size;
}
static int recv(int fd, char *buf, int size, int flags) {
(void)fd; (void)buf; (void)size; (void)flags;
assert(0 && "WSS must use TLS receive"); return -1;
}
static int mbedtls_ssl_read(int *ctx, unsigned char *buf, size_t size) {
int ret = calls < reply_count ? replies[calls] : MBEDTLS_ERR_SSL_WANT_READ;
(void)ctx; ++calls;
if (ret > 0) {
assert((size_t)ret <= size && offset + (size_t)ret <= sizeof(response) - 1);
memcpy(buf, response + offset, (size_t)ret); offset += (size_t)ret;
}
return ret;
}
'''
harness += receive + upgrade
harness += r'''
static void reset(_rws_socket *s, tls *ctx) {
free(s->received); memset(s, 0, sizeof(*s));
s->scheme = "wss"; s->ssl = ctx;
s->expected_accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
s->payload_buff = (char *)malloc(RWS_RECEIVE_BUFF_SIZE); assert(s->payload_buff);
s->command = COMMAND_WAIT_HANDSHAKE_RESPONCE;
calls = closes = reply_count = 0; offset = 0; ticks = 0;
}
int main(void) {
tls ctx = {0}; _rws_socket s = {0}; int want;
for (want = -2; want >= -3; --want) {
reset(&s, &ctx); replies[0] = want; reply_count = 1;
assert(!rws_socket_recv(&s));
assert(!s.error && s.command == COMMAND_WAIT_HANDSHAKE_RESPONCE && closes == 0);
replies[1] = 17; replies[2] = want; replies[3] = (int)sizeof(response) - 1 - 17;
reply_count = 4;
rws_socket_wait_handshake_responce(&s);
assert(s.is_connected && !s.error && s.command == COMMAND_INFORM_CONNECTED);
assert(calls == 4 && closes == 0 && s.received_len == 4);
assert(!memcmp(s.received, "DATA", 4)); free(s.payload_buff);
}
reset(&s, &ctx);
rws_socket_wait_handshake_responce(&s);
assert(!s.is_connected && s.error && closes == 1 && ticks >= 2000 && calls < 300);
free(s.payload_buff);
reset(&s, &ctx); ticks = (unsigned int)-1000;
rws_socket_wait_handshake_responce(&s);
assert(!s.is_connected && s.error && closes == 1 && calls < 300); free(s.payload_buff);
for (want = 0; want >= -99; want -= 99) {
reset(&s, &ctx); replies[0] = want; reply_count = 1;
rws_socket_wait_handshake_responce(&s);
assert(!s.is_connected && s.error && closes == 1 && calls == 1);
assert(s.command == COMMAND_INFORM_DISCONNECTED); free(s.payload_buff);
}
reset(&s, NULL); rws_socket_wait_handshake_responce(&s);
assert(s.error && closes == 1 && calls == 0); free(s.payload_buff);
reset(&s, &ctx); s.expected_accept = "wrong";
replies[0] = (int)sizeof(response) - 1; reply_count = 1;
rws_socket_wait_handshake_responce(&s);
assert(!s.is_connected && s.error && closes == 1); free(s.payload_buff);
free(s.received);
puts("PASS: actual TLS receive/upgrade loop: WANT_READ/WRITE, split HTTP 101, trailing frame bytes, timeout/wrap, EOF/fatal, null TLS, invalid Accept");
return 0;
}
'''
output = root / 'artifacts/wss'
output.mkdir(parents=True, exist_ok=True)
cfile = output / 'test_wss_receive.c'
cfile.write_text(harness, encoding='utf-8')
exe = output / 'test_wss_receive.exe'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/wd4018', '/TC',
'/I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'),
'/Fo' + str(output) + '/', '/Fe' + str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+67
View File
@@ -0,0 +1,67 @@
"""Verify the new-board WSS package, including enabled GNSS task entry points."""
import hashlib
import json
import re
import subprocess
import sys
import zipfile
from pathlib import Path
root = Path(__file__).resolve().parents[1]
package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/new-board/EG800AKCN_11LG-new-board-wss-x25519.zip'
output = package.parent
output.mkdir(parents=True, exist_ok=True)
objdump = root / 'ql-cross-tool/win32/owtoolchain/gcc-arm-none-eabi/bin/arm-none-eabi-objdump.exe'
app = root / 'ql-application/threadx/build/app.elf'
symbols = subprocess.check_output([str(objdump.with_name('arm-none-eabi-nm.exe')), str(app)], text=True)
required = ['talkingq_parse_wss_url', 'talkingq_root_ca', 'rws_check_handshake', 'rws_ssl_conn',
'mbedtls_ssl_set_hostname', 'mbedtls_ssl_get_verify_result', 'mbedtls_ssl_conf_authmode',
'mbedtls_ctr_drbg_random', 'mbedtls_sha1', 'mbedtls_base64_encode', 'mbedtls_ssl_handshake_step',
'rws_tls_preferred_curves']
for symbol in required:
assert re.search(r'\b' + symbol + r'$', symbols, re.M), 'Missing linked symbol: ' + symbol
kernel_symbols = subprocess.check_output([str(objdump.with_name('arm-none-eabi-nm.exe')),
str(root / 'ql-kernel/threadx/build/kernel.axf')], text=True)
kernel_required = ['ecdsa_verify_restartable', 'mbedtls_ecdsa_read_signature',
'mbedtls_gcm_crypt_and_tag', 'mbedtls_x509_time_is_future',
'mbedtls_x509_time_is_past', 'mbedtls_platform_gmtime_r', 'mbedtls_time',
'ecp_mod_p256', 'ecp_mod_p384', 'ecp_mod_p521']
for symbol in kernel_required:
assert re.search(r'\b' + symbol + r'$', kernel_symbols, re.M), 'Missing kernel capability: ' + symbol
clock_body = subprocess.check_output([str(objdump), '-d', '--disassemble=x509_get_current_time',
str(root / 'ql-kernel/threadx/build/kernel.axf')], text=True)
assert '<mbedtls_platform_gmtime_r>' in clock_body and '<GetCurrentCompTime>' not in clock_body
(output / 'x509-clock.disasm.txt').write_text(clock_body)
report = {'package': package.name, 'sha256': hashlib.sha256(package.read_bytes()).hexdigest(),
'board': 'new board with GNSS enabled',
'linked_symbols': required, 'kernel_symbols': kernel_required,
'hardware_test': 'New-board package awaits device testing; legacy board verified X25519 full handshake at 1455 ms on 2026-09-16',
'backend': '2026-09-16 11:32 CST: WSS 443 /ws HTTP 101; 8080 plaintext WS'}
with zipfile.ZipFile(package) as archive:
assert archive.testzip() is None, 'ZIP CRC failure'
for name, source in {'cp.bin': 'ql-kernel/threadx/build/release_kernel.bin',
'customer_app.bin': 'ql-application/threadx/build/app.bin'}.items():
data = archive.read(name)
assert data == (root / source).read_bytes(), 'Stale image: ' + name
report[name] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
assert b'wss://banban.api.talkingq.com:443/ws' in archive.read('customer_app.bin')
assert b'WSS TLS handshake total=' in archive.read('customer_app.bin')
assert b'WSS TLS full handshake: prefer X25519' in archive.read('customer_app.bin')
assert b'Legacy board:' not in archive.read('customer_app.bin')
assert b'Legacy board:' not in archive.read('cp.bin')
assert b'gnss_Task_Ref' in archive.read('customer_app.bin')
report['download_manifest'] = json.loads(archive.read('download.json'))
task_create_address = int(re.search(r'^([0-9a-fA-F]+) T ql_rtos_task_create$', symbols, re.M).group(1), 16)
for symbol, binary, expected in [
('quec_get_gnssid_task', root / 'ql-kernel/threadx/build/kernel.axf', '<quec_rtos_task_create>'),
('app_gnss_init', app, '0x%08x' % (task_create_address | 1)),
('set_gnss_mode', app, 'strb')]:
body = subprocess.check_output([str(objdump), '-d', '--disassemble=' + symbol, str(binary)], text=True)
assert '<' + symbol + '>:' in body and expected in body, 'GNSS entry point disabled or unexpected code: ' + symbol
(output / (symbol + '.disasm.txt')).write_text(body)
for symbol, table in [('quec_get_ocname_by_gnss_task', kernel_symbols), ('quec_gnss_test', symbols)]:
assert re.search(r'\b' + symbol + r'$', table, re.M), 'Missing GNSS worker: ' + symbol
report['verification'] = 'PASS: ZIP CRC, current APP/kernel identity, GNSS task creation, WSS URL, ECDSA/GCM/RTC/NIST kernel symbols and handshake timing logs'
(output / 'verification.json').write_text(json.dumps(report, indent=2))
print(report['verification'])
print('SHA256:', report['sha256'])
+1
View File
@@ -0,0 +1 @@
void CPUartLogPrintf(const char *format, ...);
+14
View File
@@ -0,0 +1,14 @@
/* Host adapter: crypto/X.509 features are the firmware's shared profile.
* Only CPU assembly and the OS allocator/clock implementation differ. */
#include "../ql-config/config/talkingq_mbedtls_config.h"
#ifdef WSS_TEST_DISABLE_NIST_OPTIM
#undef MBEDTLS_ECP_NIST_OPTIM
#endif
#ifdef WSS_TEST_ECP_WINDOW
#undef MBEDTLS_ECP_WINDOW_SIZE
#define MBEDTLS_ECP_WINDOW_SIZE WSS_TEST_ECP_WINDOW
#endif
#undef MBEDTLS_HAVE_ASM
#undef MBEDTLS_FS_IO
#define QUECTEL_MBEDTLS_SUPPORT
#include "mbedtls/check_config.h"