3 * File format support for EGNOS Message Server files
4 * Copyright (c) 2023 by Timo Warns <timo.warns@gmail.com>
6 * SPDX-License-Identifier: GPL-2.0-or-later
11 #define WS_LOG_DOMAIN LOG_DOMAIN_WIRETAP
18 #include "file_wrappers.h"
20 #include <wsutil/buffer.h>
21 #include <wsutil/nstime.h>
22 #include <wsutil/strtoi.h>
23 #include <wsutil/wslog.h>
25 static bool ems_read(wtap
*wth
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char
26 **err_info
, int64_t *data_offset
);
27 static bool ems_seek_read(wtap
*wth
, int64_t seek_off
, wtap_rec
*rec
, Buffer
28 *buf
, int *err
, char **err_info
);
30 #define MAX_EMS_LINE_LEN 256
31 #define EMS_MSG_SIZE 40
33 typedef struct ems_msg_s
{
45 static int ems_file_type_subtype
= -1;
48 * Gets one character and returns in case of error.
49 * Without error, peeks at next character and returns it.
51 static int get_and_peek(FILE_T fh
) {
60 return file_peekc(fh
);
64 * Peeks / returns next relevant character.
65 * Skips whitespace at the beginning of a line, comment lines, and empty
68 static int peek_relevant_character(FILE_T fh
) {
78 // ignore whitespace at the beginning of a line
79 else if (g_ascii_isspace(c
)) {
80 ws_debug("ignoring whitespace at the beginning of line");
86 } while (g_ascii_isspace(c
));
91 // ignore comment and empty lines
92 else if (c
== '\r' || c
== '\n' || c
== '#') {
93 ws_debug("ignoring comment or empty line");
104 // return current character for further inspection
112 * Parses EMS line to ems_msg struct.
113 * Return false on error, true otherwise.
115 static bool parse_ems_line(FILE_T fh
, ems_msg_t
* ems_msg
) {
116 char line
[MAX_EMS_LINE_LEN
];
119 if (!file_gets(line
, array_length(line
), fh
)) {
123 i
= sscanf(line
, "%03u %02u %02u %02u %02u %02u %02u %u %64c",
137 if (ems_msg
->prn
> 255 ||
138 ems_msg
->year
> 255 ||
139 ems_msg
->month
> 12 ||
141 ems_msg
->hour
> 23 ||
142 ems_msg
->minute
> 59 ||
143 ems_msg
->second
> 59 ||
151 wtap_open_return_val
ems_open(wtap
*wth
, int *err
, char **err_info
) {
155 ws_debug("opening file");
157 // skip irrelevant characters
158 c
= peek_relevant_character(wth
->fh
);
160 if (file_eof(wth
->fh
)) {
161 return WTAP_OPEN_NOT_MINE
;
163 *err
= file_error(wth
->fh
, err_info
);
164 return WTAP_OPEN_ERROR
;
167 // EMS nav msg lines start with a digit (first digit of PRN).
168 // Check whether current line starts with a digit.
169 if (!g_ascii_isdigit(c
)) {
170 return WTAP_OPEN_NOT_MINE
;
173 // Check whether the current line matches the EMS format
174 if (parse_ems_line(wth
->fh
, &msg
)) {
175 /* return to the beginning of the file */
176 if (file_seek(wth
->fh
, 0, SEEK_SET
, err
) == -1) {
177 *err
= file_error(wth
->fh
, err_info
);
178 return WTAP_OPEN_ERROR
;
181 wth
->file_encap
= WTAP_ENCAP_EMS
;
182 wth
->snapshot_length
= 0;
183 wth
->file_tsprec
= WTAP_TSPREC_SEC
;
184 wth
->subtype_read
= ems_read
;
185 wth
->subtype_seek_read
= ems_seek_read
;
186 wth
->file_type_subtype
= ems_file_type_subtype
;
188 return WTAP_OPEN_MINE
;
191 return WTAP_OPEN_NOT_MINE
;
194 static bool ems_read_message(FILE_T fh
, wtap_rec
*rec
, Buffer
*buf
,
195 int *err
, char **err_info
) {
200 // skip irrelevant characters
201 c
= peek_relevant_character(fh
);
203 *err
= file_error(fh
, err_info
);
207 // parse line with EMS message
208 if (parse_ems_line(fh
, &msg
)) {
209 char ts
[NSTIME_ISO8601_BUFSIZE
+ 1];
211 ws_buffer_assure_space(buf
, EMS_MSG_SIZE
);
213 ws_buffer_end_ptr(buf
)[0] = msg
.prn
;
214 ws_buffer_end_ptr(buf
)[1] = msg
.year
;
215 ws_buffer_end_ptr(buf
)[2] = msg
.month
;
216 ws_buffer_end_ptr(buf
)[3] = msg
.day
;
217 ws_buffer_end_ptr(buf
)[4] = msg
.hour
;
218 ws_buffer_end_ptr(buf
)[5] = msg
.minute
;
219 ws_buffer_end_ptr(buf
)[6] = msg
.second
;
220 ws_buffer_end_ptr(buf
)[7] = msg
.mt
;
223 for (i
= 0; i
< 32; i
++) {
225 char s
[3] = {msg
.sbas_msg
[i
*2], msg
.sbas_msg
[i
*2+1], 0};
226 if (!ws_hexstrtou8(s
, NULL
, &v
)) {
229 ws_buffer_end_ptr(buf
)[8 + i
] = v
;
232 ws_buffer_increase_length(buf
, EMS_MSG_SIZE
);
234 rec
->rec_type
= REC_TYPE_PACKET
;
235 rec
->block
= wtap_block_create(WTAP_BLOCK_PACKET
);
236 rec
->presence_flags
= WTAP_HAS_TS
;
237 rec
->rec_header
.packet_header
.len
= EMS_MSG_SIZE
;
238 rec
->rec_header
.packet_header
.caplen
= EMS_MSG_SIZE
;
240 // use EMS timestamp as packet timestamp
241 snprintf(ts
, sizeof(ts
), "%04u-%02u-%02uT%02u:%02u:%02uZ", msg
.year
242 + 2000, msg
.month
, msg
.day
, msg
.hour
, msg
.minute
, msg
.second
);
243 iso8601_to_nstime(&rec
->ts
, ts
, ISO8601_DATETIME
);
251 static bool ems_read(wtap
*wth
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char
252 **err_info
, int64_t *offset
) {
254 *offset
= file_tell(wth
->fh
);
255 ws_debug("reading at offset %" PRId64
, *offset
);
257 if (!ems_read_message(wth
->fh
, rec
, buf
, err
, err_info
)) {
264 static bool ems_seek_read(wtap
*wth
, int64_t offset
, wtap_rec
*rec
, Buffer
265 *buf
, int *err
, char **err_info
) {
267 if (file_seek(wth
->random_fh
, offset
, SEEK_SET
, err
) == -1) {
268 *err
= file_error(wth
->fh
, err_info
);
272 if (!ems_read_message(wth
->random_fh
, rec
, buf
, err
, err_info
)) {
279 static const struct supported_block_type ems_blocks_supported
[] = {
280 { WTAP_BLOCK_PACKET
, MULTIPLE_BLOCKS_SUPPORTED
, NO_OPTIONS_SUPPORTED
}
283 static const struct file_type_subtype_info ems_info
= {
284 "EGNOS Message Server File Format", "ems", "ems", "ems",
285 false, BLOCKS_SUPPORTED(ems_blocks_supported
),
289 void register_ems(void)
291 ems_file_type_subtype
= wtap_register_file_type_subtype(&ems_info
);
293 wtap_register_backwards_compatibility_lua_name("EMS",
294 ems_file_type_subtype
);