Coverage Report

Created: 2026-02-21 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/lib/ptm_lib.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/* PTM Library
3
 * Copyright (C) 2015 Cumulus Networks, Inc.
4
 */
5
6
#ifdef HAVE_CONFIG_H
7
#include "config.h"
8
#endif
9
10
#include <stdio.h>
11
#include <stdlib.h>
12
#include <stdbool.h>
13
#include <stddef.h>
14
#include <string.h>
15
#include <ctype.h>
16
#include <unistd.h>
17
#include <errno.h>
18
#include <sys/socket.h>
19
#include "csv.h"
20
#include "ptm_lib.h"
21
22
0
#define DEBUG_E 0
23
0
#define DEBUG_V 0
24
25
#define ERRLOG(fmt, ...)                                                       \
26
0
  do {                                                                   \
27
0
    if (DEBUG_E)                                                   \
28
0
      fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__,          \
29
0
        __LINE__, __func__, ##__VA_ARGS__);            \
30
0
  } while (0)
31
32
#define DLOG(fmt, ...)                                                         \
33
0
  do {                                                                   \
34
0
    if (DEBUG_V)                                                   \
35
0
      fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__,          \
36
0
        __LINE__, __func__, ##__VA_ARGS__);            \
37
0
  } while (0)
38
39
typedef struct ptm_lib_msg_ctxt_s {
40
  int cmd_id;
41
  csv_t *csv;
42
  ptmlib_msg_type type;
43
} ptm_lib_msg_ctxt_t;
44
45
static csv_record_t *_ptm_lib_encode_header(csv_t *csv, csv_record_t *rec,
46
              int msglen, int version, int type,
47
              int cmd_id, char *client_name)
48
0
{
49
0
  char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16];
50
0
  char client_buf[32];
51
0
  csv_record_t *rec1;
52
53
0
  snprintf(msglen_buf, sizeof(msglen_buf), "%4d", msglen);
54
0
  snprintf(vers_buf, sizeof(vers_buf), "%4d", version);
55
0
  snprintf(type_buf, sizeof(type_buf), "%4d", type);
56
0
  snprintf(cmdid_buf, sizeof(cmdid_buf), "%4d", cmd_id);
57
0
  snprintf(client_buf, 17, "%16.16s", client_name);
58
0
  if (rec) {
59
0
    rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf,
60
0
           type_buf, cmdid_buf, client_buf);
61
0
  } else {
62
0
    rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, type_buf,
63
0
          cmdid_buf, client_buf);
64
0
  }
65
0
  return (rec1);
66
0
}
67
68
static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version,
69
          int *type, int *cmd_id, char *client_name)
70
0
{
71
0
  char *hdr;
72
0
  csv_record_t *rec;
73
0
  csv_field_t *fld;
74
0
  int i, j;
75
76
0
  csv_decode(csv, NULL);
77
0
  rec = csv_record_iter(csv);
78
0
  if (rec == NULL) {
79
0
    DLOG("malformed CSV\n");
80
0
    return -1;
81
0
  }
82
0
  hdr = csv_field_iter(rec, &fld);
83
0
  if (hdr == NULL) {
84
0
    DLOG("malformed CSV\n");
85
0
    return -1;
86
0
  }
87
0
  *msglen = atoi(hdr);
88
0
  hdr = csv_field_iter_next(&fld);
89
0
  if (hdr == NULL) {
90
0
    DLOG("malformed CSV\n");
91
0
    return -1;
92
0
  }
93
0
  *version = atoi(hdr);
94
0
  hdr = csv_field_iter_next(&fld);
95
0
  if (hdr == NULL) {
96
0
    DLOG("malformed CSV\n");
97
0
    return -1;
98
0
  }
99
0
  *type = atoi(hdr);
100
0
  hdr = csv_field_iter_next(&fld);
101
0
  if (hdr == NULL) {
102
0
    DLOG("malformed CSV\n");
103
0
    return -1;
104
0
  }
105
0
  *cmd_id = atoi(hdr);
106
0
  hdr = csv_field_iter_next(&fld);
107
0
  if (hdr == NULL) {
108
0
    DLOG("malformed CSV\n");
109
0
    return -1;
110
0
  }
111
  /* remove leading spaces */
112
0
  for (i = j = 0; i < csv_field_len(fld); i++) {
113
0
    if (!isspace((unsigned char)hdr[i])) {
114
0
      client_name[j] = hdr[i];
115
0
      j++;
116
0
    }
117
0
  }
118
0
  client_name[j] = '\0';
119
120
0
  return 0;
121
0
}
122
123
int ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, const char *key,
124
           const char *val)
