diff --git a/rigs/guohetec/guohetec.c b/rigs/guohetec/guohetec.c index 3a01e7c2b..004d722f7 100644 --- a/rigs/guohetec/guohetec.c +++ b/rigs/guohetec/guohetec.c @@ -19,7 +19,6 @@ * */ -#include #include "hamlib/rig.h" #include "hamlib/port.h" #include "iofunc.h" @@ -27,24 +26,23 @@ #include "riglist.h" #include "guohetec.h" -// Common response validation function implementations -int validate_packet_header(const unsigned char *reply, const char *func_name) +enum +{ + GUOHE_FRAME_HEADER_LENGTH = 5, + GUOHE_STATUS_MIN_PACKET_LENGTH = 15, + GUOHE_STATUS_COMMAND = 0x0B +}; + +static int validate_packet_header(const unsigned char *reply, + const char *func_name) { if (reply[0] != 0xA5 || reply[1] != 0xA5 || reply[2] != 0xA5 || reply[3] != 0xA5) { rig_debug(RIG_DEBUG_ERR, "%s: Invalid packet header, using cached values\n", func_name); - return -1; + return -RIG_EPROTO; } - return 0; -} -int validate_data_length(const unsigned char *reply, int reply_size, const char *func_name) -{ - if (reply[4] == 0 || reply[4] > reply_size - 5) { - rig_debug(RIG_DEBUG_ERR, "%s: Invalid data length %d, using cached values\n", func_name, reply[4]); - return -1; - } - return 0; + return RIG_OK; } // CRC16/CCITT-FALSE @@ -157,154 +155,156 @@ uint16_t CRC16Check(const unsigned char *buf, int len) return result; } -// Common response validation functions - -/** - * Read rig response with validation - * @param rig RIG structure - * @param reply Reply buffer - * @param reply_size Size of reply buffer - * @param func_name Function name for debug messages - * @return 0 on success, -1 on error - */ -int read_rig_response(RIG *rig, unsigned char *reply, int reply_size, - const char *func_name) +int guohetec_read_response(RIG *rig, unsigned char *reply, size_t reply_size, + const char *func_name) { hamlib_port_t *rp = RIGPORT(rig); int ret; - - // Read header - ret = read_block(rp, reply, 5); - if (ret < 0) { + + if (reply_size < GUOHE_FRAME_HEADER_LENGTH) + { + return -RIG_EINVAL; + } + + ret = read_block(rp, reply, GUOHE_FRAME_HEADER_LENGTH); + + if (ret < 0) + { rig_debug(RIG_DEBUG_ERR, "%s: Failed to read header, using cached values\n", func_name); - return -1; + return ret; } - - // Validate data length - if (reply[4] == 0 || reply[4] > reply_size - 5) { + + if (validate_packet_header(reply, func_name) < 0) + { + return -RIG_EPROTO; + } + + if (reply[4] == 0 || reply[4] > reply_size - GUOHE_FRAME_HEADER_LENGTH) + { rig_debug(RIG_DEBUG_ERR, "%s: Invalid data length %d, using cached values\n", func_name, reply[4]); - return -1; + return -RIG_EPROTO; } - - // Read data section - ret = read_block(rp, &reply[5], reply[4]); - if (ret < 0) { + + ret = read_block(rp, &reply[GUOHE_FRAME_HEADER_LENGTH], reply[4]); + + if (ret < 0) + { rig_debug(RIG_DEBUG_ERR, "%s: Failed to read data, using cached values\n", func_name); - return -1; + return ret; } - - // Validate response length matches expected - if (ret != reply[4]) { - rig_debug(RIG_DEBUG_ERR, "%s: Data read mismatch: expected %d, got %d, using cached values\n", - func_name, reply[4], ret); - return -1; + + if (ret != reply[4]) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Data read mismatch: expected %d, got %d, using cached values\n", + func_name, reply[4], ret); + return -RIG_EPROTO; } - - return 0; + + return GUOHE_FRAME_HEADER_LENGTH + ret; } -/** - * Validate basic rig response - * @param rig RIG structure - * @param reply Reply buffer - * @param reply_size Size of reply buffer - * @param func_name Function name for debug messages - * @return 0 on success, -1 on error - */ -int validate_rig_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name) +int guohetec_decode_status(const unsigned char *reply, size_t reply_size, + struct guohetec_status *status, + const char *func_name) { - // Validate packet header - if (validate_packet_header(reply, func_name) < 0) { - return -1; + size_t expected_size; + size_t crc_offset; + uint16_t received_crc; + uint16_t calculated_crc; + + if (reply == NULL || status == NULL || func_name == NULL) + { + return -RIG_EINVAL; } - - // Validate data length - if (validate_data_length(reply, reply_size, func_name) < 0) { - return -1; + + if (reply_size < GUOHE_FRAME_HEADER_LENGTH || + validate_packet_header(reply, func_name) < 0) + { + return -RIG_EPROTO; } - - return 0; + + if (reply[4] < GUOHE_STATUS_MIN_PACKET_LENGTH) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Status response too short: %u, using cached values\n", + func_name, reply[4]); + return -RIG_EPROTO; + } + + expected_size = GUOHE_FRAME_HEADER_LENGTH + reply[4]; + + if (reply_size != expected_size) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Status response length mismatch: expected %zu, got %zu, using cached values\n", + func_name, expected_size, reply_size); + return -RIG_EPROTO; + } + + if (reply[5] != GUOHE_STATUS_COMMAND) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Unexpected status response command: 0x%02X, using cached values\n", + func_name, reply[5]); + return -RIG_EPROTO; + } + + crc_offset = expected_size - 2; + received_crc = ((uint16_t)reply[crc_offset] << 8) | + reply[crc_offset + 1]; + calculated_crc = CRC16Check(&reply[4], reply[4] - 1); + + if (received_crc != calculated_crc) + { + rig_debug(RIG_DEBUG_ERR, + "%s: CRC check failed (received: %04X, calculated: %04X), using cached values\n", + func_name, received_crc, calculated_crc); + return -RIG_EPROTO; + } + + status->ptt = reply[6]; + status->mode_a = reply[7]; + status->mode_b = reply[8]; + status->freq_a = (uint32_t)from_be(&reply[9], 4); + status->freq_b = (uint32_t)from_be(&reply[13], 4); + status->vfo = reply[17] == 1 ? RIG_VFO_B : RIG_VFO_A; + + return RIG_OK; } -/** - * Validate frequency response with CRC check - * @param rig RIG structure - * @param reply Reply buffer - * @param reply_size Size of reply buffer - * @param func_name Function name for debug messages - * @return 0 on success, -1 on error - */ -int validate_freq_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name) +int guohetec_get_status(RIG *rig, struct guohetec_status *status, + const char *func_name) { - // Basic validation - if (validate_rig_response(rig, reply, reply_size, func_name) < 0) { - return -1; - } - - // Validate buffer boundaries for CRC - int expected_total_length = 5 + reply[4] + 2; // header(5) + data_length + CRC(2) - if (expected_total_length > reply_size) { - rig_debug(RIG_DEBUG_ERR, "%s: Response too large for buffer: %d > %d, using cached values\n", - func_name, expected_total_length, reply_size); - return -1; - } - - // CRC check - uint16_t recv_crc = (reply[31] << 8) | reply[32]; // Last 2 bytes are CRC - uint16_t calc_crc = CRC16Check(&reply[4], 27); - if (recv_crc != calc_crc) { - rig_debug(RIG_DEBUG_ERR, "%s: CRC check failed (received: %04X, calculated: %04X), using cached values\n", - func_name, recv_crc, calc_crc); - return -1; - } - - // Validate frequency field offset - int freq_b_offset = 13; // VFOB frequency starting position - if (freq_b_offset + 3 >= expected_total_length - 2) { // -2 for CRC - rig_debug(RIG_DEBUG_ERR, "%s: Frequency field offset out of bounds, using cached values\n", func_name); - return -1; - } - - return 0; -} + unsigned char command[GUOHE_STATUS_CMD_LENGTH] = { + 0xA5, 0xA5, 0xA5, 0xA5, 0x03, GUOHE_STATUS_COMMAND, 0x00, 0x00 + }; + unsigned char reply[GUOHE_MAX_FRAME_LENGTH]; + hamlib_port_t *rp = RIGPORT(rig); + uint16_t crc; + int ret; -/** - * Validate mode response with bounds checking - * @param rig RIG structure - * @param reply Reply buffer - * @param reply_size Size of reply buffer - * @param func_name Function name for debug messages - * @param min_length Minimum required data length - * @return 0 on success, -1 on error - */ -int validate_mode_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name, int min_length) -{ - // Basic validation - if (validate_rig_response(rig, reply, reply_size, func_name) < 0) { - return -1; + crc = CRC16Check(&command[4], 2); + command[6] = crc >> 8; + command[7] = crc & 0xFF; + + rig_flush(rp); + ret = write_block(rp, command, sizeof(command)); + + if (ret != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: Failed to write status request\n", func_name); + return ret; } - - // Validate minimum length for mode data - if (reply[4] < min_length) { - rig_debug(RIG_DEBUG_ERR, "%s: Response too short for mode data, using cached values\n", func_name); - return -1; + + ret = guohetec_read_response(rig, reply, sizeof(reply), func_name); + + if (ret < 0) + { + return ret; } - - // Validate mode field indices are within bounds - if (reply[7] >= GUOHE_MODE_TABLE_MAX) { - rig_debug(RIG_DEBUG_ERR, "%s: Invalid mode A index %d, using cached values\n", func_name, reply[7]); - return -1; - } - - if (reply[8] >= GUOHE_MODE_TABLE_MAX) { - rig_debug(RIG_DEBUG_ERR, "%s: Invalid mode B index %d, using cached values\n", func_name, reply[8]); - return -1; - } - - return 0; + + return guohetec_decode_status(reply, (size_t)ret, status, func_name); } // Initialization function @@ -321,7 +321,7 @@ DECLARE_INITRIG_BACKEND(guohetec) { // Probe function implementation DECLARE_PROBERIG_BACKEND(guohetec) { - uint8_t cmd[PMR171_CMD_LENGTH] = { + uint8_t cmd[GUOHE_STATUS_CMD_LENGTH] = { 0xA5, 0xA5, 0xA5, 0xA5, 0x03, 0x0B, @@ -334,7 +334,8 @@ DECLARE_PROBERIG_BACKEND(guohetec) { const int rates[] = {9600, 19200, 38400, 57600, 115200, 0}; for (int i = 0; rates[i]; i++) { - uint8_t reply[PMR171_REPLY_LENGTH]; + uint8_t reply[GUOHE_MAX_FRAME_LENGTH]; + struct guohetec_status status; port->parm.serial.rate = rates[i]; port->timeout = 500; @@ -345,39 +346,35 @@ DECLARE_PROBERIG_BACKEND(guohetec) { rig_flush(port); - int retval = write_block(port, cmd, PMR171_CMD_LENGTH); + int retval = write_block(port, cmd, GUOHE_STATUS_CMD_LENGTH); if (retval != RIG_OK) { continue; } - retval = read_block(port, reply, 6); - if (retval < 6 || memcmp(reply, "\xA5\xA5\xA5\xA5", 4) != 0) { + retval = read_block(port, reply, GUOHE_FRAME_HEADER_LENGTH); + if (retval < GUOHE_FRAME_HEADER_LENGTH || + validate_packet_header(reply, __func__) < 0) { continue; } uint8_t pkt_len = reply[4]; - if (pkt_len < 2 || pkt_len > (PMR171_REPLY_LENGTH - 5)) { + if (pkt_len == 0 || + pkt_len > (GUOHE_MAX_FRAME_LENGTH - GUOHE_FRAME_HEADER_LENGTH)) { continue; } - retval = read_block(port, &reply[6], pkt_len - 1); - if (retval < (pkt_len - 1)) { + retval = read_block(port, &reply[GUOHE_FRAME_HEADER_LENGTH], pkt_len); + if (retval != pkt_len) { continue; } - - uint16_t recv_crc = (reply[pkt_len + 4] << 8) | reply[pkt_len + 5]; - uint16_t calc_crc = CRC16Check(&reply[4], pkt_len + 1); - - if (recv_crc != calc_crc) { + + if (guohetec_decode_status(reply, + GUOHE_FRAME_HEADER_LENGTH + pkt_len, + &status, __func__) < 0) { continue; } - - if (reply[5] != 0x0B) { - continue; - } - - uint32_t freq = (reply[9] << 24) | (reply[10] << 16) | - (reply[11] << 8) | reply[12]; + + uint32_t freq = status.freq_a; if (freq < 100000 || freq > 470000000) { continue; } @@ -397,4 +394,3 @@ DECLARE_PROBERIG_BACKEND(guohetec) { return RIG_MODEL_NONE; } - diff --git a/rigs/guohetec/guohetec.h b/rigs/guohetec/guohetec.h index e19f67cbd..3921e2755 100644 --- a/rigs/guohetec/guohetec.h +++ b/rigs/guohetec/guohetec.h @@ -25,8 +25,8 @@ #include "hamlib/rig.h" -#define PMR171_CMD_LENGTH 8 -#define PMR171_REPLY_LENGTH 24 +#define GUOHE_STATUS_CMD_LENGTH 8 +#define GUOHE_MAX_FRAME_LENGTH 260 #define GUOHE_MODE_TABLE_MAX 8 @@ -52,43 +52,31 @@ return RIG_OK; \ } while(0) -// Common response validation function declarations -int validate_packet_header(const unsigned char *reply, const char *func_name); -int validate_data_length(const unsigned char *reply, int reply_size, const char *func_name); - -// Keep the macro for backward compatibility -#define VALIDATE_PACKET_HEADER(reply, func_name) validate_packet_header(reply, func_name) -#define VALIDATE_DATA_LENGTH(reply, reply_size, func_name) validate_data_length(reply, reply_size, func_name) - -#define VALIDATE_READ_RESULT(ret, expected, func_name) do { \ - if (ret < 0) { \ - rig_debug(RIG_DEBUG_ERR, "%s: Failed to read data, using cached values\n", func_name); \ - return -1; \ - } \ - if (ret != expected) { \ - rig_debug(RIG_DEBUG_ERR, "%s: Data read mismatch: expected %d, got %d, using cached values\n", \ - func_name, expected, ret); \ - return -1; \ - } \ -} while(0) - extern struct rig_caps pmr171_caps; extern struct rig_caps q900_caps; +struct guohetec_status +{ + unsigned char ptt; + unsigned char mode_a; + unsigned char mode_b; + uint32_t freq_a; + uint32_t freq_b; + vfo_t vfo; +}; + uint16_t CRC16Check(const unsigned char *buf, int len); rmode_t guohe2rmode(unsigned char mode, const rmode_t mode_table[]); unsigned char rmode2guohe(rmode_t mode, const rmode_t mode_table[]); unsigned char *to_be(unsigned char data[], unsigned long long freq, unsigned int byte_len); unsigned long long from_be(const unsigned char data[],unsigned int byte_len); -// Common response validation functions -int validate_rig_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name); -int read_rig_response(RIG *rig, unsigned char *reply, int reply_size, - const char *func_name); -int validate_freq_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name); -int validate_mode_response(RIG *rig, const unsigned char *reply, int reply_size, - const char *func_name, int min_length); +int guohetec_decode_status(const unsigned char *reply, size_t reply_size, + struct guohetec_status *status, + const char *func_name); +int guohetec_read_response(RIG *rig, unsigned char *reply, size_t reply_size, + const char *func_name); +int guohetec_get_status(RIG *rig, struct guohetec_status *status, + const char *func_name); #endif // _guohetec_H_ diff --git a/rigs/guohetec/pmr171.c b/rigs/guohetec/pmr171.c index dea33fe13..e12c8bb14 100644 --- a/rigs/guohetec/pmr171.c +++ b/rigs/guohetec/pmr171.c @@ -355,72 +355,25 @@ static int pmr171_open(RIG *rig) return RIG_OK; } - static int pmr171_send_cmd1(RIG *rig, unsigned char cmd, unsigned char *reply) - { - hamlib_port_t *rp = RIGPORT(rig); - rig_debug(RIG_DEBUG_VERBOSE, "%s: called\n", __func__); - unsigned char buf[8] = { 0xa5, 0xa5, 0xa5, 0xa5, 0x03, 0x00, 0x00, 0x00 }; - - buf[5] = cmd; - unsigned int crc = CRC16Check(&buf[4], 2); - buf[6] = crc >> 8; - buf[7] = crc & 0xff; - rig_flush(rp); - write_block(rp, buf, 8); - return RIG_OK; - } - - /* ---------------------------------------------------------------------- */ static int pmr171_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { - // Send status query command (0x0B) - unsigned char cmd[8] = { - 0xA5, 0xA5, 0xA5, 0xA5, - 0x03, - 0x0B, - 0x00, 0x00 - }; - - // Calculate CRC and fill - uint16_t crc = CRC16Check(&cmd[4], 2); - cmd[6] = crc >> 8; - cmd[7] = crc & 0xFF; + struct guohetec_status status; + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[40]; - pmr171_send(rig, cmd, sizeof(cmd), reply, sizeof(reply)); - - // Validate response using common function - if (validate_freq_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_FREQ(rig, vfo, freq); - } - - // Parse frequency (big-endian) - int freq_a_offset = 9; // VFOA frequency starting position - int freq_b_offset = 13; // VFOB frequency starting position - - uint32_t freq_a = (reply[freq_a_offset] << 24) | - (reply[freq_a_offset+1] << 16) | - (reply[freq_a_offset+2] << 8) | - reply[freq_a_offset+3]; - - uint32_t freq_b = (reply[freq_b_offset] << 24) | - (reply[freq_b_offset+1] << 16) | - (reply[freq_b_offset+2] << 8) | - reply[freq_b_offset+3]; - - // Update cache - CACHE(rig)->freqMainA = (freq_t)freq_a; - CACHE(rig)->freqMainB = (freq_t)freq_b; - - // Return requested VFO frequency - *freq = (vfo == RIG_VFO_A) ? CACHE(rig)->freqMainA : CACHE(rig)->freqMainB; - - rig_debug(RIG_DEBUG_VERBOSE, "%s: Successfully got VFOA=%.0f Hz, VFOB=%.0f Hz\n", - __func__, CACHE(rig)->freqMainA, CACHE(rig)->freqMainB); + RETURN_CACHED_FREQ(rig, vfo, freq); } + + CACHE(rig)->freqMainA = (freq_t)status.freq_a; + CACHE(rig)->freqMainB = (freq_t)status.freq_b; + *freq = vfo == RIG_VFO_A ? CACHE(rig)->freqMainA : CACHE(rig)->freqMainB; + + rig_debug(RIG_DEBUG_VERBOSE, + "%s: Successfully got VFOA=%.0f Hz, VFOB=%.0f Hz\n", + __func__, CACHE(rig)->freqMainA, CACHE(rig)->freqMainB); + return RIG_OK; } @@ -428,25 +381,27 @@ static int pmr171_open(RIG *rig) { struct rig_cache *cachep = CACHE(rig); const pmr171_data_t *p = (pmr171_data_t *) STATE(rig)->priv; + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[40]; - // Get latest status from hardware - pmr171_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); - } - // Validate mode response using common function - if (validate_mode_response(rig, reply, sizeof(reply), __func__, 5) < 0) { - RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); - } - // Update cache - cachep->modeMainA = guohe2rmode(reply[7], pmr171_modes); - cachep->modeMainB = guohe2rmode(reply[8], pmr171_modes); - // Return requested mode - *mode = (vfo == RIG_VFO_A) ? cachep->modeMainA : cachep->modeMainB; - *width = p->filterBW; + RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); } + + if (status.mode_a >= GUOHE_MODE_TABLE_MAX || + status.mode_b >= GUOHE_MODE_TABLE_MAX) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Invalid mode indices %u/%u, using cached values\n", + __func__, status.mode_a, status.mode_b); + RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); + } + + cachep->modeMainA = guohe2rmode(status.mode_a, pmr171_modes); + cachep->modeMainB = guohe2rmode(status.mode_b, pmr171_modes); + *mode = vfo == RIG_VFO_A ? cachep->modeMainA : cachep->modeMainB; + *width = p->filterBW; + return RIG_OK; } @@ -463,22 +418,15 @@ static int pmr171_open(RIG *rig) static int pmr171_get_vfo(RIG *rig, vfo_t *vfo) { + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[40]; - // Send status sync command to get current VFO state - pmr171_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_VFO(rig, vfo); - } - // Validate VFO status field index won't overflow - if (reply[4] < 13) { // Need at least 13 bytes to access reply[17] - rig_debug(RIG_DEBUG_ERR, "%s: Response too short for VFO data, using cached values\n", __func__); - RETURN_CACHED_VFO(rig, vfo); - } - // According to protocol doc, reply[17] is A/B frequency status - *vfo = (reply[17] == 1) ? RIG_VFO_B : RIG_VFO_A; + RETURN_CACHED_VFO(rig, vfo); } + + *vfo = status.vfo; + return RIG_OK; } @@ -486,22 +434,16 @@ static int pmr171_open(RIG *rig) static int pmr171_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) { struct rig_cache *cachep = CACHE(rig); + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[40]; - pmr171_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_PTT(rig, ptt, cachep); - } - // Validate PTT status field index won't overflow - if (reply[4] < 2) { // Need at least 2 bytes to access reply[6] - rig_debug(RIG_DEBUG_ERR, "%s: Response too short for PTT data, using cached values\n", __func__); - RETURN_CACHED_PTT(rig, ptt, cachep); - } - // Get PTT status - cachep->ptt = reply[6]; - *ptt = cachep->ptt; + RETURN_CACHED_PTT(rig, ptt, cachep); } + + cachep->ptt = status.ptt; + *ptt = cachep->ptt; + return RIG_OK; } @@ -551,7 +493,7 @@ static int pmr171_open(RIG *rig) { unsigned char reply[40]; // Use common response reading function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { + if (guohetec_read_response(rig, reply, sizeof(reply), __func__) < 0) { return RIG_OK; // Return OK to use cached values } } @@ -658,7 +600,7 @@ static int pmr171_open(RIG *rig) write_block(rp, cmd, 10); // Use common response reading function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { + if (guohetec_read_response(rig, reply, sizeof(reply), __func__) < 0) { // Update cache with requested mode even if response failed if (vfo == RIG_VFO_B) { diff --git a/rigs/guohetec/q900.c b/rigs/guohetec/q900.c index 408fd497f..ed56e7ccb 100644 --- a/rigs/guohetec/q900.c +++ b/rigs/guohetec/q900.c @@ -354,63 +354,25 @@ static int q900_open(RIG *rig) return RIG_OK; } - static int q900_send_cmd1(RIG *rig, unsigned char cmd, unsigned char *reply) - { - hamlib_port_t *rp = RIGPORT(rig); - rig_debug(RIG_DEBUG_VERBOSE, "%s: called\n", __func__); - unsigned char buf[8] = { 0xa5, 0xa5, 0xa5, 0xa5, 0x03, 0x00, 0x00, 0x00 }; - - buf[5] = cmd; - unsigned int crc = CRC16Check(&buf[4], 2); - buf[6] = crc >> 8; - buf[7] = crc & 0xff; - rig_flush(rp); - write_block(rp, buf, 8); - return RIG_OK; - } - - /* ---------------------------------------------------------------------- */ static int q900_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { - unsigned char cmd[8] = { - 0xA5, 0xA5, 0xA5, 0xA5, - 0x03, - 0x0B, - 0x00, 0x00 - }; - - uint16_t crc = CRC16Check(&cmd[4], 2); - cmd[6] = crc >> 8; - cmd[7] = crc & 0xFF; + struct guohetec_status status; + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[40]; - q900_send(rig, cmd, sizeof(cmd), reply, sizeof(reply)); - // Validate response using common function - if (validate_freq_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_FREQ(rig, vfo, freq); - } - // Parse frequency (big-endian) - int freq_a_offset = 9; // VFOA frequency starting position - int freq_b_offset = 13; // VFOB frequency starting position - uint32_t freq_a = (reply[freq_a_offset] << 24) | - (reply[freq_a_offset+1] << 16) | - (reply[freq_a_offset+2] << 8) | - reply[freq_a_offset+3]; - uint32_t freq_b = (reply[freq_b_offset] << 24) | - (reply[freq_b_offset+1] << 16) | - (reply[freq_b_offset+2] << 8) | - reply[freq_b_offset+3]; - // Update cache - CACHE(rig)->freqMainA = (freq_t)freq_a; - CACHE(rig)->freqMainB = (freq_t)freq_b; - // Return requested VFO frequency - *freq = (vfo == RIG_VFO_A) ? CACHE(rig)->freqMainA : CACHE(rig)->freqMainB; - rig_debug(RIG_DEBUG_VERBOSE, "%s: Successfully got VFOA=%.0f Hz, VFOB=%.0f Hz\n", - __func__, CACHE(rig)->freqMainA, CACHE(rig)->freqMainB); + RETURN_CACHED_FREQ(rig, vfo, freq); } + + CACHE(rig)->freqMainA = (freq_t)status.freq_a; + CACHE(rig)->freqMainB = (freq_t)status.freq_b; + *freq = vfo == RIG_VFO_A ? CACHE(rig)->freqMainA : CACHE(rig)->freqMainB; + + rig_debug(RIG_DEBUG_VERBOSE, + "%s: Successfully got VFOA=%.0f Hz, VFOB=%.0f Hz\n", + __func__, CACHE(rig)->freqMainA, CACHE(rig)->freqMainB); + return RIG_OK; } @@ -418,25 +380,27 @@ static int q900_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { struct rig_cache *cachep = CACHE(rig); const q900_data_t *p = (q900_data_t *) STATE(rig)->priv; + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[255]; - // Get latest status from hardware - q900_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); - } - // Validate mode response using common function - if (validate_mode_response(rig, reply, sizeof(reply), __func__, 5) < 0) { - RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); - } - // Update cache - cachep->modeMainA = guohe2rmode(reply[7], q900_modes); - cachep->modeMainB = guohe2rmode(reply[8], q900_modes); - // Return requested mode - *mode = (vfo == RIG_VFO_A) ? cachep->modeMainA : cachep->modeMainB; - *width = p->filterBW; + RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); } + + if (status.mode_a >= GUOHE_MODE_TABLE_MAX || + status.mode_b >= GUOHE_MODE_TABLE_MAX) + { + rig_debug(RIG_DEBUG_ERR, + "%s: Invalid mode indices %u/%u, using cached values\n", + __func__, status.mode_a, status.mode_b); + RETURN_CACHED_MODE(rig, vfo, mode, width, cachep, p); + } + + cachep->modeMainA = guohe2rmode(status.mode_a, q900_modes); + cachep->modeMainB = guohe2rmode(status.mode_b, q900_modes); + *mode = vfo == RIG_VFO_A ? cachep->modeMainA : cachep->modeMainB; + *width = p->filterBW; + return RIG_OK; } @@ -453,22 +417,15 @@ static int q900_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) static int q900_get_vfo(RIG *rig, vfo_t *vfo) { + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[255]; - // Send status sync command to get current VFO state - q900_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_VFO(rig, vfo); - } - // Validate VFO status field index won't overflow - if (reply[4] < 13) { // Need at least 13 bytes to access reply[17] - rig_debug(RIG_DEBUG_ERR, "%s: Response too short for VFO data, using cached values\n", __func__); - RETURN_CACHED_VFO(rig, vfo); - } - // According to protocol doc, reply[17] is A/B frequency status - *vfo = (reply[17] == 1) ? RIG_VFO_B : RIG_VFO_A; + RETURN_CACHED_VFO(rig, vfo); } + + *vfo = status.vfo; + return RIG_OK; } @@ -476,22 +433,16 @@ static int q900_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) static int q900_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) { struct rig_cache *cachep = CACHE(rig); + struct guohetec_status status; + + if (guohetec_get_status(rig, &status, __func__) < 0) { - unsigned char reply[255]; - q900_send_cmd1(rig, 0x0b, 0); - // Read and validate response using common function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { - RETURN_CACHED_PTT(rig, ptt, cachep); - } - // Validate PTT status field index won't overflow - if (reply[4] < 2) { // Need at least 2 bytes to access reply[6] - rig_debug(RIG_DEBUG_ERR, "%s: Response too short for PTT data, using cached values\n", __func__); - RETURN_CACHED_PTT(rig, ptt, cachep); - } - // Get PTT status - cachep->ptt = reply[6]; - *ptt = cachep->ptt; + RETURN_CACHED_PTT(rig, ptt, cachep); } + + cachep->ptt = status.ptt; + *ptt = cachep->ptt; + return RIG_OK; } @@ -544,7 +495,7 @@ static int q900_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) { unsigned char reply[256]; // Use common response reading function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { + if (guohetec_read_response(rig, reply, sizeof(reply), __func__) < 0) { return RIG_OK; // Return OK to use cached values } } @@ -648,7 +599,7 @@ static int q900_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) write_block(rp, cmd, 10); // Use common response reading function - if (read_rig_response(rig, reply, sizeof(reply), __func__) < 0) { + if (guohetec_read_response(rig, reply, sizeof(reply), __func__) < 0) { // Update cache with requested mode even if response failed if (vfo == RIG_VFO_B) { @@ -790,4 +741,3 @@ static int q900_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt) } /* ---------------------------------------------------------------------- */ - diff --git a/tests/Makefile.am b/tests/Makefile.am index 647af4d0e..f7770a095 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,6 +24,7 @@ DIRECT_TESTS = \ testftx1parsers \ testgeministatus \ testgs100 \ + testguohetec \ testicomts GENERATED_TEST_WRAPPERS = \ @@ -102,9 +103,11 @@ testdebug_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) testctlparser_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) testgeministatus_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) testgs100_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) +testguohetec_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) testicomts_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/rigs/icom testgeministatus_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/amplifiers/gemini testftx1parsers_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/rigs/yaesu/ftx1 +testguohetec_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/rigs/guohetec if TESTS_HAVE_LIBUSB rigtestlibusb_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) $(LIBUSB_CFLAGS) endif @@ -130,6 +133,7 @@ testicomts_LDADD = $(top_builddir)/rigs/icom/libhamlib-icom.la $(LDADD) testgeministatus_LDADD = $(PTHREAD_LIBS) $(top_builddir)/amplifiers/gemini/libhamlib-gemini.la $(LDADD) testgs100_LDADD = $(PTHREAD_LIBS) $(top_builddir)/rigs/gomspace/libhamlib-gomspace.la $(LDADD) testftx1parsers_LDADD = $(top_builddir)/rigs/yaesu/libhamlib-yaesu.la $(LDADD) +testguohetec_LDADD = $(PTHREAD_LIBS) $(top_builddir)/rigs/guohetec/libhamlib-guohetec.la $(LDADD) if TESTS_HAVE_LIBUSB rigtestlibusb_LDADD = $(LIBUSB_LIBS) endif @@ -208,6 +212,9 @@ $(top_builddir)/amplifiers/gemini/libhamlib-gemini.la: $(top_builddir)/rigs/gomspace/libhamlib-gomspace.la: $(MAKE) -C $(top_builddir)/rigs/gomspace/ libhamlib-gomspace.la +$(top_builddir)/rigs/guohetec/libhamlib-guohetec.la: + $(MAKE) -C $(top_builddir)/rigs/guohetec/ libhamlib-guohetec.la + $(top_builddir)/rigs/yaesu/libhamlib-yaesu.la: $(MAKE) -C $(top_builddir)/rigs/yaesu/ libhamlib-yaesu.la diff --git a/tests/testguohetec.c b/tests/testguohetec.c new file mode 100644 index 000000000..0c9348720 --- /dev/null +++ b/tests/testguohetec.c @@ -0,0 +1,369 @@ +/* + * Hamlib GUOHETEC status framing 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "hamlib/rig.h" +#include "hamlib/port.h" +#include "hamlib/rig_state.h" +#include "guohetec.h" + +#if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_SOCKET_H) +#include +#include +#include +#define TEST_GUOHE_TRANSACTIONS 1 +#endif + +static const unsigned char captured_firmware_3_5_status[] = { + 0xA5, 0xA5, 0xA5, 0xA5, 0x1B, 0x0B, 0x00, 0x00, + 0x00, 0x01, 0x41, 0xA7, 0xC0, 0x01, 0x41, 0x90, + 0x50, 0x00, 0x00, 0x3C, 0x3C, 0x32, 0x00, 0xA6, + 0x05, 0x2C, 0x0C, 0x21, 0x01, 0x00, 0x14, 0xB0 +}; + +static const unsigned char documented_status[] = { + 0xA5, 0xA5, 0xA5, 0xA5, 0x1B, 0x0B, 0x01, 0x05, + 0x04, 0x08, 0xAC, 0x27, 0x60, 0x19, 0xED, 0x92, + 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xD4 +}; + +static int expect_valid_status(const char *name, const unsigned char *frame, + size_t frame_size, uint32_t freq_a, + uint32_t freq_b, unsigned char ptt, + unsigned char mode_a, unsigned char mode_b, + vfo_t vfo) +{ + struct guohetec_status status; + int ret = guohetec_decode_status(frame, frame_size, &status, name); + + if (ret != RIG_OK || status.freq_a != freq_a || status.freq_b != freq_b || + status.ptt != ptt || status.mode_a != mode_a || + status.mode_b != mode_b || status.vfo != vfo) + { + fprintf(stderr, + "%s: unexpected decode result %d/%u/%u/%u/%u/%u/%d\n", + name, ret, status.freq_a, status.freq_b, status.ptt, + status.mode_a, status.mode_b, status.vfo); + return 1; + } + + return 0; +} + +static int expect_invalid_status(const char *name, const unsigned char *frame, + size_t frame_size) +{ + struct guohetec_status status; + + if (guohetec_decode_status(frame, frame_size, &status, name) >= 0) + { + fprintf(stderr, "%s: malformed status response was accepted\n", name); + return 1; + } + + return 0; +} + +static int test_status_decoder(void) +{ + unsigned char invalid[sizeof(captured_firmware_3_5_status)]; + static const unsigned char short_status[] = { + 0xA5, 0xA5, 0xA5, 0xA5, 0x03, 0x0B, 0x00, 0x00 + }; + + if (expect_valid_status("firmware-3.5", captured_firmware_3_5_status, + sizeof(captured_firmware_3_5_status), + 21080000, 21074000, 0, 0, 0, RIG_VFO_A) != 0 || + expect_valid_status("documented", documented_status, + sizeof(documented_status), + 145500000, 435000000, 1, 5, 4, + RIG_VFO_B) != 0) + { + return 1; + } + + memcpy(invalid, captured_firmware_3_5_status, sizeof(invalid)); + invalid[0] = 0; + + if (expect_invalid_status("bad-header", invalid, sizeof(invalid)) != 0) + { + return 1; + } + + memcpy(invalid, captured_firmware_3_5_status, sizeof(invalid)); + invalid[4]--; + + if (expect_invalid_status("length-mismatch", invalid, sizeof(invalid)) != 0 || + expect_invalid_status("truncated", captured_firmware_3_5_status, + sizeof(captured_firmware_3_5_status) - 1) != 0 || + expect_invalid_status("short-payload", short_status, + sizeof(short_status)) != 0) + { + return 1; + } + + memcpy(invalid, captured_firmware_3_5_status, sizeof(invalid)); + invalid[4] = 0xFF; + + if (expect_invalid_status("oversized-length", invalid, sizeof(invalid)) != 0) + { + return 1; + } + + memcpy(invalid, captured_firmware_3_5_status, sizeof(invalid)); + invalid[5] = 0x0A; + + if (expect_invalid_status("wrong-command", invalid, sizeof(invalid)) != 0) + { + return 1; + } + + memcpy(invalid, captured_firmware_3_5_status, sizeof(invalid)); + invalid[sizeof(invalid) - 1] ^= 0x01; + + return expect_invalid_status("bad-crc", invalid, sizeof(invalid)); +} + +#ifdef TEST_GUOHE_TRANSACTIONS + +static const unsigned char status_request[] = { + 0xA5, 0xA5, 0xA5, 0xA5, 0x03, 0x0B, 0xF9, 0x37 +}; + +struct peer_case +{ + int fd; + const unsigned char **responses; + size_t response_count; + int status; +}; + +static int read_all(int fd, unsigned char *buffer, size_t length) +{ + size_t total = 0; + + while (total < length) + { + ssize_t count = read(fd, buffer + total, length - total); + + if (count <= 0) { return -1; } + + total += (size_t)count; + } + + return 0; +} + +static int write_all(int fd, const unsigned char *buffer, size_t length) +{ + size_t total = 0; + + while (total < length) + { + ssize_t count = write(fd, buffer + total, length - total); + + if (count <= 0) { return -1; } + + total += (size_t)count; + } + + return 0; +} + +static void *run_peer(void *arg) +{ + struct peer_case *test = arg; + unsigned char request[sizeof(status_request)]; + + test->status = -1; + + for (size_t i = 0; i < test->response_count; i++) + { + if (read_all(test->fd, request, sizeof(request)) != 0 || + memcmp(request, status_request, sizeof(request)) != 0 || + write_all(test->fd, test->responses[i], + sizeof(captured_firmware_3_5_status)) != 0) + { + return NULL; + } + } + + test->status = 0; + return NULL; +} + +static int open_test_rig(rig_model_t model, int fd, RIG **rig) +{ + *rig = rig_init(model); + + if (*rig == NULL) { return -1; } + + RIGPORT(*rig)->fd = fd; + RIGPORT(*rig)->timeout = 250; + RIGPORT(*rig)->retry = 0; + return 0; +} + +static int run_transaction_case(const char *name, rig_model_t model) +{ + const unsigned char *responses[] = { + captured_firmware_3_5_status, + documented_status, + captured_firmware_3_5_status + }; + struct peer_case test = { + .fd = -1, + .responses = responses, + .response_count = sizeof(responses) / sizeof(responses[0]), + .status = -1 + }; + int sockets[2]; + pthread_t thread; + RIG *rig = NULL; + freq_t freq_a = 0; + freq_t freq_b = 0; + ptt_t ptt = RIG_PTT_ON; + int ret_a = -1; + int ret_b = -1; + int ret_ptt = -1; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) + { + perror("socketpair"); + return 1; + } + + test.fd = sockets[1]; + + if (pthread_create(&thread, NULL, run_peer, &test) != 0) + { + close(sockets[0]); + close(sockets[1]); + return 1; + } + + if (open_test_rig(model, sockets[0], &rig) == 0) + { + ret_a = rig->caps->get_freq(rig, RIG_VFO_A, &freq_a); + ret_b = rig->caps->get_freq(rig, RIG_VFO_B, &freq_b); + ret_ptt = rig->caps->get_ptt(rig, RIG_VFO_A, &ptt); + RIGPORT(rig)->fd = -1; + rig_cleanup(rig); + } + + close(sockets[0]); + pthread_join(thread, NULL); + close(sockets[1]); + + if (test.status != 0 || ret_a != RIG_OK || ret_b != RIG_OK || + ret_ptt != RIG_OK || freq_a != 21080000 || freq_b != 435000000 || + ptt != RIG_PTT_OFF) + { + fprintf(stderr, + "%s: transaction mismatch %d/%d/%d/%g/%g/%d peer=%d\n", + name, ret_a, ret_b, ret_ptt, freq_a, freq_b, ptt, + test.status); + return 1; + } + + return 0; +} + +static int test_cached_fallback(void) +{ + unsigned char corrupt_status[sizeof(captured_firmware_3_5_status)]; + const unsigned char *responses[] = { + captured_firmware_3_5_status, + corrupt_status + }; + struct peer_case test = { + .fd = -1, + .responses = responses, + .response_count = sizeof(responses) / sizeof(responses[0]), + .status = -1 + }; + int sockets[2]; + pthread_t thread; + RIG *rig = NULL; + freq_t first = 0; + freq_t cached = 0; + int first_ret = -1; + int cached_ret = -1; + + memcpy(corrupt_status, captured_firmware_3_5_status, + sizeof(corrupt_status)); + corrupt_status[sizeof(corrupt_status) - 1] ^= 0x01; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) + { + perror("socketpair"); + return 1; + } + + test.fd = sockets[1]; + + if (pthread_create(&thread, NULL, run_peer, &test) != 0) + { + close(sockets[0]); + close(sockets[1]); + return 1; + } + + if (open_test_rig(RIG_MODEL_PMR171, sockets[0], &rig) == 0) + { + first_ret = rig->caps->get_freq(rig, RIG_VFO_A, &first); + cached_ret = rig->caps->get_freq(rig, RIG_VFO_B, &cached); + RIGPORT(rig)->fd = -1; + rig_cleanup(rig); + } + + close(sockets[0]); + pthread_join(thread, NULL); + close(sockets[1]); + + if (test.status != 0 || first_ret != RIG_OK || cached_ret != RIG_OK || + first != 21080000 || cached != 21074000) + { + fprintf(stderr, + "cached-fallback: mismatch %d/%d/%g/%g peer=%d\n", + first_ret, cached_ret, first, cached, test.status); + return 1; + } + + return 0; +} + +#endif + +int main(void) +{ + if (test_status_decoder() != 0) { return 1; } + +#ifdef TEST_GUOHE_TRANSACTIONS + rig_register(&pmr171_caps); + rig_register(&q900_caps); + + if (run_transaction_case("PMR-171", RIG_MODEL_PMR171) != 0 || + run_transaction_case("Q900", RIG_MODEL_Q900) != 0 || + test_cached_fallback() != 0) + { + return 1; + } +#endif + + return 0; +}