suricata
util-macset.c
Go to the documentation of this file.
1 /* Copyright (C) 2020 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Sascha Steinbiss <sascha.steinbiss@dcso.de>
22  *
23  * Set-like data store for MAC addresses. Implemented as array for memory
24  * locality reasons as the expected number of items is typically low.
25  *
26  */
27 
28 #include "suricata-common.h"
29 #include "suricata.h"
30 #include "flow-util.h"
31 #include "flow-private.h"
32 #include "flow-storage.h"
33 #include "util-macset.h"
34 #include "util-unittest.h"
35 #include "util-unittest-helper.h"
36 #include "conf.h"
37 
38 typedef uint8_t MacAddr[6];
39 typedef enum {
40  EMPTY_SET, /* no address inserted yet */
41  SINGLE_MAC, /* we have a single pair of addresses (likely) */
42  MULTI_MAC /* we have multiple addresses per flow */
44 
45 struct MacSet_ {
46  /* static store for a single MAC address per side */
48  /* state determines how addresses are stored per side:
49  - SINGLE_MAC uses static locations allocated with the MacSet
50  itself to store a single address (most likely case)
51  - MULTI_MAC is used when more than one distinct address
52  is detected (causes another allocation and linear-time add) */
54  /* buffer for multiple MACs per flow and direction */
55  MacAddr *buf[2];
56  int size, last[2];
57 };
58 
60 
62 {
63  SCConfNode *root = SCConfGetNode("outputs");
64  SCConfNode *node = NULL;
65  /* we only need to register if at least one enabled 'eve-log' output
66  has the ethernet setting enabled */
67  if (root != NULL) {
68  TAILQ_FOREACH (node, &root->head, next) {
69  if (node->val && strcmp(node->val, "eve-log") == 0) {
70  const char *enabled = SCConfNodeLookupChildValue(node->head.tqh_first, "enabled");
71  if (enabled != NULL && SCConfValIsTrue(enabled)) {
72  const char *ethernet =
73  SCConfNodeLookupChildValue(node->head.tqh_first, "ethernet");
74  if (ethernet != NULL && SCConfValIsTrue(ethernet)) {
76  "macset", sizeof(void *), NULL, (void (*)(void *))MacSetFree);
77  return;
78  }
79  }
80  }
81  }
82  }
83 }
84 
86 {
87  return (g_macset_storage_id.id != -1);
88 }
89 
90 MacSet *MacSetInit(int size)
91 {
92  MacSet *ms = NULL;
93  if (!FLOW_CHECK_MEMCAP(sizeof(*ms))) {
94  return NULL;
95  }
96  ms = SCCalloc(1, sizeof(*ms));
97  if (unlikely(ms == NULL)) {
98  SCLogError("Unable to allocate MacSet memory");
99  return NULL;
100  }
101  (void)SC_ATOMIC_ADD(flow_memuse, (sizeof(*ms)));
103  if (size < 3) {
104  /* we want to make sure we have at space for at least 3 items to
105  fit MACs during the initial extension to MULTI_MAC storage */
106  size = 3;
107  }
108  ms->size = size;
109  ms->last[MAC_SET_SRC] = ms->last[MAC_SET_DST] = 0;
110  return ms;
111 }
112 
114 {
115  return g_macset_storage_id;
116 }
117 
118 static inline void MacUpdateEntry(
119  MacSet *ms, const uint8_t *addr, int side, ThreadVars *tv, StatsCounterMaxId ctr)
120 {
121  switch (ms->state[side]) {
122  case EMPTY_SET:
123  memcpy(ms->singles[side], addr, sizeof(MacAddr));
124  ms->state[side] = SINGLE_MAC;
125  if (tv != NULL)
126  StatsCounterMaxUpdateI64(&tv->stats, ctr, 1);
127  break;
128  case SINGLE_MAC:
129  if (unlikely(memcmp(addr, ms->singles[side], sizeof(MacAddr)) != 0)) {
130  if (ms->buf[side] == NULL) {
131  if (!FLOW_CHECK_MEMCAP(ms->size * sizeof(MacAddr))) {
132  /* in this case there is not much we can do */
133  return;
134  }
135  ms->buf[side] = SCCalloc(ms->size, sizeof(MacAddr));
136  if (unlikely(ms->buf[side] == NULL)) {
137  SCLogError("Unable to allocate "
138  "MacSet memory");
139  return;
140  }
141  (void)SC_ATOMIC_ADD(flow_memuse, (ms->size * sizeof(MacAddr)));
142  }
143  memcpy(ms->buf[side], ms->singles[side], sizeof(MacAddr));
144  memcpy(ms->buf[side] + 1, addr, sizeof(MacAddr));
145  ms->last[side] = 2;
146  if (tv != NULL)
147  StatsCounterMaxUpdateI64(&tv->stats, ctr, 2);
148  ms->state[side] = MULTI_MAC;
149  }
150  break;
151  case MULTI_MAC:
152  if (unlikely(ms->last[side] == ms->size)) {
153  /* MacSet full, ignore item. We intentionally do not output
154  any warning in order not to stall packet processing */
155  return;
156  }
157  /* If the set is non-empty... */
158  if (ms->last[side] > 0) {
159  /* ...we search for duplicates in the set to decide whether
160  we need to insert the current item. We do this backwards,
161  since we expect the latest item to match more likely than
162  the first */
163  for (int i = ms->last[side] - 1; i >= 0; i--) {
164  uint8_t *addr2 = (uint8_t *)((ms->buf[side]) + i);
165  /* If we find a match, we return early with no action */
166  if (likely(memcmp(addr2, addr, sizeof(MacAddr)) == 0)) {
167  return;
168  }
169  }
170  }
171  /* Otherwise, we insert the new address at the end */
172  memcpy(ms->buf[side] + ms->last[side], addr, sizeof(MacAddr));
173  ms->last[side]++;
174  if (tv != NULL)
175  StatsCounterMaxUpdateI64(&tv->stats, ctr, (int64_t)ms->last[side]);
176  break;
177  }
178 }
179 
180 void MacSetAddWithCtr(MacSet *ms, const uint8_t *src_addr, const uint8_t *dst_addr, ThreadVars *tv,
181  StatsCounterMaxId ctr_src, StatsCounterMaxId ctr_dst)
182 {
183  if (ms == NULL)
184  return;
185  MacUpdateEntry(ms, src_addr, MAC_SET_SRC, tv, ctr_src);
186  MacUpdateEntry(ms, dst_addr, MAC_SET_DST, tv, ctr_dst);
187 }
188 
189 void MacSetAdd(MacSet *ms, const uint8_t *src_addr, const uint8_t *dst_addr)
190 {
191  StatsCounterMaxId no_counter = { .id = 0 };
192  MacSetAddWithCtr(ms, src_addr, dst_addr, NULL, no_counter, no_counter);
193 }
194 
195 static inline int MacSetIterateSide(
196  const MacSet *ms, MacSetIteratorFunc IterFunc, MacSetSide side, void *data)
197 {
198  int ret = 0;
199  switch (ms->state[side]) {
200  case EMPTY_SET:
201  return 0;
202  case SINGLE_MAC:
203  ret = IterFunc((uint8_t *)ms->singles[side], side, data);
204  if (unlikely(ret != 0)) {
205  return ret;
206  }
207  break;
208  case MULTI_MAC:
209  for (int i = 0; i < ms->last[side]; i++) {
210  ret = IterFunc((uint8_t *)ms->buf[side][i], side, data);
211  if (unlikely(ret != 0)) {
212  return ret;
213  }
214  }
215  break;
216  }
217  return 0;
218 }
219 
220 int MacSetForEach(const MacSet *ms, MacSetIteratorFunc IterFunc, void *data)
221 {
222  int ret = 0;
223  if (ms == NULL)
224  return 0;
225 
226  ret = MacSetIterateSide(ms, IterFunc, MAC_SET_SRC, data);
227  if (ret != 0) {
228  return ret;
229  }
230  return MacSetIterateSide(ms, IterFunc, MAC_SET_DST, data);
231 }
232 
233 uint8_t *MacSetGetFirst(const MacSet *ms, MacSetSide side)
234 {
235  switch (ms->state[side]) {
236  case EMPTY_SET:
237  return NULL;
238  case SINGLE_MAC:
239  return (uint8_t *)ms->singles[side];
240  case MULTI_MAC:
241  return (uint8_t *)ms->buf[side][0];
242  }
243  return NULL;
244 }
245 
246 int MacSetSize(const MacSet *ms)
247 {
248  int size = 0;
249  if (ms == NULL)
250  return 0;
251 
252  switch (ms->state[MAC_SET_SRC]) {
253  case EMPTY_SET:
254  /* pass */
255  break;
256  case SINGLE_MAC:
257  size += 1;
258  break;
259  case MULTI_MAC:
260  size += ms->last[MAC_SET_SRC];
261  break;
262  }
263  switch (ms->state[MAC_SET_DST]) {
264  case EMPTY_SET:
265  /* pass */
266  break;
267  case SINGLE_MAC:
268  size += 1;
269  break;
270  case MULTI_MAC:
271  size += ms->last[MAC_SET_DST];
272  break;
273  }
274  return size;
275 }
276 
278 {
279  size_t total_free = 0;
280  if (ms == NULL)
281  return;
282  if (ms->buf[MAC_SET_SRC] != NULL) {
283  SCFree(ms->buf[MAC_SET_SRC]);
284  total_free += ms->size * sizeof(MacAddr);
285  }
286  if (ms->buf[MAC_SET_DST] != NULL) {
287  SCFree(ms->buf[MAC_SET_DST]);
288  total_free += ms->size * sizeof(MacAddr);
289  }
290  SCFree(ms);
291  total_free += sizeof(*ms);
292  (void)SC_ATOMIC_SUB(flow_memuse, total_free);
293 }
294 
296 {
297  if (ms == NULL)
298  return;
299 
300  MacAddr tmp_single;
301  memcpy(tmp_single, ms->singles[0], sizeof(MacAddr));
302  memcpy(ms->singles[0], ms->singles[1], sizeof(MacAddr));
303  memcpy(ms->singles[1], tmp_single, sizeof(MacAddr));
304 
305  MacSetState tmp_state = ms->state[0];
306  ms->state[0] = ms->state[1];
307  ms->state[1] = tmp_state;
308 
309  MacAddr *tmp_buf = ms->buf[0];
310  ms->buf[0] = ms->buf[1];
311  ms->buf[1] = tmp_buf;
312 
313  int tmp_last = ms->last[0];
314  ms->last[0] = ms->last[1];
315  ms->last[1] = tmp_last;
316 }
317 
318 #ifdef UNITTESTS
319 
320 static int CheckTest1Membership(uint8_t *addr, MacSetSide side, void *data)
321 {
322  int *i = (int *)data;
323  switch (*i) {
324  case 0:
325  if (addr[5] != 1)
326  return 1;
327  break;
328  case 1:
329  if (addr[5] != 2)
330  return 1;
331  break;
332  case 2:
333  if (addr[5] != 3)
334  return 1;
335  break;
336  }
337  (*i)++;
338  return 0;
339 }
340 
341 static int MacSetTest01(void)
342 {
343  MacSet *ms = NULL;
344  int ret = 0, i = 0;
345  MacAddr addr1 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 }, addr2 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 },
346  addr3 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x3 };
347  SC_ATOMIC_SET(flow_config.memcap, 10000);
348 
349  ms = MacSetInit(10);
350  FAIL_IF_NULL(ms);
351  FAIL_IF_NOT(MacSetSize(ms) == 0);
352 
353  ret = MacSetForEach(ms, CheckTest1Membership, &i);
354  FAIL_IF_NOT(ret == 0);
355 
356  MacSetAdd(ms, addr1, addr2);
357  FAIL_IF_NOT(MacSetSize(ms) == 2);
358 
359  ret = MacSetForEach(ms, CheckTest1Membership, &i);
360  FAIL_IF_NOT(ret == 0);
361 
362  MacSetAdd(ms, addr1, addr3);
363  FAIL_IF_NOT(MacSetSize(ms) == 3);
364 
365  i = 0;
366  ret = MacSetForEach(ms, CheckTest1Membership, &i);
367  FAIL_IF_NOT(ret == 0);
368 
369  MacSetFree(ms);
370  PASS;
371 }
372 
373 static int MacSetTest02(void)
374 {
375  MacSet *ms = NULL;
376  int ret = 0, i = 0;
377  SC_ATOMIC_SET(flow_config.memcap, 10000);
378 
379  ms = MacSetInit(10);
380  FAIL_IF_NULL(ms);
381  FAIL_IF_NOT(MacSetSize(ms) == 0);
382 
383  for (i = 1; i < 100; i++) {
384  MacAddr addr1 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 }, addr2 = { 0x1, 0x0, 0x0, 0x0, 0x0, 0x2 };
385  MacSetAdd(ms, addr1, addr2);
386  }
387  FAIL_IF_NOT(MacSetSize(ms) == 2);
388 
389  ret = MacSetForEach(ms, CheckTest1Membership, &i);
390  FAIL_IF_NOT(ret == 0);
391 
392  MacSetFree(ms);
393  PASS;
394 }
395 
396 static int MacSetTest03(void)
397 {
398  MacSet *ms = NULL;
399  SC_ATOMIC_SET(flow_config.memcap, 10000);
400 
401  ms = MacSetInit(10);
402  FAIL_IF_NULL(ms);
403  FAIL_IF_NOT(MacSetSize(ms) == 0);
404 
405  for (uint8_t i = 1; i < 100; i++) {
406  MacAddr addr1 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 }, addr2 = { 0x1, 0x0, 0x0, 0x0, 0x0, 0x1 };
407  addr1[5] = i;
408  addr2[5] = i;
409  MacSetAdd(ms, addr1, addr2);
410  }
411  FAIL_IF_NOT(MacSetSize(ms) == 20);
412 
413  MacSetFree(ms);
414  PASS;
415 }
416 
417 static int MacSetTest04(void)
418 {
419  MacSet *ms = NULL;
420  SC_ATOMIC_SET(flow_config.memcap, 2);
421 
422  ms = MacSetInit(10);
423  FAIL_IF_NOT_NULL(ms);
424 
425  PASS;
426 }
427 
428 static int MacSetTest05(void)
429 {
430  MacSet *ms = NULL;
431  int ret = 0;
432  SC_ATOMIC_SET(flow_config.memcap, 64);
433 
434  ms = MacSetInit(10);
435  FAIL_IF_NULL(ms);
436  FAIL_IF_NOT(MacSetSize(ms) == 0);
437 
438  for (uint8_t i = 1; i < 100; i++) {
439  MacAddr addr1 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 }, addr2 = { 0x1, 0x0, 0x0, 0x0, 0x0, 0x1 };
440  addr1[5] = i;
441  addr2[5] = i;
442  MacSetAdd(ms, addr1, addr2);
443  }
444  FAIL_IF_NOT(MacSetSize(ms) == 2);
445 
446  int i2 = 100;
447  ret = MacSetForEach(ms, CheckTest1Membership, &i2);
448  FAIL_IF_NOT(ret == 0);
449 
450  MacSetFree(ms);
451  PASS;
452 }
453 
454 static int MacSetTest06(void)
455 {
456  SC_ATOMIC_SET(flow_config.memcap, 128);
457 
458  MacSet *ms = MacSetInit(10);
459  FAIL_IF_NULL(ms);
460  FAIL_IF_NOT(MacSetSize(ms) == 0);
461 
462  uint8_t *src0 = MacSetGetFirst(ms, MAC_SET_SRC);
463  uint8_t *dst0 = MacSetGetFirst(ms, MAC_SET_DST);
464 
465  MacAddr addr1 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 }, addr2 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 },
466  addr3 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x3 }, addr4 = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x4 };
467 
468  MacSetAdd(ms, addr1, addr2);
469  uint8_t *src1 = MacSetGetFirst(ms, MAC_SET_SRC);
470  uint8_t *dst1 = MacSetGetFirst(ms, MAC_SET_DST);
471 
472  MacSetAdd(ms, addr3, addr4);
473  uint8_t *src2 = MacSetGetFirst(ms, MAC_SET_SRC);
474  uint8_t *dst2 = MacSetGetFirst(ms, MAC_SET_DST);
475 
476  FAIL_IF_NOT_NULL(src0);
477  FAIL_IF_NOT_NULL(dst0);
478  FAIL_IF_NOT(src1[5] == addr1[5]);
479  FAIL_IF_NOT(dst1[5] == addr2[5]);
480  FAIL_IF_NOT(src2[5] == addr1[5]);
481  FAIL_IF_NOT(dst2[5] == addr2[5]);
482 
483  MacSetFree(ms);
484  PASS;
485 }
486 
487 #endif /* UNITTESTS */
488 
490 {
491 
492 #ifdef UNITTESTS
493  UtRegisterTest("MacSetTest01", MacSetTest01);
494  UtRegisterTest("MacSetTest02", MacSetTest02);
495  UtRegisterTest("MacSetTest03", MacSetTest03);
496  UtRegisterTest("MacSetTest04", MacSetTest04);
497  UtRegisterTest("MacSetTest05", MacSetTest05);
498  UtRegisterTest("MacSetTest06", MacSetTest06);
499 #endif
500 }
FlowStorageId
Definition: flow-storage.h:31
MacSetAddWithCtr
void MacSetAddWithCtr(MacSet *ms, const uint8_t *src_addr, const uint8_t *dst_addr, ThreadVars *tv, StatsCounterMaxId ctr_src, StatsCounterMaxId ctr_dst)
Definition: util-macset.c:180
MacSetSide
MacSetSide
Definition: util-macset.h:28
StatsCounterMaxUpdateI64
void StatsCounterMaxUpdateI64(StatsThreadContext *stats, StatsCounterMaxId id, int64_t x)
update the value of the localmax counter
Definition: counters.c:224
SCConfValIsTrue
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition: conf.c:551
FAIL_IF_NULL
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
Definition: util-unittest.h:89
SINGLE_MAC
@ SINGLE_MAC
Definition: util-macset.c:41
MacSetFree
void MacSetFree(MacSet *ms)
Definition: util-macset.c:277
flow-util.h
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
SC_ATOMIC_SET
#define SC_ATOMIC_SET(name, val)
Set the value for the atomic variable.
Definition: util-atomic.h:386
UtRegisterTest
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
Definition: util-unittest.c:103
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
MacSetIteratorFunc
int(* MacSetIteratorFunc)(uint8_t *addr, MacSetSide side, void *)
Definition: util-macset.h:30
util-macset.h
flow-private.h
SC_ATOMIC_ADD
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
Definition: util-atomic.h:332
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
MacSetRegisterFlowStorage
void MacSetRegisterFlowStorage(void)
Definition: util-macset.c:61
SCConfNodeLookupChildValue
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition: conf.c:824
util-unittest.h
util-unittest-helper.h
FAIL_IF_NOT
#define FAIL_IF_NOT(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:82
FLOW_CHECK_MEMCAP
#define FLOW_CHECK_MEMCAP(size)
check if a memory alloc would fit in the memcap
Definition: flow-util.h:134
MULTI_MAC
@ MULTI_MAC
Definition: util-macset.c:42
FAIL_IF_NOT_NULL
#define FAIL_IF_NOT_NULL(expr)
Fail a test if expression evaluates to non-NULL.
Definition: util-unittest.h:96
PASS
#define PASS
Pass the test.
Definition: util-unittest.h:105
StatsCounterMaxId
Definition: counters.h:38
MacSet_::singles
MacAddr singles[2]
Definition: util-macset.c:47
ThreadVars_
Per thread variable structure.
Definition: threadvars.h:58
MacSet_::last
int last[2]
Definition: util-macset.c:56
FlowStorageRegister
FlowStorageId FlowStorageRegister(const char *name, const unsigned int size, void *(*Alloc)(unsigned int), void(*Free)(void *))
Definition: flow-storage.c:66
MAC_SET_DST
@ MAC_SET_DST
Definition: util-macset.h:28
MacSetFlowStorageEnabled
bool MacSetFlowStorageEnabled(void)
Definition: util-macset.c:85
SC_ATOMIC_SUB
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
Definition: util-atomic.h:341
MacSet_::size
int size
Definition: util-macset.c:56
MacSet_
Definition: util-macset.c:45
conf.h
MacSetGetFlowStorageID
FlowStorageId MacSetGetFlowStorageID(void)
Definition: util-macset.c:113
MacSetRegisterTests
void MacSetRegisterTests(void)
Definition: util-macset.c:489
MacSetAdd
void MacSetAdd(MacSet *ms, const uint8_t *src_addr, const uint8_t *dst_addr)
Definition: util-macset.c:189
EMPTY_SET
@ EMPTY_SET
Definition: util-macset.c:40
MAC_SET_SRC
@ MAC_SET_SRC
Definition: util-macset.h:28
MacSetState
MacSetState
Definition: util-macset.c:39
flow-storage.h
suricata-common.h
MacSetSize
int MacSetSize(const MacSet *ms)
Definition: util-macset.c:246
flow_config
FlowConfig flow_config
Definition: flow.c:94
StatsCounterMaxId::id
uint16_t id
Definition: counters.h:39
MacSetInit
MacSet * MacSetInit(int size)
Definition: util-macset.c:90
tv
ThreadVars * tv
Definition: fuzz_decodepcapfile.c:33
MacSetSwap
void MacSetSwap(MacSet *ms)
Definition: util-macset.c:295
SCConfGetNode
SCConfNode * SCConfGetNode(const char *name)
Get a SCConfNode by name.
Definition: conf.c:181
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
SCFree
#define SCFree(p)
Definition: util-mem.h:61
MacSetGetFirst
uint8_t * MacSetGetFirst(const MacSet *ms, MacSetSide side)
Definition: util-macset.c:233
MacSetForEach
int MacSetForEach(const MacSet *ms, MacSetIteratorFunc IterFunc, void *data)
Definition: util-macset.c:220
FlowStorageId::id
int id
Definition: flow-storage.h:32
MacSet_::state
MacSetState state[2]
Definition: util-macset.c:53
suricata.h
MacAddr
uint8_t MacAddr[6]
Definition: util-macset.c:38
likely
#define likely(expr)
Definition: util-optimize.h:32
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
ThreadVars_::stats
StatsThreadContext stats
Definition: threadvars.h:121
SCConfNode_
Definition: conf.h:37
MacSet_::buf
MacAddr * buf[2]
Definition: util-macset.c:55
SCConfNode_::val
char * val
Definition: conf.h:39
g_macset_storage_id
FlowStorageId g_macset_storage_id
Definition: util-macset.c:59