125
0
{
126
0
  ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
127
0
  csv_t *csv;
128
0
  csv_record_t *mh_rec, *rec;
129
130
0
  if (!p_ctxt) {
131
0
    ERRLOG("%s: no context \n", __func__);
132
0
    return -1;
133
0
  }
134
135
0
  csv = p_ctxt->csv;
136
0
  mh_rec = csv_record_iter(csv);
137
0
  rec = csv_record_iter_next(mh_rec);
138
139
  /* append to the hdr record */
140
0
  rec = csv_append_record(csv, rec, 1, key);
141
0
  if (!rec) {
142
0
    ERRLOG("%s: Could not append key \n", __func__);
143
0
    return -1;
144
0
  }
145
146
0
  rec = csv_record_iter_next(rec);
147
  /* append to the data record */
148
0
  rec = csv_append_record(csv, rec, 1, val);
149
0
  if (!rec) {
150
0
    ERRLOG("%s: Could not append val \n", __func__);
151
0
    return -1;
152
0
  }
153
154
  /* update the msg hdr */
155
0
  _ptm_lib_encode_header(csv, mh_rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
156
0
             PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
157
0
             hdl->client_name);
158
159
0
  return 0;
160
0
}
161
162
int ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, void *in_ctxt,
163
         void **out_ctxt)
