hamlib/tests/testctlparser.c

301 lines
7.9 KiB
C
Raw Permalink Normal View History

/*
* 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 = &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;
}