/* Contest Arbitrator v0.3.2 by Dmitry Gorokh, UR4MCK
This program is FREEWARE!
Cabrillo logs parser
Changes:
11.03.2015: fixed bug with negative dates in parse_value() for TAG_OFFTIME
now split sent/received numbers also by '/' in parse_qso()
*/
#define _XOPEN_SOURCE
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include <sys/stat.h>
#include "config.h"
#include "main.h"
#include "cabrillo.h"
#include "report.h"
/* Cabrillo tag types (mixed with v2, v3, Ermak) */
#define TAG_START_OF_LOG 1
#define TAG_CALLSIGN 2
/* 3 - 7 reserved */
#define TAG_CATEGORY 8
#define TAG_CATEGORY_ASSISTED 9
#define TAG_CATEGORY_BAND 10
#define TAG_CATEGORY_MODE 11
#define TAG_CATEGORY_OPERATOR 12
#define TAG_CATEGORY_POWER 13
#define TAG_CATEGORY_STATION 14
#define TAG_CATEGORY_TIME 15
#define TAG_CATEGORY_TRANSMITTER 16
#define TAG_CATEGORY_OVERLAY 17
#define TAG_CATEGORY_DXPEDITION 18
/* 19 - 31 reserved */
#define TAG_CLAIMED_SCORE 32
#define TAG_CLUB 33
#define TAG_CONTEST 34
#define TAG_CREATED_BY 35
#define TAG_EMAIL 36
#define TAG_LOCATION 37
#define TAG_NAME 38
#define TAG_ADDRESS 39
#define TAG_OPERATORS 40
#define TAG_OFFTIME 41
#define TAG_SOAPBOX 42
#define TAG_ARRL_SECTION 43
#define TAG_IOTA_ISLAND_NAME 44
/* 45 - 63 reserved */
#define TAG_QSO 64
#define TAG_QTC 65
/* 65 - 125 reserved */
#define TAG_SIGNATURE 126
#define TAG_DEBUG 127
#define TAG_END_OF_LOG 128
/* Special cases: */
#define TAG_WARNING 255
#define TAG_ERROR -1
#define TAG_EMPTY 0
#define DELIMITER ":" /* tag <--> value delimiter */
/* Warnings */
#define WARN_MISSING_START_OF_LOG (TAG_START_OF_LOG)
#define WARN_MISSING_END_OF_LOG (TAG_END_OF_LOG)
#define WARN_UNSUPPORTED_VALUE 130
/* TODO */
/* Errors */
#define ERR_UNABLE_TO_ALLOCATE_SPACE -128
#define ERR_ERROR_IN_QSO_RECORD -132
#define ERR_ERROR_IN_CABRILLO_LOG -133
/* TODO */
typedef struct {
char callsign[CALLSIGN_SIZE];
unsigned int cnt;
void *next;
} LINK_S;
/* Known Cabrillo (v2, v3) tags */
static const struct {
int id;
char *name;
} tags[] = {
{TAG_START_OF_LOG, "START-OF-LOG"},
{TAG_CALLSIGN, "CALLSIGN"},
{TAG_CATEGORY, "CATEGORY"},
{TAG_CATEGORY_ASSISTED, "CATEGORY-ASSISTED"},
{TAG_CATEGORY_BAND, "CATEGORY-BAND"},
{TAG_CATEGORY_MODE, "CATEGORY-MODE"},
{TAG_CATEGORY_OPERATOR, "CATEGORY-OPERATOR"},
{TAG_CATEGORY_POWER, "CATEGORY-POWER"},
{TAG_CATEGORY_STATION, "CATEGORY-STATION"},
{TAG_CATEGORY_TIME, "CATEGORY-TIME"},
{TAG_CATEGORY_TRANSMITTER, "CATEGORY-TRANSMITTER"},
{TAG_CATEGORY_OVERLAY, "CATEGORY-OVERLAY"},
{TAG_CATEGORY_DXPEDITION, "CATEGORY-DXPEDITION"},
{TAG_CLAIMED_SCORE, "CLAIMED-SCORE"},
{TAG_CLUB, "CLUB"},
{TAG_CONTEST, "CONTEST"},
{TAG_CREATED_BY, "CREATED-BY"},
{TAG_EMAIL, "EMAIL"},
{TAG_LOCATION, "LOCATION"},
{TAG_NAME, "NAME"},
{TAG_ADDRESS, "ADDRESS"},
{TAG_OPERATORS, "OPERATORS"},
{TAG_OFFTIME, "OFFTIME"},
{TAG_SOAPBOX, "SOAPBOX"},
{TAG_ARRL_SECTION, "ARRL-SECTION"},
{TAG_IOTA_ISLAND_NAME, "IOTA-ISLAND-NAME"},
{TAG_QSO, "QSO"},
{TAG_QTC, "QTC"},
{TAG_SIGNATURE, "SIGNATURE"},
{TAG_DEBUG, "DEBUG"},
{TAG_END_OF_LOG, "END-OF-LOG"},
{0, NULL} /* End of tags list */
};
struct {
unsigned int cnt;
LINK_S *link;
} unresolved;
extern CONFIG_S config;
static int log_started = 0;
static HAM_S *new_ham = NULL;
static QSO_S *qso_ptr = NULL;
/* TODO: move to separate module */
int strip_leading_spaces(char *str);
int strip_trailing_spaces(char *str);
static int parse_tag(char *tag_name, int *tag_id2);
static char *tag_name(int tag_id);
static int correlate_str(char *str1, char *str2);
static int parse_value(int tag_id, char *value, BASE_S *base, FILE *report_fd);
static int warn(int warn_id, char *p0, char *p1, FILE *report_fd);
static char *get_power(int id);
static char *get_assistment(int id);
static char *get_tx(int id);
static char *get_station(int id);
static char *get_time(int id);
static char *get_overlay(int id);
static int valid_callsign(char *call, int report_error, FILE *report_fd);
static inline int is_char(char c);
static inline int is_special_char(char c);
static inline int is_digit(char c);
static int is_alpha(char *s);
static int is_number(char *s);
static int parse_qso(HAM_S *ham, QSO_S *qso, char *str, FILE *report_fd);
static int unresolved_add(char *callsign);
static void unresolved_print(void);
static char *qso_status(int status);
int cabrillo_init(void) {
/* Cabrillo log parser module initialization */
DEBUG("CABRILLO: ==> cabrillo_init()\n");
memset(&unresolved, 0, sizeof(unresolved)); /* Clear unresolved callsigns list */
/* TODO: other initialization */
DEBUG("CABRILLO: <== cabrillo_init()\n");
return 0;
}
int parse_logs(CONFIG_S *config, BASE_S *base) {
/* This function parses all supplied in 'config' Cabrillo log files
and places information into the database */
int n, tag_id, id2, ret, do_report;
unsigned int line_num, errors, warns, total_errors, total_warns;
LOG_FILE_S *p;
FILE *fd, *report_fd;
char s[MAX_STR], tmp[MAX_STR], *tag, *value;
struct stat st;
time_t t;
DEBUG("CABRILLO: ==> parse_logs(%p, %p)\n", config, base);
total_errors = total_warns = 0; /* Reset all errors & warnings counters */
/* Check reporting directory */
if (stat(config->report_dir, &st)) {
/* Create report directory if none is exists */
if (mkdir(config->report_dir, 0777) != 0) {
out(NULL, "WARNING: Unable to create reporting directory\n");
total_warns++;
do_report = 0;
} else do_report = 1;
} else if (!S_ISDIR(st.st_mode)) {
out(NULL, "WARNING: Unable to stat reporting directory. Try to delete file with name %s\n", config->report_dir);
total_warns++;
do_report = 0;
} else do_report = 1;
/* Open and parse each log file in the list */
n = 0; /* Number of log files parsed */
p = config->file_list;
while (p) {
if (!(fd = fopen(p->name, "r"))) {
out(NULL, "ERROR: Couldn't open log file: %s\n", p->name);
total_errors++;
} else {
/* Open report file */
if (do_report) {
/* Construct report file name (with path) */
if (report_name(s, p->name, config)) {
/* Open report file to write analyzis info into it */
if (!(report_fd = fopen(s, "w"))) {
out(NULL, "WARNING: Unable to create report file: %s\n", s);
total_warns++;
} else {
time(&t);
report_add(report_fd, "Log analyzis report file\n"
"Created by Contest Arbitrator version %s\n", VERSION);
report_add(report_fd, "Started: %s\n", ctime(&t));
}
} else report_fd = NULL;
} else report_fd = NULL;
out(NULL, "CABRILLO: Analyzing log file: %s\n", p->name);
report_add(report_fd, "Analyzing log file: %s\n\n", p->name);
line_num = errors = warns = 0;
while (!feof(fd) && fgets(s, MAX_STR, fd)) {
line_num++; /* Increment log file lines counter */
if (s[0] == '\0' || s[0] == '\n' || s[0] == '\r') continue; /* Skip blank line */
strcpy(tmp, s); /* Needed for error reporting */
/* Strip spaces and garbage at the start of the line */
strip_leading_spaces(s);
/* Check for short line after spaces strip */
if (strlen(s) <= 2) {
out(report_fd, "WARNING: Misprint in Cabrillo log file %s, line %u:\n"
"WARNING: line is too short\n", p->name, line_num);
warns++;
continue;
}
/* Check for Cabrillo format conformance: look for tag <--> value delimiter */
if (!strstr(s, DELIMITER) && !config->contest.ignore_tags) {
out(report_fd, "WARNING: Misprint in Cabrillo log file %s, line %u:\n"
"WARNING: %s\nWARNING: tag <--> value delimiter is absent\n", p->name, line_num, tmp);
warns++;
/* Try to fix the log line if it lacks a delimiter */
if ((tag = strchr(s, ' '))) { /* Points to first space char */
*tag = DELIMITER[0]; /* which is replaced with delimiter */
out(report_fd, "WARNING: Fixed.\n");
strcpy(tmp, s); /* Because of changes in this line */
} else {
out(report_fd, "WARNING: NOT fixed.\n");
continue;
}
}
/* Split into tag and its value */
tag = strtok(s, DELIMITER);
value = strtok(NULL, "\n");
if (!tag) {
warn(ERR_ERROR_IN_CABRILLO_LOG, "Cabrillo tag is absent:", tmp, report_fd);
errors++;
continue;
}
/* Remove garbage */
strip_trailing_spaces(tag);
strip_trailing_spaces(value);
strip_leading_spaces(value);
/* Get tag ID */
if ((tag_id = parse_tag(tag, &id2)) < 0) {
if (!config->contest.ignore_tags) {
out(report_fd, "\nERROR: Unknown Cabrillo tag '%s' in log file %s, line %u:\n", tag, p->name, line_num);
out(report_fd, "ERROR: %s\n", tmp);
errors++;
}
} else if (tag_id > TAG_END_OF_LOG) {
if (!config->contest.ignore_tags) {
out(report_fd, "\nWARNING: Cabrillo tag '%s' in log file %s is possibly misprinted, line %u:\n", tag, p->name, line_num);
out(report_fd, "WARNING: %s", tmp);
if (config->contest.search_tags) {
out(report_fd, "WARNING: This tag is treated as '%s'\n\n", tag_name(id2));
tag_id = id2;
}
warns++;
}
}
/* Parse and store tag value */
if (tag_id > 0) {
ret = parse_value(tag_id, value, base, report_fd);
if (ret < 0) errors++; /* Increment errors counter */
else if (ret > 0) warns++; /* Increment warnings counter */
}
if (tag_id == TAG_END_OF_LOG) break; /* Force log close */
}
out(NULL, "CABRILLO: %u lines have been analyzed in log file: %s\n", line_num, p->name);
out(NULL, "CABRILLO: %u error%s %u warning%s\n\n", errors, errors == 1 ? "," : "s,", warns, warns == 1 ? "" : "s");
report_add(report_fd, "\nTotal: %u error%s %u warning%s\n\n", errors, errors == 1 ? "," : "s,", warns, warns == 1 ? "" : "s");
if (errors + warns > 0) {
report_add(report_fd, "Please correct your log file submission and send it again to contest manager.\n");
if (config->contest.email[0] != '\0') report_add(report_fd, "E-mail: %s\n", config->contest.email);
}
report_add(report_fd, "\n-------------\n"
"Thank You for participating in this contest!\n\n"
"Sincerely yours,\n\"%s\" contest team\n", config->contest.name);
total_errors += errors;
total_warns += warns;
fclose(fd); /* Close log file */
if (report_fd) fclose(report_fd); /* Close report file */
n++;
}
p = (LOG_FILE_S*)p->next; /* Go to the next log file in the list */
}
DEBUG("CABRILLO: %d log files have been parsed from the list\n", n);
if (total_errors > 0 || total_warns > 0) {
out(NULL, "------------------------------------------------------------------------\n"
"CABRILLO: Total errors: %u, Total warnings: %u\n\n", total_errors, total_warns);
out(NULL, "Contest judge, please check and fix misprints in log files listed above.\nOtherwise results will be inaccurate!\n"
"------------------------------------------------------------------------\n");
}
#ifdef _DEBUG_
// dump_database(base);
#endif
DEBUG("CABRILLO: <== parse_logs(%p, %p)\n", config, base);
return n;
}
int strip_leading_spaces(char *str) {
/* Strips non-expected chars from the start of line (str) */
int i, j;
if (!str) return -1;
/* Skip non-alfanumeric chars */
i = 0;
while (i < MAX_STR && str[i] != '\0' && !is_char(str[i]) && !is_special_char(str[i]) && !is_digit(str[i])) i++;
if (i > 0) {
/* Copy string to the begining */
j = 0;
while (i < MAX_STR && str[i] != '\0') {
/* Replace tabs with spaces */
if (str[i] == 0x0b || str[i] == 0x09) str[j++] = ' ';
else {
/* Strip unrecognized chars from the string */
if (str[i] == ' ' || is_char(str[i]) || is_special_char(str[i]) || is_digit(str[i])) str[j++] = str[i];
}
i++;
}
str[j] ='\0';
i = j;
}
return i; /* New line number */
}
int strip_trailing_spaces(char *str) {
/* Strips non-expected chars from the end of line (str) */
char *s;
if (!str) return -1;
/* Go to the end of line */
s = str;
while (*s != '\0') s++;
/* Check for the non-expected chars */
while (s > str && !is_char(*s) && !is_special_char(*s) && !is_digit(*s)) {
*s = '\0';
s--;
}
return 0;
}
static int parse_tag(char *tag_name, int *tag_id2) {
/* Returns tag ID by its name */
int i, j, k, n, len, corr, max_corr;
n = strlen(tag_name);
i = max_corr = 0;
while (tags[i].id != 0 && tags[i].name) {
/* Try to comparing strings */
if (strcasecmp(tags[i].name, tag_name) == 0) break;
/* Prepare scaling factor for correlation */
if ((len = strlen(tags[i].name)) > 0) {
if (len >= n) k = 1000 * n / len;
else if (n > len) k = 1000 * len / n;
} else k = 0;
/* Try to calculate maximum correlation */
j = 0;
while (j < len) {
if ((corr = (correlate_str((char*)(tags[i].name + j), tag_name) * k)) > max_corr) {
max_corr = corr;
*tag_id2 = tags[i].id;
}
j++;
}
i++;
}
if (tags[i].name) return tags[i].id; /* Found tag */
if (max_corr > 0) return TAG_WARNING; /* Some correlation found (ID is in 'tag_id2') */
return TAG_ERROR; /* Tag was not found */
}
static char *tag_name(int tag_id) {
/* Returns name of the tag by its ID */
int i;
i = 0;
while (tags[i].id != 0 && tags[i].name && tags[i].id != tag_id) i++;
if (tags[i].name) return tags[i].name;
return "UNKNOWN";
}
static int correlate_str(char *str1, char *str2) {
/* Calculates (0 offset) correlation between two strings */
int n;
n = 0;
while (*str1 != '\0' && *str2 != '\0')
if (*str1++ == *str2++) n++; /* Increase correlation sum */
return n;
}
static int parse_value(int tag_id, char *value, BASE_S *base, FILE *report_fd) {
/* Parses tag value and stores it in the database */
int ret, ret2, i, flag;
QSO_S *ptr;
static struct tm tm;
char *s, t_start[32], t_end[32];
DEBUG("CABRILLO: ==> parse_value(%d (%s), '%s', %p, %p)\n", tag_id, tag_name(tag_id), value, base, report_fd);
ret = 0;
switch (tag_id) {
case TAG_START_OF_LOG:
if (log_started) {
ret = warn(WARN_MISSING_END_OF_LOG, NULL, NULL, report_fd);
/* Place previous ham record into the database */
parse_value(TAG_END_OF_LOG, NULL, base, report_fd);
}
/* Allocate space for this ham */
if (!(new_ham = calloc(1, sizeof(HAM_S)))) ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
qso_ptr = NULL;
log_started = 1; /* New log is being started for the new ham */
/* NOTE: 'value' is unused for now */
break;
case TAG_CALLSIGN:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (value && (i = strlen(value)) > 0) {
if (i >= CALLSIGN_SIZE) {
out(report_fd, "WARNING: Callsign is too long: '%s'. It was trancated\n", value);
value[CALLSIGN_SIZE] = '\0';
ret = 140;
}
/* Checking callsign */
if (!valid_callsign(value, 1, report_fd)) {
out(report_fd, "WARNING: Callsign is seems to be wrong: %s\n", value);
ret = 141;
}
strcpy(new_ham->callsign, value); /* Copy callsign */
} else {
out(report_fd, "ERROR: Callsign is not present in the log header\n");
ret = -8;
}
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_CATEGORY_ASSISTED:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (assistment_type[i].name) {
if (strcasecmp(assistment_type[i].name, value) == 0) {
new_ham->assisted_id = assistment_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_BAND:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (band_list[i].name) {
if (strcasecmp(band_list[i].name, value) == 0) {
new_ham->band_id = band_list[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_MODE:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (mode_type[i].name) {
if (strcasecmp(mode_type[i].name, value) == 0) {
new_ham->mode_id = mode_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_OPERATOR:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (operation_type[i].name) {
if (strcasecmp(operation_type[i].name, value) == 0) {
new_ham->optype_id = operation_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_POWER:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (power_type[i].name) {
if (strcasecmp(power_type[i].name, value) == 0) {
new_ham->power_id = power_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_STATION:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (station_type[i].name) {
if (strcasecmp(station_type[i].name, value) == 0) {
new_ham->station_id = station_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_TIME:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (time_type[i].name) {
if (strcasecmp(time_type[i].name, value) == 0) {
new_ham->time_id = time_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_TRANSMITTER:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (tx_type[i].name) {
if (strcasecmp(tx_type[i].name, value) == 0) {
new_ham->tx_id = tx_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_OVERLAY:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
i = 1;
flag = 0;
while (overlay_type[i].name) {
if (strcasecmp(overlay_type[i].name, value) == 0) {
new_ham->overlay_id = overlay_type[i].id;
flag = 1;
break;
}
i++;
}
if (!flag) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CATEGORY_DXPEDITION:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_CLAIMED_SCORE:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
if ((i = atoi(value)) < 0) ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(tag_id), value, report_fd);
else new_ham->claimed_score = (unsigned int)i;
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CLUB:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
if (strlen(value) >= MAX_STR) {
out(report_fd, "WARNING: Club name is too long: '%s'. It was trancated\n", value);
value[MAX_STR] = '\0';
ret = 255; /* Default warning code */
}
strcpy(new_ham->club_name, value); /* Copy club name */
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CONTEST:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
/* TODO */
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_CREATED_BY:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_EMAIL:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_LOCATION:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
if (strlen(value) >= MAX_STR) {
out(report_fd, "WARNING: Location description is too long: '%s'. It was trancated\n", value);
value[MAX_STR] = '\0';
ret = 255; /* Default warning code */
}
strcpy(new_ham->location, value); /* Copy location */
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_NAME:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
if (strlen(value) >= MAX_STR) {
out(report_fd, "WARNING: Name is too long: '%s'. It was trancated\n", value);
value[MAX_STR] = '\0';
ret = 255; /* Default warning code */
}
strcpy(new_ham->name, value); /* Copy ham's name */
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_ADDRESS:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_OPERATORS:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_OFFTIME:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
/* First split offtime start & end */
s = strchr(value, ' ') + 1;
if ((s = strchr(s, ' '))) {
*s = '\0';
s++; /* Now s points to the offtime_end, value points to offtime_start */
/* Convert date & time to integer representation */
if (!strptime(value, "%Y-%m-%d %H%M", &tm)) {
out(report_fd, "WARNING: Unable to convert date and time: %s\n", value);
ret = 26;
break;
}
new_ham->off_time_start = timegm(&tm); /* Save off time (start) */
/* Convert date & time to integer representation */
if (!strptime(s, "%Y-%m-%d %H%M", &tm)) {
out(report_fd, "WARNING: Unable to convert date and time: %s\n", s);
ret = 27;
break;
}
new_ham->off_time_end = timegm(&tm); /* Save off time stamp (end) */
/* Validate start & end times */
if (new_ham->off_time_start > 0 && new_ham->off_time_end > 0) {
/* Check 'off time' values */
if (new_ham->off_time_start >= new_ham->off_time_end) {
strftime(t_start, sizeof(t_start), "%Y-%m-%d %H%M UTC", gmtime((time_t*)&new_ham->off_time_start));
strftime(t_end, sizeof(t_end), "%Y-%m-%d %H%M UTC", gmtime((time_t*)&new_ham->off_time_end));
out(report_fd, "WARNING: Off time period is wrong:\nWARNING: Start: %s, End: %s\n", t_start, t_end);
}
} else {
/* Clear off-times start/end */
new_ham->off_time_start = new_ham->off_time_end = 0;
}
} else ret = warn(WARN_UNSUPPORTED_VALUE, tag_name(TAG_OFFTIME), value, report_fd);
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_SOAPBOX:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_ARRL_SECTION:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_IOTA_ISLAND_NAME:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_QSO:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
/* Allocate space for storing this QSO record */
if (!(ptr = calloc(1, sizeof(QSO_S)))) {
ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
}
/* Analyze QSO */
if ((ret2 = parse_qso(new_ham, ptr, value, report_fd)) != 0) {
free(ptr); /* Release memory on error */
ret = ret2;
} else {
/* Link with ham */
if (!new_ham->qso) new_ham->qso = ptr;
else qso_ptr->next = ptr;
qso_ptr = ptr;
}
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_QTC:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_SIGNATURE:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
if (!value || strlen(value) == 0) break; /* Silently skip empty value */
if (strlen(value) >= MAX_STR) {
out(NULL, "WARNING: Signature is too long: '%s'. It was trancated\n", value);
value[MAX_STR] = '\0';
ret = 255; /* Default warning code */
}
/* Check signature from the log with one appeared in config file */
if (strcmp(config.contest.signature, value) == 0)
new_ham->flags |= HAM_FLAG_VALID_SIGNATURE; /* Signature is valid */
else {
new_ham->flags &= ~(HAM_FLAG_VALID_SIGNATURE); /* Signture is invalid, so reset this flag */
out(NULL, "WARNING: Signature is not valid!\n");
ret = 255; /* Default warning code */
}
} else ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
break;
case TAG_DEBUG:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
/* Skiped for now */
break;
case TAG_END_OF_LOG:
if (!log_started) ret = warn(WARN_MISSING_START_OF_LOG, NULL, NULL, report_fd);
if (new_ham) {
/* Link current ham record with the database */
if (!base->ham) {
if (!(base->ham = malloc(sizeof(HAM_S*)))) ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
} else if (!(base->ham = (HAM_S**)realloc(base->ham, sizeof(HAM_S*) * (base->ham_num + 1)))) ret = warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
if (ret == 0) { /* Success */
base->ham[base->ham_num++] = new_ham; /* Link with database */
}
new_ham = NULL;
}
log_started = 0;
break;
}
DEBUG("CABRILLO: <== parse_value(%d (%s), '%s', %p, %p) = %d\n", tag_id, tag_name(tag_id), value, base, report_fd, ret);
return ret;
}
static int warn(int warn_id, char *p0, char *p1, FILE *report_fd) {
/* Print format warnings */
switch (warn_id) {
/* Warnings */
case WARN_MISSING_START_OF_LOG:
out(report_fd, "WARNING: corrupted log header (no tag START-OF-LOG at the begining)\n");
break;
case WARN_MISSING_END_OF_LOG:
out(report_fd, "WARNING: Corrupted log header (no tag END-OF-LOG at the end)\n");
break;
case WARN_UNSUPPORTED_VALUE:
out(report_fd, "WARNING: Unsupported value '%s' for tag '%s'\n", p1, p0);
break;
/* TODO: other warnings */
/* Errors */
case ERR_UNABLE_TO_ALLOCATE_SPACE:
out(NULL, "ERROR: Unable to allocate space for storing log information\n");
break;
case ERR_ERROR_IN_QSO_RECORD:
out(report_fd, "ERROR: Corrupted QSO record: %s\n", p0);
if (p1) out(report_fd, "ERROR: %s\n", p1);
break;
case ERR_ERROR_IN_CABRILLO_LOG:
out(report_fd, "ERROR: Corrupted Cabrillo log file: %s\n", p0);
if (p1) out(report_fd, "ERROR: %s\n", p1);
break;
/* TODO: other errors */
}
return warn_id;
}
void dump_database(BASE_S *base) {
/* Dumps contest participants database */
int i, j, n;
HAM_S *ham;
QSO_S *p;
DEBUG("CABRILLO: ==> dump_database(%p)\n", base);
if (base && base->ham && (n = base->ham_num) > 0) {
i = 0;
ham = base->ham[0];
while (i < n && ham) {
DEBUG("CABRILLO: i = %d, ham_ptr = %p\n", i, ham);
/* Dump all fields in the 'ham' structure */
DEBUG("CABRILLO: DUMP: --------------------------------------\n");
DEBUG("CABRILLO: DUMP: callsign : %s\n", ham->callsign);
DEBUG("CABRILLO: DUMP: optype_id : %d (%s)\n", ham->optype_id, get_optype(ham->optype_id));
DEBUG("CABRILLO: DUMP: band_id : %d (%s)\n", ham->band_id, get_band(ham->band_id));
DEBUG("CABRILLO: DUMP: mode_id : %d (%s)\n", ham->mode_id, get_mode(ham->mode_id));
DEBUG("CABRILLO: DUMP: power_id : %d (%s)\n", ham->power_id, get_power(ham->power_id));
DEBUG("CABRILLO: DUMP: assisted_id : %d (%s)\n", ham->assisted_id, get_assistment(ham->assisted_id));
DEBUG("CABRILLO: DUMP: tx_id : %d (%s)\n", ham->tx_id, get_tx(ham->tx_id));
DEBUG("CABRILLO: DUMP: station_id : %d (%s)\n", ham->station_id, get_station(ham->station_id));
DEBUG("CABRILLO: DUMP: time_id : %d (%s)\n", ham->time_id, get_time(ham->time_id));
DEBUG("CABRILLO: DUMP: overlay_id : %d (%s)\n", ham->overlay_id, get_overlay(ham->overlay_id));
DEBUG("CABRILLO: DUMP: name : %s\n", ham->name);
DEBUG("CABRILLO: DUMP: location : %s\n", ham->location);
DEBUG("CABRILLO: DUMP: club_name : %s\n", ham->club_name);
DEBUG("CABRILLO: DUMP: offtimestart : %u: %s", ham->off_time_start, ham->off_time_start > 0 ? asctime(gmtime(&ham->off_time_start)) : "\n");
DEBUG("CABRILLO: DUMP: offtimeend : %u: %s", ham->off_time_end, ham->off_time_end > 0 ? asctime(gmtime(&ham->off_time_end)) : "\n");
DEBUG("CABRILLO: DUMP: claimed_score: %u\n", ham->claimed_score);
DEBUG("CABRILLO: DUMP: sum_score : %u\n", ham->sum_score);
DEBUG("CABRILLO: DUMP: actual_score : %u\n", ham->actual_score);
DEBUG("CABRILLO: DUMP: total_mult : %u\n", ham->total_mult);
DEBUG("CABRILLO: DUMP: rating_place : %u\n", ham->rating_place);
DEBUG("CABRILLO: DUMP: no_loc : %d\n", ham->no_loc);
DEBUG("CABRILLO: DUMP: systematic_dt: %d\n", ham->systematic_dt);
DEBUG("CABRILLO: DUMP: flags : %#08x\n", ham->flags);
DEBUG("CABRILLO: DUMP: ubn_file : '%s'\n", ham->ubn_file);
DEBUG("CABRILLO: DUMP: qso_ptr : %p\n", ham->qso);
DEBUG("CABRILLO: DUMP: *** DUMPING QSO ***\n");
j = 0;
p = ham->qso;
while (p) {
DEBUG("CABRILLO: DUMP: --- QSO #%u ---\n", j++);
DEBUG("CABRILLO: DUMP: ham_id : %d%s\n", p->ham_id, p->ham_id < 0 ? " (unresolved)" : "");
DEBUG("CABRILLO: DUMP: checked : %s\n", qso_status(p->checked));
DEBUG("CABRILLO: DUMP: marked : %d\n", p->marked);
DEBUG("CABRILLO: DUMP: dupe : %d\n", p->dupe);
DEBUG("CABRILLO: DUMP: callsign : %s\n", p->callsign);
DEBUG("CABRILLO: DUMP: freq : %u\n", p->freq);
DEBUG("CABRILLO: DUMP: mode : %d (%s)\n", p->mode_op, get_qso_mode(p->mode_op));
DEBUG("CABRILLO: DUMP: timestamp : %u: %s", p->timestamp, p->timestamp > 0 ? asctime(gmtime(&p->timestamp)) : "\n");
DEBUG("CABRILLO: DUMP: chk_snt[0] : '%s'\n", p->check_sent[0]);
DEBUG("CABRILLO: DUMP: chk_snt[1] : '%s'\n", p->check_sent[1]);
DEBUG("CABRILLO: DUMP: chk_snt[2] : '%s'\n", p->check_sent[2]);
DEBUG("CABRILLO: DUMP: chk_snt[3] : '%s'\n", p->check_sent[3]);
DEBUG("CABRILLO: DUMP: chk_rcv[0] : '%s'\n", p->check_recv[0]);
DEBUG("CABRILLO: DUMP: chk_rcv[1] : '%s'\n", p->check_recv[1]);
DEBUG("CABRILLO: DUMP: chk_rcv[2] : '%s'\n", p->check_recv[2]);
DEBUG("CABRILLO: DUMP: chk_rcv[3] : '%s'\n", p->check_recv[3]);
DEBUG("CABRILLO: DUMP: tx_num : %d\n", p->tx_num);
DEBUG("CABRILLO: DUMP: round_num : %d\n", p->round_num);
DEBUG("CABRILLO: DUMP: score : %d\n", p->score);
DEBUG("CABRILLO: DUMP: mult : %u\n", p->mult);
DEBUG("CABRILLO: DUMP: mark : %#08x\n", p->mark);
DEBUG("CABRILLO: DUMP: error_cnt : %u\n", p->error_cnt);
DEBUG("CABRILLO: DUMP: his_qso : %p\n", p->his_qso);
DEBUG("CABRILLO: DUMP: next : %p\n", p->next);
DEBUG("CABRILLO: DUMP: warnings : %p\n", p->warnings);
DEBUG("CABRILLO: DUMP: TODO: DUMP QSO WARNINGS\n");
p = (QSO_S*)p->next;
}
DEBUG("CABRILLO: DUMP: *** END OF QSO DUMP ***\n");
DEBUG("CABRILLO: DUMP: warnings_ptr : %p\n", ham->warnings);
DEBUG("CABRILLO: DUMP: TODO: DUMP GENERAL WARNINGS\n");
DEBUG("CABRILLO: DUMP: --------------------------------------\n\n");
ham = base->ham[++i];
}
}
DEBUG("CABRILLO: <== dump_database(%p)\n", base);
}
char *get_optype(int id) {
int i;
i = 0;
while (operation_type[i].name) {
if (operation_type[i].id == id) return operation_type[i].name;
i++;
}
return "UNKNOWN";
}
char *get_band(int id) {
int i;
i = 0;
while (band_list[i].name) {
if (band_list[i].id == id) return band_list[i].name;
i++;
}
return "UNKNOWN";
}
char *get_mode(int id) {
int i;
i = 0;
while (mode_type[i].name) {
if (mode_type[i].id == id) return mode_type[i].name;
i++;
}
return "UNKNOWN";
}
char *get_qso_mode(int id) {
int i;
i = 0;
while (qso_mode[i].name) {
if (qso_mode[i].id == id) return qso_mode[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_power(int id) {
int i;
i = 0;
while (power_type[i].name) {
if (power_type[i].id == id) return power_type[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_assistment(int id) {
int i;
i = 0;
while (assistment_type[i].name) {
if (assistment_type[i].id == id) return assistment_type[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_tx(int id) {
int i;
i = 0;
while (tx_type[i].name) {
if (tx_type[i].id == id) return tx_type[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_station(int id) {
int i;
i = 0;
while (station_type[i].name) {
if (station_type[i].id == id) return station_type[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_time(int id) {
int i;
i = 0;
while (time_type[i].name) {
if (time_type[i].id == id) return time_type[i].name;
i++;
}
return "UNKNOWN";
}
static char *get_overlay(int id) {
int i;
i = 0;
while (overlay_type[i].name) {
if (overlay_type[i].id == id) return overlay_type[i].name;
i++;
}
return "UNKNOWN";
}
static int valid_callsign(char *call, int report_error, FILE *report_fd) {
/* Return 1 if callsign is correct hamradio callsign, 0 - otherwise */
int i, j;
char *start, *end, c[CALLSIGN_SIZE];
/* Check for invalid chars */
start = call;
i = 0;
while (*start && i < CALLSIGN_SIZE) {
if (!is_char(*start) && !is_digit(*start) && *start != '/') {
if (report_error) out(report_fd, "ERROR: invaild char '%c' in callsign '%s'\n", *start, call);
return 0; /* Callsign is not valid */
}
c[i++] = *start++;
}
if (*start) {
if (report_error) out(report_fd, "WARNING: Callsign '%s' is too long\n", call);
return 0; /* Not valid */
}
c[i] = '\0';
/* Remove extensions */
if ((start = strchr(c, '/')) && (end = strchr(start + 1, '/'))) {
*end = '\0';
i = (int)(end - c);
}
/* Check for first to numbers */
if (i > 1 && (is_digit(c[0]) && is_digit(c[1]))) {
if (report_error) out(report_fd, "ERROR: Malformed callsign '%s' - callsign can't start with 2 digits\n", call);
return 0; /* Callsign is not valid */
}
/* Check for number inside the call */
start = c;
while (*start) {
if (is_digit(*start)) {
j = (int)(start - c);
if (j >= 1 && j < i) return 1;
}
start++;
}
if (report_error) out(report_fd, "ERROR: Malformed callsign '%s' - no number\n", call);
return 0; /* Callsign is not valid */
}
static inline int is_char(char c) {
if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') return 1;
return 0;
}
static inline int is_special_char(char c) {
if (c == '#' || c == '@' || c == '$' || c == '%' || c == '^' || c == '&' || c == '*' || \
c == '(' || c == ')' || c == '-' || c == '+' || c == '_' || c == '=' || c == '.' || \
c == ',' || c == '/' || c == '|' || c == '[' || c == ']' || c == '{' || c == '}' || \
c == '!' || c == '?' || c == '~' || c == '"' || c == '\'' || c == '\\') return 1;
return 0;
}
static inline int is_digit(char c) {
if (c >= '0' && c <= '9') return 1;
return 0;
}
static int is_alpha(char *s) {
/* Return 1 if 's' is a pure alphabetic string */
char *p;
p = s;
while (p && *p != '\0') {
if (!is_char(*p++)) return 0;
p++;
}
return 1;
}
static int is_number(char *s) {
/* Return 1 if 's' is a pure numeric string */
char *p;
p = s;
while (p && *p != '\0') {
if (!is_digit(*p)) return 0;
p++;
}
return 1;
}
static int parse_qso(HAM_S *ham, QSO_S *qso, char *str, FILE *report_fd) {
int i, j, n, ret;
char *val, *v[2 * CHK_CNT + 3], *s, *sp;
char tmp[MAX_STR], buf[MAX_STR];
static struct tm tm;
/* Preset */
ret = 0;
qso->ham_id = -1; /* Set to unresolved */
strcpy(tmp, str); /* Needed for error reporting */
/* Get frequency */
if (!(val = strtok(str, " "))) {
warn(ERR_ERROR_IN_QSO_RECORD, str, NULL, report_fd);
return -1;
}
DEBUG("CABRILLO: frequency = %s\n", val);
if ((n = atoi(val)) <= 0) {
out(report_fd, "ERROR: Wrong frequency: %s\n", val);
return -2;
}
/* Check is this frequency lies within the band_list */
i = qso->freq = 0;
while (band_list[i].name) {
if (n >= band_list[i].lo_range && n <= band_list[i].hi_range) {
qso->freq = (unsigned int)n;
break;
}
i++;
}
if (qso->freq == 0) {
/* Maybe it is a band name instead of frequency in kHz */
i = 0;
while (band_list[i].name) {
if (strstr(band_list[i].name, val) == band_list[i].name) {
qso->freq = band_list[i].lo_range;
break;
}
i++;
}
if (qso->freq == 0) {
out(report_fd, "ERROR: Unrecognized frequency: %s\n", val);
return -3;
}
}
/* Get mode */
if (!(val = strtok(NULL, " "))) {
warn(ERR_ERROR_IN_QSO_RECORD, str, NULL, report_fd);
return -8;
}
DEBUG("CABRILLO: mode = %s\n", val);
i = 1;
j = 0;
while (qso_mode[i].name) {
if (strcasecmp(qso_mode[i].name, val) == 0) {
qso->mode_op = qso_mode[i].id;
j = 1;
break;
}
i++;
}
if (!j) {
out(report_fd, "WARNING: Unrecognized mode: '%s'. Operation mode was set to 'NONE'\n", val);
qso->mode_op = 0;
ret = 9;
}
/* Check this QSO mode with reported in the log header */
if (ham->mode_id > 0 && ham->mode_id != 8 /* MIXED || ALL */ && qso->mode_op > 0 && ham->mode_id != mode_type2id(qso->mode_op)) {
out(report_fd, "WARNING: Operation mode differs with one declared in the log header: %s <--> %s\n", get_mode(mode_type2id(qso->mode_op)), get_mode(ham->mode_id));
out(report_fd, "WARNING: %s\n", tmp);
ret = 10;
}
/* Get date */
if (!(val = strtok(NULL, " "))) {
warn(ERR_ERROR_IN_QSO_RECORD, str, NULL, report_fd);
return -16;
}
DEBUG("CABRILLO: date = %s\n", val);
sprintf(buf, "%s ", val); /* Copy to temporary string buffer */
/* Get time */
if (!(val = strtok(NULL, " "))) {
warn(ERR_ERROR_IN_QSO_RECORD, str, NULL, report_fd);
return -24;
}
DEBUG("CABRILLO: time = %s\n", val);
strcat(buf, val); /* Concatenate date and time */
/* Convert date & time to integer representation */
if (!strptime(buf, "%Y-%m-%d %H%M", &tm)) {
out(report_fd, "ERROR: Unable to convert date and time: %s\n", tmp);
return -25;
}
qso->timestamp = timegm(&tm); /* Save time stamp */
/* Split other log 'QSO:' fields into separate strings */
i = 0;
while (i < 2 * CHK_CNT + 3 && (v[i] = strtok(NULL, " "))) i++;
if ((n = i) < 2) {
warn(ERR_ERROR_IN_QSO_RECORD, "Insufficient fields in QSO:", tmp, report_fd);
return -32;
}
#ifdef _DEBUG_
for (i = 0; i < n; i++) DEBUG("CABRILLO: v[%d] = '%s'\n", i, v[i]);
#endif
/* Look for my_call */
if (!valid_callsign(v[0], 0, NULL)) {
warn(ERR_ERROR_IN_QSO_RECORD, "my_call is absent or corrupted in QSO:", tmp, report_fd);
return -42;
}
/* Check this callsign with one reported in the log header */
if (strcasecmp(ham->callsign, v[0]) != 0) {
warn(ERR_ERROR_IN_QSO_RECORD, "my_call and callsign from the log header differs in QSO:", tmp, report_fd);
return -43;
}
if (--n < 1) {
warn(ERR_ERROR_IN_QSO_RECORD, "Insufficient fields in QSO:", tmp, report_fd);
return -44;
}
/* Check for short log */
if (n == 1) {
if (valid_callsign(v[1], 0, NULL)) strcpy(qso->callsign, v[1]);
else {
warn(ERR_ERROR_IN_QSO_RECORD, "his_call is absent or corrupted in QSO:", tmp, report_fd);
return -45;
}
} else {
/* Parse sent check numbers */
for (i = 1, j = 0; i < 2 * CHK_CNT + 3 && j < CHK_CNT && n > 0 && v[i]; i++, n--) {
if (valid_callsign(v[i], 0, NULL)) {
strcpy(qso->callsign, v[i]);
i++;
break;
} else {
s = strtok_r(v[i], "/", &sp);
while (s && j < CHK_CNT) {
strcpy(qso->check_sent[j++], s);
s = strtok_r(NULL, "/", &sp);
}
}
}
if (qso->callsign[0] == '\0') {
warn(ERR_ERROR_IN_QSO_RECORD, "his_call is absent or corrupted in QSO:", tmp, report_fd);
return -46;
}
/* Parse received check numbers */
for (j = 0; i < 2 * CHK_CNT + 3 && j < CHK_CNT && n > 0 && v[i]; i++, n--) {
s = strtok_r(v[i], "/", &sp);
while (s && j < CHK_CNT) {
strcpy(qso->check_recv[j++], s);
s = strtok_r(NULL, "/", &sp);
}
}
if (j == 0) {
warn(ERR_ERROR_IN_QSO_RECORD, "Received check numbers are absent or corrupted in QSO:", tmp, report_fd);
return -47;
}
}
return ret;
}
int free_logs(BASE_S *base) {
/* Release used memory for database */
int i, n;
HAM_S *ham;
QSO_S *p, *next;
DEBUG("CABRILLO: ==> free_logs(%p)\n", base);
#ifdef _DEBUG_
dump_database(base);
#endif
if (base && base->ham) {
n = base->ham_num;
i = 0;
while (i < n && (ham = base->ham[i++])) {
/* Free memory from each QSO */
p = ham->qso;
while (p) {
next = (QSO_S*)p->next;
free(p);
p = next;
}
/* Free memory from warnings */
free(ham->warnings);
}
/* Free memory from all ham's records in the database */
free(base->ham);
base->ham_num = 0; /* Database is now empty */
}
DEBUG("CABRILLO: <== free_logs(%p)\n", base);
return 0;
}
int cross_check(BASE_S *base, int contest_id) {
/* Perform general cross-check on all QSOs in the database */
int n, i, j, tmp1, tmp2, dt, delta_time, do_ubn;
unsigned int qso_num, qso_ok, qso_err, qso_unres, his_errors, my_errors, errors;
HAM_S *ham, *remote;
QSO_S *qso, *his_qso;
char *my_call, *his_call, *dir, str[MAX_STR];
FILE *ubn_fd;
struct stat st;
DEBUG("CABRILLO: ==> cross_check(%p, %d)\n", base, contest_id);
out(NULL, "\nCABRILLO: General QSO cross-checking...\n\n");
/* Initialization */
qso_ok = qso_err = qso_unres = 0;
/* Check UBN files directory */
if (stat(config.ubn_dir, &st)) {
/* Create UBN directory if none is exists */
if (mkdir(config.ubn_dir, 0777) != 0) {
out(NULL, "WARNING: Unable to create UBN directory\n");
do_ubn = 0;
} else do_ubn = 1;
} else if (!S_ISDIR(st.st_mode)) {
out(NULL, "WARNING: Unable to stat UBN directory. Try to delete file with name %s\n", config.ubn_dir);
do_ubn = 0;
} else do_ubn = 1;
n = base->ham_num;
for (i = 0; i < n; i++) {
out(NULL, "\n================================================================================================\n");
ham = base->ham[i];
/* Check operator's properties */
/* TODO */
my_call = ham->callsign;
if (do_ubn) {
/* Remove slashes from callsign */
strcpy(str, my_call);
dir = str;
while (*dir != '\0' && (dir = strchr(dir, '/'))) {
*dir = '_';
dir++;
}
/* Create UBN file */
if (config.ubn_dir[0] != '\0') dir = config.ubn_dir; else dir = ".";
sprintf(ham->ubn_file, "%s/%s.ubn", dir, str);
if (!(ubn_fd = fopen(ham->ubn_file, "w"))) out(NULL, "WARNING: Unable to create UBN file: %s\n", ham->ubn_file);
} else ubn_fd = NULL;
/* Print UBN header */
report_add(ubn_fd, "\"Unique, Bad, Not in log\" (UBN) file for \"%s\" contest\n", config.contest.name);
report_add(ubn_fd, "Created by Contest Arbitrator version %s\n\n", VERSION);
report_add(ubn_fd, "List of log checking errors and warnings:\n\n");
/* Perform general contest's QSO checking */
qso_num = 1;
dt = errors = 0;
qso = ham->qso;
while (qso) {
if (qso->ham_id == -1) { /* Not resolved yet */
his_call = qso->callsign;
/* Look for the his_call on all ham records in the database */
for (j = 0; j < n; j++) {
if (j != i) { /* Skip current ham record */
remote = base->ham[j]; /* Remote correspondent */
if (strcasecmp(remote->callsign, his_call) == 0) {
/* Found his_call in the database */
qso->ham_id = j; /* ID of ham with callsign 'his_call' */
break;
}
}
}
if (qso->ham_id < 0) {
out(ubn_fd, "WARNING: %s, QSO #%u: Unresolved QSO with %s\n", my_call, qso_num, his_call);
qso_unres++;
errors++;
j = unresolved_add(his_call); /* Place unresolved call into a separate list */
if (j < -1) qso->ham_id = j;
} else {
if (qso->checked == QSO_STATUS_NOT_CHECKED) {
/* Look for his QSO with us */
his_qso = remote->qso;
j = 0;
while (his_qso) {
delta_time = his_qso->timestamp - qso->timestamp;
/* Check callsigns in his log */
if (his_qso->checked == QSO_STATUS_NOT_CHECKED && strcasecmp(his_qso->callsign, my_call) == 0 && \
abs(delta_time) < GENERAL_XCHECK_TIME_DIFFERENCE) {
j = 1; /* Found */
/* Resolve links for both of us */
qso->his_qso = his_qso;
his_qso->his_qso = qso;
his_qso->ham_id = i;
break;
}
his_qso = his_qso->next;
}
if (j > 0) {
/* Perform general cross-checking */
his_errors = my_errors = 0;
/* Accumulate difference in time */
dt += delta_time;
/* Check frequencies */
tmp1 = band(his_qso->freq);
tmp2 = band(qso->freq);
if (tmp1 > 0 && tmp2 > 0 && tmp1 != tmp2) {
out(ubn_fd, " ERROR: %s, QSO #%u: Operating bands differs in QSO with %s (%s <--> %s)\n", my_call, qso_num,
his_call, get_band(tmp1), get_band(tmp2));
his_errors++;
my_errors++;
}
/* Check operating modes */
tmp1 = his_qso->mode_op;
tmp2 = qso->mode_op;
if (tmp1 > 0 && tmp2 > 0 && tmp1 != tmp2) {
out(ubn_fd, " ERROR: %s, QSO #%u: Operating modes differs in QSO with %s (%s <--> %s)\n", my_call, qso_num,
his_call, get_qso_mode(tmp1), get_qso_mode(tmp2));
his_errors++;
my_errors++;
}
/* Count errors */
if (his_errors > 0) {
his_qso->checked = QSO_STATUS_ERROR;
} else his_qso->checked = QSO_STATUS_GENERAL_XCHECK_OK;
if (my_errors > 0) {
qso->checked = QSO_STATUS_ERROR;
errors += my_errors;
} else qso->checked = QSO_STATUS_GENERAL_XCHECK_OK;
if (his_errors == 0 && my_errors == 0) qso_ok++;
} else {
/* Not found */
out(ubn_fd, " ERROR: %s, QSO #%u: not in the log of %s\n", my_call, qso_num, his_call);
errors++;
qso->checked = QSO_STATUS_ERROR;
}
}
}
}
qso = qso->next;
qso_num++;
}
/* Adjust and print systematic difference in time */
ham->systematic_dt = dt / (int)qso_num;
out(NULL, " INFO: %s: Systematic difference in time = %s%u min %u sec\n", my_call,
ham->systematic_dt < 0 ? "-" : "", ham->systematic_dt < 0 ? (unsigned int)(-ham->systematic_dt / 60) : (unsigned int)(ham->systematic_dt / 60),
ham->systematic_dt < 0 ? (unsigned int)(-ham->systematic_dt % 60) : (unsigned int)(ham->systematic_dt % 60));
out(NULL, "================================================================================================\n");
/* Write statistics to UBN file */
report_add(ubn_fd, "General cross-check totals: %u errors and warnings\n\n", errors);
if (ubn_fd) fclose(ubn_fd);
qso_err += errors;
}
out(NULL, "\nCABRILLO: General cross-check completed\n"
"CABRILLO: %u QSOs are OK\n"
"CABRILLO: %u QSOs with errors or warnings\n"
"CABRILLO: %u QSOs are unresolved\n\n", qso_ok, qso_err, qso_unres);
#ifdef _DEBUG_
// dump_database(base);
#endif
unresolved_print();
DEBUG("CABRILLO: <== cross_check(%p, %d)\n", base, contest_id);
return 0;
}
static int unresolved_add(char *callsign) {
int i;
LINK_S *link, *prev;
DEBUG("CABRILLO: ==> unresolved_add(%s)\n", callsign);
/* Search for existing callsign in the unresolved list */
link = prev = unresolved.link;
i = 0;
while (link) {
if (strcasecmp(link->callsign, callsign) == 0) {
/* Found */
link->cnt++; /* Increment number of total links with callsign */
break;
}
i++;
prev = link;
link = (LINK_S*)link->next;
}
if (!link) {
/* Not found. So add new one */
if (!(link = calloc(1, sizeof(LINK_S)))) {
warn(ERR_UNABLE_TO_ALLOCATE_SPACE, NULL, NULL, NULL);
return 1; /* Error */
}
strcpy(link->callsign, callsign);
link->cnt = 1;
/* Make list */
if (prev) prev->next = link;
else unresolved.link = link;
unresolved.cnt++;
}
DEBUG("CABRILLO: <== unresolved_add(%s) = %d\n", callsign, -(i + 2));
return -(i + 2); /* Negative index of unresolved callsign */
}
static void unresolved_print(void) {
int n, i;
unsigned int max;
LINK_S *link, *ptr, *max_ptr;
DEBUG("CABRILLO: ==> unresolved_print()\n");
if (unresolved.cnt > 0) {
/* Copy unresolved links to separate struct */
if (!(link = calloc(unresolved.cnt, sizeof(LINK_S)))) {
perror("calloc");
return;
}
/* Copy links payload */
for (i = 0, ptr = unresolved.link; i < unresolved.cnt && ptr; i++) {
memcpy(&link[i], ptr, sizeof(LINK_S));
ptr = (LINK_S*)(ptr->next);
}
out(NULL, "\nMissing logs list\n"
"------------------+----------\n"
" Callsign | QSOs \n"
"------------------+----------\n");
/* Sort links */
for (n = 0; n < unresolved.cnt; n++) {
/* Sort */
max = 0;
max_ptr = NULL;
for (i = 0; i < unresolved.cnt; i++) {
ptr = &link[i];
if (ptr->cnt > max) {
max = ptr->cnt;
max_ptr = ptr;
}
}
/* Print */
if (max_ptr) {
/* Output formatting */
out(NULL, " %s", max_ptr->callsign);
for (i = strlen(max_ptr->callsign) + 5; i < 18; i++) out(NULL, " ");
out(NULL, "| %u\n", max_ptr->cnt);
max_ptr->cnt = 0; /* Reset used link */
} else break;
}
out(NULL, "------------------+----------\n\n");
/* Free temporary structure */
free(link);
}
DEBUG("CABRILLO: <== unresolved_print()\n");
}
void unresolved_free(void) {
LINK_S *link, *next;
DEBUG("CABRILLO: ==> unresolved_free()\n");
/* Step down to the end and free every record in the list */
link = unresolved.link;
while (link) {
next = link->next;
free(link);
link = next;
}
unresolved.link = NULL;
unresolved.cnt = 0;
DEBUG("CABRILLO: <== unresolved_free()\n");
}
int unresolved_get(int id) {
/* Returns number of QSOs linked with ham id */
int i;
LINK_S *p;
DEBUG("CABRILLO: ==> unresolved_get(%d)\n", id);
/* Go to requested unresolved ham id */
i = -(id + 2); /* Unresolved index */
p = unresolved.link;
while (p && i-- > 0) p = (LINK_S*)(p->next);
if (p) i = p->cnt; /* Links count */
else i = -1; /* Not found */
DEBUG("CABRILLO: <== unresolved_get(%d) = %d\n", id, i);
return i;
}
int band(unsigned int freq) {
int i;
char *name;
i = 0;
while (band_list[i].name) {
if (freq >= band_list[i].lo_range && freq <= band_list[i].hi_range) return band_list[i].id;
i++;
}
return 0; /* Not found */
}
int mode_type2id(int type) {
/* Return QSO operating mode id */
int i;
i = 0;
while (qso_mode[i].name) {
if (qso_mode[i].id == type) return qso_mode[i].op_mode;
i++;
}
return 0; /* Unresolved */
}
static char *qso_status(int status) {
char *stat;
switch (status) {
case QSO_STATUS_CONTEST_ERROR:
/* Contest specific checking has failed */
stat = "CONTEST SPECIFIC ERROR";
break;
case QSO_STATUS_ERROR:
/* General checking has failed */
stat = "ERROR";
break;
case QSO_STATUS_NOT_CHECKED:
/* QSO is not checked yet */
stat = "NOT CHECKED";
break;
case QSO_STATUS_GENERAL_XCHECK_OK:
/* QSO has passed general cross-check */
stat = "GENERAL CROSS-CHECK OK";
break;
case QSO_STATUS_CONTEST_XCHECK_FAIL:
/* Contest specific cross-check is in progress */
stat = "CONTEST SPECIFIC CROSS-CHECK FAIL";
break;
case QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_RECV:
/* Contest specific cross-check is failed from this QSO side (and probably valid from other QSO side) */
stat = "CONTEST SPECIFIC CROSS-CHECK FROM ONE SIDE (FAIL RECV)";
break;
case QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_FAIL_SENT:
/* Contest specific cross-check is failed from this QSO side (and probably valid from other QSO side) */
stat = "CONTEST SPECIFIC CROSS-CHECK FROM ONE SIDE (FAIL SENT)";
break;
case QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID:
/* Contest specific cross-check is valid from this QSO side */
stat = "CONTEST SPECIFIC CROSS-CHECK FROM ONE SIDE (VALID)";
break;
case QSO_STATUS_CONTEST_XCHECK_OK:
/* Contest specific cross-check is in progress */
stat = "CONTEST SPECIFIC CROSS-CHECK OK";
break;
default:
/* Unknown status */
stat = "UNKNOWN";
}
return stat;
}