164
0
{
165
0
  ptm_lib_msg_ctxt_t *p_ctxt;
166
0
  ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt;
167
0
  csv_t *csv;
168
0
  csv_record_t *rec, *d_rec;
169
170
  /* Initialize csv for using discrete record buffers */
171
0
  csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
172
173
0
  if (!csv) {
174
0
    ERRLOG("%s: Could not allocate csv \n", __func__);
175
0
    return -1;
176
0
  }
177
178
0
  rec = _ptm_lib_encode_header(csv, NULL, 0, PTMLIB_MSG_VERSION, type,
179
0
             cmd_id, hdl->client_name);
180
181
0
  if (!rec) {
182
0
    ERRLOG("%s: Could not allocate record \n", __func__);
183
0
    csv_clean(csv);
184
0
    csv_free(csv);
185
0
    return -1;
186
0
  }
187
188
0
  p_ctxt = calloc(1, sizeof(*p_ctxt));
189
0
  if (!p_ctxt) {
190
0
    ERRLOG("%s: Could not allocate context \n", __func__);
191
0
    csv_clean(csv);
192
0
    csv_free(csv);
193
0
    return -1;
194
0
  }
195
196
0
  p_ctxt->csv = csv;
197
0
  p_ctxt->cmd_id = cmd_id;
198
0
  p_ctxt->type = type;
199
200
0
  *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt;
201
202
  /* caller supplied a context to initialize with? */
203
0
  if (p_in_ctxt) {
204
    /* insert the hdr rec */
205
0
    rec = csv_record_iter(p_in_ctxt->csv);
206
0
    csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
207
0
    csv_insert_record(csv, d_rec);
208
    /* insert the data rec */
209
0
    rec = csv_record_iter_next(rec);
210
0
    csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
211
0
    csv_insert_record(csv, d_rec);
212
0
  }
213
0
  return 0;
214
0
}
215
216
int ptm_lib_cleanup_msg(ptm_lib_handle_t *hdl, void *ctxt)
217
0
{
218
0
  ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
219
0
  csv_t *csv;
220
221
0
  if (!p_ctxt) {
222
0
    ERRLOG("%s: no context \n", __func__);
223
0
    return -1;
224
0
  }
225
226
0
  csv = p_ctxt->csv;
227
228
0
  csv_clean(csv);
229
0
  csv_free(csv);
230
0
  free(p_ctxt);
231
232
0
  return 0;
233
0
}
234
235
int ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, char *buf, int *len)
236
0
{
237
0
  ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
238
0
  csv_t *csv;
239
0
  csv_record_t *rec;
240
241
0
  if (!p_ctxt) {
242
0
    ERRLOG("%s: no context \n", __func__);
243
0
    return -1;
244
0
  }
245
246
0
  csv = p_ctxt->csv;
247
0
  rec = csv_record_iter(csv);
248
249
0
  _ptm_lib_encode_header(csv, rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
250
0
             PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
251
0
             hdl->client_name);
252
253
  /* parse csv contents into string */
254
0
  if (buf && len) {
255
0
    if (csv_serialize(csv, buf, *len)) {
256
0
      ERRLOG("%s: cannot serialize\n", __func__);
257
0
      return -1;
258
0
    }
259
0
    *len = csvlen(csv);
260
0
  }
261
262
0
  csv_clean(csv);
263
0
  csv_free(csv);
264
0
  free(p_ctxt);
265
266
0
  return 0;
267
0
}
268
269
int ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val)
270
0
{
271
0
  ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
272
0
  csv_t *csv = p_ctxt->csv;
273
0
  csv_record_t *hrec, *drec;
274
0
  csv_field_t *hfld, *dfld;
275
0
  char *hstr, *dstr;
276
277
  /**
278
   * skip over ptm hdr if present
279
   * The next hdr is the keys (column name)
280
   * The next hdr is the data
281
   */
282
0
  if (csv_num_records(csv) > 2) {
283
0
    hrec = csv_record_iter(csv);
284
0
    hrec = csv_record_iter_next(hrec);
285
0
  } else {
286
0
    hrec = csv_record_iter(csv);
287
0
  }
288
0
  drec = csv_record_iter_next(hrec);
289
0
  val[0] = '\0';
290
0
  for (hstr = csv_field_iter(hrec, &hfld),
291
0
      dstr = csv_field_iter(drec, &dfld);
292
0
       (hstr && dstr); hstr = csv_field_iter_next(&hfld),
293
0
      dstr = csv_field_iter_next(&dfld)) {
294
0
    if (!strncmp(hstr, key, csv_field_len(hfld))) {
295
0
      snprintf(val, csv_field_len(dfld) + 1, "%s", dstr);
296
0
      return 0;
297
0
    }
298
0
  }
299
300
0
  return -1;
301
0
}
302
303
static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len)
304
0
{
305
0
  int retries = 0, rc;
306
0
  int bytes_read = 0;
307
308
0
  while (bytes_read != len) {
309
0
    rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read),
310
0
        MSG_DONTWAIT);
311
0
    if (rc <= 0) {
312
0
      if (errno && (errno != EAGAIN)
313
0
          && (errno != EWOULDBLOCK)) {
314
0
        ERRLOG("fatal recv error(%s), closing connection, rc %d\n",
315
0
               strerror(errno), rc);
316
0
        return (rc);
317
0
      } else {
318
0
        if (retries++ < 2) {
319
0
          usleep(10000);
320
0
          continue;
321
0
        }
322
0
        DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n",
323
0
             errno, strerror(errno), bytes_read, len);
324
0
        return (bytes_read);
325
0
      }
326
0
      break;
327
0
    } else {
328
0
      bytes_read += rc;
329
0
    }
330
0
  }
331
332
0
  return bytes_read;
333
0
}
334
335
int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen,
336
      void *arg)
337
0
{
338
0
  int rc, len;
339
0
  char client_name[32];
340
0
  int cmd_id = 0, type = 0, ver = 0, msglen = 0;
341
0
  csv_t *csv;
342
0
  ptm_lib_msg_ctxt_t *p_ctxt = NULL;
343
344
0
  len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN);
345
0
  if (len <= 0)
346
0
    return (len);
347
348
0
  csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN);
349
350
0
  if (!csv) {
351
0
    DLOG("Cannot allocate csv for hdr\n");
352
0
    return -1;
353
0
  }
354
355
0
  rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id,
356
0
            client_name);
