mirror of
https://github.com/Hamlib/Hamlib
synced 2026-08-13 17:32:05 -04:00
Exercise command stream failures, hexadecimal byte formats, backend truncation and framing, metadata absence, and physical-device reply boundaries with focused valid and malformed cases. Ignore the generated test binaries and harness logs.
300 lines
7.9 KiB
C
300 lines
7.9 KiB
C
/*
|
|
* Hamlib command parser boundary tests
|
|
* Copyright (c) 2026 by Hamlib Team
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "hamlib/rig.h"
|
|
#include "hamlib/rig_state.h"
|
|
|
|
#include "rigctl_parse.h"
|
|
|
|
int lock_mode;
|
|
powerstat_t rig_powerstat = RIG_POWER_ON;
|
|
|
|
static char captured_description[sizeof(((channel_t *)0)->channel_desc)];
|
|
|
|
static int capture_channel(RIG *rig, vfo_t vfo, const channel_t *chan)
|
|
{
|
|
(void)rig;
|
|
(void)vfo;
|
|
memcpy(captured_description, chan->channel_desc,
|
|
sizeof(captured_description));
|
|
return RIG_OK;
|
|
}
|
|
|
|
static int parse_command(RIG *rig, FILE *input, char *argv[], int argc,
|
|
char send_cmd_term)
|
|
{
|
|
FILE *output = tmpfile();
|
|
int vfo_mode = 0;
|
|
int ext_resp = 0;
|
|
char resp_sep = '\n';
|
|
int ret;
|
|
|
|
if (output == NULL)
|
|
{
|
|
return -RIG_EINTERNAL;
|
|
}
|
|
|
|
optind = 1;
|
|
ret = rigctl_parse(rig, input, output, argv, argc, NULL, 0, 0,
|
|
&vfo_mode, send_cmd_term, &ext_resp, &resp_sep, 0);
|
|
fclose(output);
|
|
return ret;
|
|
}
|
|
|
|
static int parse_command_output(RIG *rig, FILE *input, char *argv[], int argc,
|
|
char send_cmd_term, unsigned char *output,
|
|
size_t output_size, size_t *output_length)
|
|
{
|
|
FILE *stream = tmpfile();
|
|
int vfo_mode = 0;
|
|
int ext_resp = 0;
|
|
char resp_sep = '\n';
|
|
int ret;
|
|
|
|
if (stream == NULL)
|
|
{
|
|
return -RIG_EINTERNAL;
|
|
}
|
|
|
|
optind = 1;
|
|
ret = rigctl_parse(rig, input, stream, argv, argc, NULL, 0, 0,
|
|
&vfo_mode, send_cmd_term, &ext_resp, &resp_sep, 0);
|
|
rewind(stream);
|
|
*output_length = fread(output, 1, output_size, stream);
|
|
fclose(stream);
|
|
return ret;
|
|
}
|
|
|
|
static int check_send_command(RIG *rig, const char *command,
|
|
const unsigned char *expected,
|
|
size_t expected_length)
|
|
{
|
|
unsigned char actual[64];
|
|
FILE *input = tmpfile();
|
|
char *argv[] = { "testctlparser", "w", (char *)command };
|
|
size_t actual_length;
|
|
int ret;
|
|
|
|
if (input == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ret = parse_command_output(rig, input, argv, 3, -1, actual,
|
|
sizeof(actual), &actual_length);
|
|
fclose(input);
|
|
|
|
if (ret != RIG_OK)
|
|
{
|
|
fprintf(stderr, "send command '%s': expected success, got %d\n",
|
|
command, ret);
|
|
return 1;
|
|
}
|
|
|
|
if (actual_length != expected_length
|
|
|| memcmp(actual, expected, expected_length) != 0)
|
|
{
|
|
fprintf(stderr, "send command '%s': unexpected decoded reply\n",
|
|
command);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_invalid_send_command(RIG *rig, const char *command)
|
|
{
|
|
FILE *input = tmpfile();
|
|
char *argv[] = { "testctlparser", "w", (char *)command };
|
|
int ret;
|
|
|
|
if (input == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ret = parse_command(rig, input, argv, 3, -1);
|
|
fclose(input);
|
|
|
|
if (ret != -RIG_EINVAL)
|
|
{
|
|
fprintf(stderr, "send command '%s': expected %d, got %d\n",
|
|
command, -RIG_EINVAL, ret);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_bad_input_stream(RIG *rig)
|
|
{
|
|
FILE *input = tmpfile();
|
|
FILE *output = tmpfile();
|
|
char *argv[] = { "testctlparser" };
|
|
int vfo_mode = 0;
|
|
int ext_resp = 0;
|
|
char resp_sep = '\n';
|
|
int ret;
|
|
|
|
if (input == NULL || output == NULL)
|
|
{
|
|
if (input != NULL) { fclose(input); }
|
|
|
|
if (output != NULL) { fclose(output); }
|
|
|
|
return 1;
|
|
}
|
|
|
|
close(fileno(input));
|
|
ret = rigctl_parse(rig, input, output, argv, 1, NULL, 1, 0,
|
|
&vfo_mode, 0, &ext_resp, &resp_sep, 0);
|
|
fclose(input);
|
|
fclose(output);
|
|
|
|
if (ret != RIGCTL_PARSE_ERROR)
|
|
{
|
|
fprintf(stderr, "bad input stream: expected %d, got %d\n",
|
|
RIGCTL_PARSE_ERROR, ret);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_description(RIG *rig, const char *input,
|
|
const char *expected)
|
|
{
|
|
FILE *fields = tmpfile();
|
|
char *argv[] = { "testctlparser", "H", "0" };
|
|
int ret;
|
|
|
|
if (fields == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
fputs(input, fields);
|
|
rewind(fields);
|
|
memset(captured_description, 0xa5, sizeof(captured_description));
|
|
ret = parse_command(rig, fields, argv, 3, 0);
|
|
fclose(fields);
|
|
|
|
if (ret != RIG_OK)
|
|
{
|
|
fprintf(stderr, "description '%s': expected success, got %d\n",
|
|
input, ret);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(captured_description, expected) != 0)
|
|
{
|
|
fprintf(stderr, "description '%s': expected '%s', got '%s'\n",
|
|
input, expected, captured_description);
|
|
return 1;
|
|
}
|
|
|
|
if (captured_description[sizeof(captured_description) - 1] != '\0')
|
|
{
|
|
fprintf(stderr, "description '%s' was not terminated\n", input);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
static const char maximum[] = "12345678901234567890123456789";
|
|
static const char thirty[] = "123456789012345678901234567890";
|
|
static const char thirty_one[] = "1234567890123456789012345678901";
|
|
static const unsigned char binary_reply[] = "\\0x00\\0xFF 2\n";
|
|
static const unsigned char control_reply[] = { 0x01, 0x02, '\n', '\n' };
|
|
struct rig_caps caps;
|
|
struct rig_state *state;
|
|
RIG *rig;
|
|
FILE *empty_input;
|
|
char *empty_command[] = { "testctlparser", "w", "" };
|
|
int ret;
|
|
|
|
rig_load_backend("dummy");
|
|
rig = rig_init(RIG_MODEL_DUMMY);
|
|
|
|
if (rig == NULL)
|
|
{
|
|
fprintf(stderr, "unable to initialize dummy rig\n");
|
|
return 1;
|
|
}
|
|
|
|
caps = *rig->caps;
|
|
memset(caps.chan_list, 0, sizeof(caps.chan_list));
|
|
caps.chan_list[0].startc = 0;
|
|
caps.chan_list[0].endc = 0;
|
|
caps.chan_list[0].type = RIG_MTYPE_MEM;
|
|
caps.chan_list[0].mem_caps.channel_desc = 1;
|
|
caps.set_channel = capture_channel;
|
|
rig->caps = ∩︀
|
|
state = HAMLIB_STATE(rig);
|
|
memcpy(state->chan_list, caps.chan_list, sizeof(state->chan_list));
|
|
rigctl_parse_init();
|
|
|
|
if (check_description(rig, maximum, maximum) != 0
|
|
|| check_description(rig, thirty, maximum) != 0
|
|
|| check_description(rig, thirty_one, maximum) != 0)
|
|
{
|
|
rig_cleanup(rig);
|
|
return 1;
|
|
}
|
|
|
|
empty_input = tmpfile();
|
|
|
|
if (empty_input == NULL)
|
|
{
|
|
rig_cleanup(rig);
|
|
return 1;
|
|
}
|
|
|
|
ret = parse_command(rig, empty_input, empty_command, 3, -1);
|
|
fclose(empty_input);
|
|
|
|
if (ret != -RIG_EINVAL)
|
|
{
|
|
fprintf(stderr, "empty send command: expected %d, got %d\n",
|
|
-RIG_EINVAL, ret);
|
|
rig_cleanup(rig);
|
|
return 1;
|
|
}
|
|
|
|
if (check_send_command(rig, "x00 xff", binary_reply,
|
|
sizeof(binary_reply) - 1) != 0
|
|
|| check_send_command(rig, "\\0x00\\0xff", binary_reply,
|
|
sizeof(binary_reply) - 1) != 0
|
|
|| check_send_command(rig, "x00ff", binary_reply,
|
|
sizeof(binary_reply) - 1) != 0
|
|
|| check_send_command(rig, "x00xff", binary_reply,
|
|
sizeof(binary_reply) - 1) != 0
|
|
|| check_send_command(rig, "x01 x02", control_reply,
|
|
sizeof(control_reply)) != 0
|
|
|| check_invalid_send_command(rig, "x0") != 0
|
|
|| check_invalid_send_command(rig, "x000") != 0
|
|
|| check_invalid_send_command(rig, "x00 nope") != 0
|
|
|| check_invalid_send_command(rig, "\\0x00\\0xgg") != 0
|
|
|| check_bad_input_stream(rig) != 0)
|
|
{
|
|
rig_cleanup(rig);
|
|
return 1;
|
|
}
|
|
|
|
rig_cleanup(rig);
|
|
return 0;
|
|
}
|