357
358
0
  csv_clean(csv);
359
0
  csv_free(csv);
360
361
0
  if (rc < 0) {
362
    /* could not decode the CSV - maybe its legacy cmd?
363
     * get the entire cmd from the socket and see if we can process
364
     * it
365
     */
366
0
    if (len == PTMLIB_MSG_HDR_LEN) {
367
0
      len += _ptm_lib_read_ptm_socket(
368
0
        fd, (inbuf + PTMLIB_MSG_HDR_LEN),
369
0
        inlen - PTMLIB_MSG_HDR_LEN);
370
0
      if (len <= 0)
371
0
        return (len);
372
0
    }
373
374
0
    inbuf[len] = '\0';
375
    /* we only support the get-status cmd */
376
0
    if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) {
377
0
      DLOG("unsupported legacy cmd %s\n", inbuf);
378
0
      return -1;
379
0
    }
380
    /* internally create a csv-style cmd */
381
0
    ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL,
382
0
         (void *)&p_ctxt);
383
0
    if (!p_ctxt) {
384
0
      DLOG("couldnt allocate context\n");
385
0
      return -1;
386
0
    }
387
0
    ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS);
388
389
0
  } else {
390
391
0
    if (msglen > inlen) {
392
0
      DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen);
393
0
      return -1;
394
0
    }
395
396
    /* read the rest of the msg */
397
0
    len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen);
398
0
    if (len <= 0) {
399
0
      return (len);
400
0
    }
401
402
0
    inbuf[len] = '\0';
403
404
0
    csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
405
0
    if (!csv) {
406
0
      ERRLOG("Cannot allocate csv for msg\n");
407
0
      return -1;
408
0
    }
409
410
0
    csv_decode(csv, inbuf);
411
0
    p_ctxt = calloc(1, sizeof(*p_ctxt));
412
0
    if (!p_ctxt) {
413
0
      ERRLOG("%s: Could not allocate context \n", __func__);
414
0
      csv_clean(csv);
415
0
      csv_free(csv);
416
0
      return -1;
417
0
    }
418
419
0
    p_ctxt->csv = csv;
420
0
    p_ctxt->cmd_id = cmd_id;
421
0
    p_ctxt->type = type;
422
0
  }
423
424
0
  switch (p_ctxt->type) {
425
0
  case PTMLIB_MSG_TYPE_NOTIFICATION:
426
0
    if (hdl->notify_cb)
427
0
      hdl->notify_cb(arg, p_ctxt);
428
0
    break;
429
0
  case PTMLIB_MSG_TYPE_CMD:
430
0
    if (hdl->cmd_cb)
431
0
      hdl->cmd_cb(arg, p_ctxt);
432
0
    break;
433
0
  case PTMLIB_MSG_TYPE_RESPONSE:
434
0
    if (hdl->response_cb)
435
0
      hdl->response_cb(arg, p_ctxt);
436
0
    break;
437
0
  default:
438
0
    return -1;
439
0
  }
440
441
0
  csv_clean(p_ctxt->csv);
442
0
  csv_free(p_ctxt->csv);
443
0
  free(p_ctxt);
444
445
0
  return len;
446
0
}
447
448
ptm_lib_handle_t *ptm_lib_register(char *client_name, ptm_cmd_cb cmd_cb,
449
           ptm_notify_cb notify_cb,
450
           ptm_response_cb response_cb)
451
0
{
452
0
  ptm_lib_handle_t *hdl;
453
454
0
  hdl = calloc(1, sizeof(*hdl));
455
456
0
  if (hdl) {
457
0
    strncpy(hdl->client_name, client_name, PTMLIB_MAXNAMELEN - 1);
458
0
    hdl->cmd_cb = cmd_cb;
459
0
    hdl->notify_cb = notify_cb;
460
0
    hdl->response_cb = response_cb;
461
0
  }
462
463
0
  return hdl;
464
0
}
465
466
void ptm_lib_deregister(ptm_lib_handle_t *hdl)
467
0
{
468
0
  if (hdl) {
469
0
    memset(hdl, 0x00, sizeof(*hdl));
470
0
    free(hdl);
471
0
  }
472
0
}